From 1423074485e2c94717f01dcbe98608da79c72a13 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 7 Oct 2020 19:15:48 +0300 Subject: [PATCH 001/102] fixed sending inactivity event after device creation. --- .../server/service/state/DefaultDeviceStateService.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 9878bbf1a2..c6f771216b 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.service.state; +import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; @@ -392,7 +393,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (stateData != null) { DeviceState state = stateData.getState(); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); - if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime())) { + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && UUIDs.unixTimestamp(deviceId.getId()) + state.getInactivityTimeout() < ts) { state.setLastInactivityAlarmTime(ts); pushRuleEngineMessage(stateData, INACTIVITY_EVENT); save(deviceId, INACTIVITY_ALARM_TIME, ts); From 62748eef60b163ab439a6b1f81675e4769152790 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Oct 2020 13:15:49 +0300 Subject: [PATCH 002/102] added deviceCreationTime to the DeviceStateData --- .../server/service/state/DefaultDeviceStateService.java | 3 ++- .../org/thingsboard/server/service/state/DeviceStateData.java | 4 ++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index c6f771216b..9d5df549da 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -393,7 +393,7 @@ public class DefaultDeviceStateService implements DeviceStateService { if (stateData != null) { DeviceState state = stateData.getState(); state.setActive(ts < state.getLastActivityTime() + state.getInactivityTimeout()); - if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && UUIDs.unixTimestamp(deviceId.getId()) + state.getInactivityTimeout() < ts) { + if (!state.isActive() && (state.getLastInactivityAlarmTime() == 0L || state.getLastInactivityAlarmTime() < state.getLastActivityTime()) && stateData.getDeviceCreationTime() + state.getInactivityTimeout() < ts) { state.setLastInactivityAlarmTime(ts); pushRuleEngineMessage(stateData, INACTIVITY_EVENT); save(deviceId, INACTIVITY_ALARM_TIME, ts); @@ -480,6 +480,7 @@ public class DefaultDeviceStateService implements DeviceStateService { return DeviceStateData.builder() .tenantId(device.getTenantId()) .deviceId(device.getId()) + .deviceCreationTime(device.getCreatedTime()) .metaData(md) .state(deviceState).build(); } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java index 1973570f9e..3ace93b89a 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DeviceStateData.java @@ -30,8 +30,8 @@ class DeviceStateData { private final TenantId tenantId; private final DeviceId deviceId; - + private final long deviceCreationTime; private TbMsgMetaData metaData; private final DeviceState state; - + } From bec23b419064bf8ca382fc3be081c325f3db3030 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 8 Oct 2020 13:22:01 +0300 Subject: [PATCH 003/102] refactored --- .../server/service/state/DefaultDeviceStateService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index 9d5df549da..dcc8d590b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.service.state; -import com.datastax.driver.core.utils.UUIDs; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.base.Function; import com.google.common.util.concurrent.FutureCallback; From b3f6938cf0d95c653663cf8470b38a58274badfb Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 7 Oct 2020 12:08:05 +0300 Subject: [PATCH 004/102] fix duplicates --- .../thingsboard/server/actors/ruleChain/DefaultTbContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 4fef96c000..622d60d7bf 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -114,7 +114,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); + TopicPartitionInfo tpi = resolvePartition(tbMsg); enqueue(tpi, tbMsg, onFailure, onSuccess); } From 560b950a8faa522268a5a9e04ddac501dc626d47 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Wed, 7 Oct 2020 12:31:13 +0300 Subject: [PATCH 005/102] added null check for queueName --- .../server/actors/ruleChain/DefaultTbContext.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 622d60d7bf..dc4bf5eb1e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; import io.netty.channel.EventLoopGroup; import lombok.extern.slf4j.Slf4j; import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.util.StringUtils; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleEngineRpcService; @@ -43,6 +44,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.queue.ServiceQueue; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.alarm.AlarmService; @@ -178,6 +180,9 @@ class DefaultTbContext implements TbContext { } private TopicPartitionInfo resolvePartition(TbMsg tbMsg, String queueName) { + if (StringUtils.isEmpty(queueName)) { + queueName = ServiceQueue.MAIN; + } return mainCtx.resolve(ServiceType.TB_RULE_ENGINE, queueName, getTenantId(), tbMsg.getOriginator()); } From b63be05ffad15be685bf5c5f26c82d8184bb3781 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Thu, 8 Oct 2020 13:19:49 +0300 Subject: [PATCH 006/102] revert the enqueue(root rule chain) method changes --- .../thingsboard/server/actors/ruleChain/DefaultTbContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index dc4bf5eb1e..e89daf4516 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -116,7 +116,7 @@ class DefaultTbContext implements TbContext { @Override public void enqueue(TbMsg tbMsg, Runnable onSuccess, Consumer onFailure) { - TopicPartitionInfo tpi = resolvePartition(tbMsg); + TopicPartitionInfo tpi = mainCtx.resolve(ServiceType.TB_RULE_ENGINE, getTenantId(), tbMsg.getOriginator()); enqueue(tpi, tbMsg, onFailure, onSuccess); } From 2e694e1d649d46c514d0bba86e3bc27054889d74 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 15:39:15 +0300 Subject: [PATCH 007/102] Dao layer for UsageRecords --- .../dao/usagerecord/UsageRecordService.java | 28 +++++ .../server/common/data/EntityType.java | 2 +- .../server/common/data/UsageRecord.java | 65 ++++++++++ .../server/common/data/UsageRecordKey.java | 27 +++++ .../server/common/data/id/UsageRecordId.java | 42 +++++++ .../server/dao/model/ModelConstants.java | 8 ++ .../dao/model/sql/UsageRecordEntity.java | 93 +++++++++++++++ .../sql/usagerecord/JpaUsageRecordDao.java | 60 ++++++++++ .../usagerecord/UsageRecordRepository.java | 43 +++++++ .../server/dao/tenant/TenantServiceImpl.java | 6 + .../dao/usagerecord/UsageRecordDao.java | 51 ++++++++ .../usagerecord/UsageRecordServiceImpl.java | 112 ++++++++++++++++++ .../resources/sql/schema-entities-hsql.sql | 9 ++ .../main/resources/sql/schema-entities.sql | 9 ++ .../dao/service/AbstractServiceTest.java | 4 + .../service/BaseUsageRecordServiceTest.java | 56 +++++++++ .../sql/UsageRecordServiceSqlTest.java | 24 ++++ .../resources/sql/hsql/drop-all-tables.sql | 1 + .../resources/sql/psql/drop-all-tables.sql | 1 + .../sql/timescale/drop-all-tables.sql | 1 + .../org/thingsboard/mqtt/MqttClientImpl.java | 1 + 21 files changed, 642 insertions(+), 1 deletion(-) create mode 100644 common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java create mode 100644 dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java new file mode 100644 index 0000000000..55adc257ad --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.id.TenantId; + +public interface UsageRecordService { + + UsageRecord findTenantUsageRecord(TenantId tenantId); + + void deleteUsageRecordsByTenantId(TenantId tenantId); + + void createDefaultUsageRecord(TenantId id); +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index cfe12a14cb..11bf45be76 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, USAGE_RECORD; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java new file mode 100644 index 0000000000..e96cf2e452 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java @@ -0,0 +1,65 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UsageRecordId; +import org.thingsboard.server.common.data.id.UserId; + +@ToString +@EqualsAndHashCode(callSuper = true) +public class UsageRecord extends BaseData implements HasTenantId { + + private static final long serialVersionUID = 8250339805336035966L; + + private TenantId tenantId; + private EntityId entityId; + + public UsageRecord() { + super(); + } + + public UsageRecord(UsageRecordId id) { + super(id); + } + + public UsageRecord(UsageRecord ur) { + super(ur); + this.tenantId = ur.getTenantId(); + this.entityId = ur.getEntityId(); + } + + @Override + public TenantId getTenantId() { + return tenantId; + } + + public void setTenantId(TenantId tenantId) { + this.tenantId = tenantId; + } + + public EntityId getEntityId() { + return entityId; + } + + public void setEntityId(EntityId entityId) { + this.entityId = entityId; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java new file mode 100644 index 0000000000..74a54cf713 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public enum UsageRecordKey { + + MSG_COUNT, + MSG_BYTES_COUNT, + DP_TRANSPORT_COUNT, + DP_STORAGE_COUNT, + RE_EXEC_COUNT, + JS_EXEC_COUNT + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java new file mode 100644 index 0000000000..76cfaaedf6 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.id; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import org.thingsboard.server.common.data.EntityType; + +import java.util.UUID; + +public class UsageRecordId extends UUIDBased implements EntityId { + + @JsonCreator + public UsageRecordId(@JsonProperty("id") UUID id) { + super(id); + } + + public static UsageRecordId fromString(String userId) { + return new UsageRecordId(UUID.fromString(userId)); + } + + @JsonIgnore + @Override + public EntityType getEntityType() { + return EntityType.USAGE_RECORD; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 14ab9ed9e0..3ee942b0be 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -438,6 +438,14 @@ public class ModelConstants { public static final String OAUTH2_TEMPLATE_LOGIN_BUTTON_LABEL_PROPERTY = OAUTH2_LOGIN_BUTTON_LABEL_PROPERTY; public static final String OAUTH2_TEMPLATE_HELP_LINK_PROPERTY = "help_link"; + /** + * Usage Record constants. + */ + public static final String UR_TABLE_NAME = "usage_record"; + public static final String UR_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; + public static final String UR_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; + public static final String UR_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; + /** * Cassandra attributes and timeseries constants. */ diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java new file mode 100644 index 0000000000..be5cc40621 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java @@ -0,0 +1,93 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.model.sql; + +import com.fasterxml.jackson.databind.JsonNode; +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.hibernate.annotations.Type; +import org.hibernate.annotations.TypeDef; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UsageRecordId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.model.BaseEntity; +import org.thingsboard.server.dao.model.BaseSqlEntity; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.model.SearchTextEntity; +import org.thingsboard.server.dao.util.mapping.JsonStringType; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; +import javax.persistence.Table; +import java.util.UUID; + +/** + * Created by Valerii Sosliuk on 4/21/2017. + */ +@Data +@EqualsAndHashCode(callSuper = true) +@Entity +@TypeDef(name = "json", typeClass = JsonStringType.class) +@Table(name = ModelConstants.UR_TABLE_NAME) +public class UsageRecordEntity extends BaseSqlEntity implements BaseEntity { + + @Column(name = ModelConstants.UR_TENANT_ID_COLUMN) + private UUID tenantId; + + @Column(name = ModelConstants.UR_ENTITY_TYPE_COLUMN) + private String entityType; + + @Column(name = ModelConstants.UR_ENTITY_ID_COLUMN) + private UUID entityId; + + public UsageRecordEntity() { + } + + public UsageRecordEntity(UsageRecord ur) { + if (ur.getId() != null) { + this.setUuid(ur.getId().getId()); + } + this.setCreatedTime(ur.getCreatedTime()); + if (ur.getTenantId() != null) { + this.tenantId = ur.getTenantId().getId(); + } + if (ur.getEntityId() != null) { + this.entityType = ur.getEntityId().getEntityType().name(); + this.entityId = ur.getEntityId().getId(); + } + } + + @Override + public UsageRecord toData() { + UsageRecord ur = new UsageRecord(new UsageRecordId(this.getUuid())); + ur.setCreatedTime(createdTime); + if (tenantId != null) { + ur.setTenantId(new TenantId(tenantId)); + } + if (entityId != null) { + ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); + } + return ur; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java new file mode 100644 index 0000000000..8d54e6b53b --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java @@ -0,0 +1,60 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.usagerecord; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.model.sql.UsageRecordEntity; +import org.thingsboard.server.dao.sql.JpaAbstractDao; +import org.thingsboard.server.dao.usagerecord.UsageRecordDao; + +import java.util.UUID; + +/** + * @author Andrii Shvaika + */ +@Component +public class JpaUsageRecordDao extends JpaAbstractDao implements UsageRecordDao { + + private final UsageRecordRepository usageRecordRepository; + + public JpaUsageRecordDao(UsageRecordRepository usageRecordRepository) { + this.usageRecordRepository = usageRecordRepository; + } + + @Override + protected Class getEntityClass() { + return UsageRecordEntity.class; + } + + @Override + protected CrudRepository getCrudRepository() { + return usageRecordRepository; + } + + @Override + public UsageRecord findTenantUsageRecord(UUID tenantId) { + return DaoUtil.getData(usageRecordRepository.findByTenantId(tenantId)); + } + + @Override + public void deleteUsageRecordsByTenantId(TenantId tenantId) { + usageRecordRepository.deleteUsageRecordsByTenantId(tenantId.getId()); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java new file mode 100644 index 0000000000..dc2b0d9ad7 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java @@ -0,0 +1,43 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.usagerecord; + +import org.springframework.data.jpa.repository.Modifying; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.query.Param; +import org.springframework.transaction.annotation.Transactional; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.model.sql.UsageRecordEntity; +import org.thingsboard.server.dao.model.sql.UserEntity; + +import java.util.UUID; + +/** + * @author Valerii Sosliuk + */ +public interface UsageRecordRepository extends CrudRepository { + + @Query("SELECT ur FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId " + + "AND ur.entityId = :tenantId AND ur.entityType = 'TENANT' ") + UsageRecordEntity findByTenantId(@Param("tenantId") UUID tenantId); + + @Transactional + @Modifying + @Query("DELETE FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId") + void deleteUsageRecordsByTenantId(@Param("tenantId") UUID tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index 52761184fd..bd9ee321e0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -39,6 +39,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; +import org.thingsboard.server.dao.usagerecord.UsageRecordService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -72,6 +73,9 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe @Autowired private DeviceProfileService deviceProfileService; + @Autowired + private UsageRecordService usageRecordService; + @Autowired private EntityViewService entityViewService; @@ -117,6 +121,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); if (tenant.getId() == null) { deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); + usageRecordService.createDefaultUsageRecord(savedTenant.getId()); } return savedTenant; } @@ -134,6 +139,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe deviceProfileService.deleteDeviceProfilesByTenantId(tenantId); userService.deleteTenantAdmins(tenantId); ruleChainService.deleteRuleChainsByTenantId(tenantId); + usageRecordService.deleteUsageRecordsByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java new file mode 100644 index 0000000000..3b71e4ce20 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java @@ -0,0 +1,51 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.Dao; + +import java.util.UUID; + +public interface UsageRecordDao extends Dao { + + /** + * Save or update usage record object + * + * @param usageRecord the usage record + * @return saved usage record entity + */ + UsageRecord save(TenantId tenantId, UsageRecord usageRecord); + + /** + * Find usage record by tenantId. + * + * @param tenantId the tenantId + * @return the corresponding usage record + */ + UsageRecord findTenantUsageRecord(UUID tenantId); + + /** + * Delete usage record by tenantId. + * + * @param tenantId the tenantId + */ + void deleteUsageRecordsByTenantId(TenantId tenantId); +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java new file mode 100644 index 0000000000..fdaa38a1a2 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java @@ -0,0 +1,112 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserCredentialsId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; +import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import java.util.HashMap; +import java.util.Map; + +import static org.thingsboard.server.dao.service.Validator.validateId; +import static org.thingsboard.server.dao.service.Validator.validatePageLink; +import static org.thingsboard.server.dao.service.Validator.validateString; + +@Service +@Slf4j +public class UsageRecordServiceImpl extends AbstractEntityService implements UsageRecordService { + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + + private final UsageRecordDao usageRecordDao; + private final TenantDao tenantDao; + + public UsageRecordServiceImpl(TenantDao tenantDao, UsageRecordDao usageRecordDao) { + this.tenantDao = tenantDao; + this.usageRecordDao = usageRecordDao; + } + + @Override + public void deleteUsageRecordsByTenantId(TenantId tenantId) { + log.trace("Executing deleteUsageRecordsByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + usageRecordDao.deleteUsageRecordsByTenantId(tenantId); + } + + @Override + public void createDefaultUsageRecord(TenantId tenantId) { + log.trace("Executing createDefaultUsageRecord [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + UsageRecord usageRecord = new UsageRecord(); + usageRecord.setTenantId(tenantId); + usageRecord.setEntityId(tenantId); + usageRecordValidator.validate(usageRecord, UsageRecord::getTenantId); + usageRecordDao.save(usageRecord.getTenantId(), usageRecord); + } + + @Override + public UsageRecord findTenantUsageRecord(TenantId tenantId) { + log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return usageRecordDao.findTenantUsageRecord(tenantId.getId()); + } + + private DataValidator usageRecordValidator = + new DataValidator() { + @Override + protected void validateDataImpl(TenantId requestTenantId, UsageRecord usageRecord) { + if (usageRecord.getTenantId() == null) { + throw new DataValidationException("UsageRecord should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(requestTenantId, usageRecord.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Asset is referencing to non-existent tenant!"); + } + } + if (usageRecord.getEntityId() == null) { + throw new DataValidationException("UsageRecord should be assigned to entity!"); + } else if (!EntityType.TENANT.equals(usageRecord.getEntityId().getEntityType())) { + throw new DataValidationException("Only Tenant Usage Records are supported!"); + } else if (!usageRecord.getTenantId().getId().equals(usageRecord.getEntityId().getId())) { + throw new DataValidationException("Can't assign one Usage Record to multiple tenants!"); + } + } + }; + +} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 41d9e3ac81..e3cbbda351 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -404,3 +404,12 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( help_link varchar(255), CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); + +CREATE TABLE IF NOT EXISTS usage_record ( + id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid, + entity_type varchar(32), + entity_id uuid, + CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) +); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index e1ffba98eb..6cd3adb337 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -431,6 +431,15 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); +CREATE TABLE IF NOT EXISTS usage_record ( + id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, + created_time bigint NOT NULL, + tenant_id uuid, + entity_type varchar(32), + entity_id uuid, + CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) +); + CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) LANGUAGE plpgsql AS $$ diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index cf6f574180..8da1727631 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -61,6 +61,7 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.usagerecord.UsageRecordService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -85,6 +86,9 @@ public abstract class AbstractServiceTest { @Autowired protected UserService userService; + @Autowired + protected UsageRecordService usageRecordService; + @Autowired protected AdminSettingsService adminSettingsService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java new file mode 100644 index 0000000000..28e0a6d54a --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java @@ -0,0 +1,56 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.service.AbstractServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + + +public abstract class BaseUsageRecordServiceTest extends AbstractServiceTest { + + private TenantId tenantId; + + @Before + public void before() { + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + Tenant savedTenant = tenantService.saveTenant(tenant); + Assert.assertNotNull(savedTenant); + tenantId = savedTenant.getId(); + } + + @After + public void after() { + tenantService.deleteTenant(tenantId); + } + + @Test + public void testFindUsageRecordByTenantId() { + UsageRecord usageRecord = usageRecordService.findTenantUsageRecord(tenantId); + Assert.assertNotNull(usageRecord); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java new file mode 100644 index 0000000000..8275b92044 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.service.sql; + +import org.thingsboard.server.dao.service.BaseUsageRecordServiceTest; +import org.thingsboard.server.dao.service.BaseUserServiceTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +@DaoSqlTest +public class UsageRecordServiceSqlTest extends BaseUsageRecordServiceTest { +} diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index 160049a144..13c1eba943 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -27,4 +27,5 @@ DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; +DROP TABLE IF EXISTS usage_record; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index e277c39403..b8ab69c758 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -28,3 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; +DROP TABLE IF EXISTS usage_record; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index da7eaff876..4333bc33ee 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -28,3 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; +DROP TABLE IF EXISTS usage_record; \ No newline at end of file diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index aef2cad684..08e6fc4f71 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java @@ -340,6 +340,7 @@ final class MqttClientImpl implements MqttClient { MqttPublishVariableHeader variableHeader = new MqttPublishVariableHeader(topic, getNewMessageId().messageId()); MqttPublishMessage message = new MqttPublishMessage(fixedHeader, variableHeader, payload); MqttPendingPublish pendingPublish = new MqttPendingPublish(variableHeader.packetId(), future, payload.retain(), message, qos); + ChannelFuture channelFuture = this.sendAndFlushPacket(message); if (channelFuture != null) { From 24f5872adf3f50f36629ef0d5fb3b791c92be838 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 18:00:20 +0300 Subject: [PATCH 008/102] Queue interfaces with no implementation --- .../queue/provider/TbCoreQueueFactory.java | 13 ++- .../provider/TbQueueProducerProvider.java | 11 ++ .../provider/TbRuleEngineQueueFactory.java | 2 +- .../provider/TbTransportQueueFactory.java | 10 +- .../TbTransportQueueProducerProvider.java | 7 ++ .../TbUsageStatsClientQueueFactory.java | 29 +++++ .../scheduler/DefaultSchedulerComponent.java | 61 ++++++++++ .../queue/scheduler/SchedulerComponent.java | 32 ++++++ .../usagestats/DefaultTbUsageStatsClient.java | 106 ++++++++++++++++++ .../queue/usagestats/TbUsageStatsClient.java | 26 +++++ common/queue/src/main/proto/queue.proto | 13 +++ .../transport/mqtt/MqttTransportHandler.java | 5 +- .../common/transport/TransportContext.java | 3 + .../common/transport/TransportService.java | 8 +- .../service/DefaultTransportService.java | 24 ++-- 15 files changed, 318 insertions(+), 32 deletions(-) create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index 59a8f449b0..d009757997 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -16,6 +16,7 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -33,7 +34,7 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; * Responsible for initialization of various Producers and Consumers used by TB Core Node. * Implementation Depends on the queue queue.type from yml or TB_QUEUE_TYPE environment variable */ -public interface TbCoreQueueFactory { +public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory { /** * Used to push messages to instances of TB Transport Service @@ -77,6 +78,16 @@ public interface TbCoreQueueFactory { */ TbQueueConsumer> createToCoreMsgConsumer(); + /** + * Used to consume messages about usage statistics by TB Core Service + * + * @return + */ + default TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + //TODO: implement + return null; + } + /** * Used to consume high priority messages by TB Core Service * diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java index 34c6ee577f..fa20f11960 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.queue.provider; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -63,4 +64,14 @@ public interface TbQueueProducerProvider { */ TbQueueProducer> getTbCoreNotificationsMsgProducer(); + /** + * Used to push messages to other instances of TB Core Service + * + * @return + */ + default TbQueueProducer> getTbUsageStatsMsgProducer() { + //TODO: implement + return null; + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java index 561b3e84ea..2f80bbce45 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineQueueFactory.java @@ -32,7 +32,7 @@ import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; * Responsible for initialization of various Producers and Consumers used by TB Core Node. * Implementation Depends on the queue queue.type from yml or TB_QUEUE_TYPE environment variable */ -public interface TbRuleEngineQueueFactory { +public interface TbRuleEngineQueueFactory extends TbUsageStatsClientQueueFactory { /** * Used to push messages to instances of TB Transport Service diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java index dc1d2c449c..bd6c6e0584 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueFactory.java @@ -15,17 +15,17 @@ */ package org.thingsboard.server.queue.provider; -import org.thingsboard.server.queue.TbQueueConsumer; -import org.thingsboard.server.queue.TbQueueProducer; -import org.thingsboard.server.queue.TbQueueRequestTemplate; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; +import org.thingsboard.server.queue.TbQueueConsumer; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.TbQueueRequestTemplate; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; -public interface TbTransportQueueFactory { +public interface TbTransportQueueFactory extends TbUsageStatsClientQueueFactory { TbQueueRequestTemplate, TbProtoQueueMsg> createTransportApiRequestTemplate(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java index bced3f83ac..9d11aa727b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java @@ -30,6 +30,7 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider private final TbTransportQueueFactory tbQueueProvider; private TbQueueProducer> toRuleEngine; private TbQueueProducer> toTbCore; + private TbQueueProducer> toUsageStats; public TbTransportQueueProducerProvider(TbTransportQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -39,6 +40,7 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider public void init() { this.toTbCore = tbQueueProvider.createTbCoreMsgProducer(); this.toRuleEngine = tbQueueProvider.createRuleEngineMsgProducer(); + this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); } @Override @@ -65,4 +67,9 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider public TbQueueProducer> getTbCoreNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } + + @Override + public TbQueueProducer> getTbUsageStatsMsgProducer() { + return toUsageStats; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java new file mode 100644 index 0000000000..1811877338 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java @@ -0,0 +1,29 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.provider; + +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +public interface TbUsageStatsClientQueueFactory { + + default TbQueueProducer> createToUsageStatsServiceMsgProducer() { + //TODO: implement + return null; + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java new file mode 100644 index 0000000000..02ce60496e --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/DefaultSchedulerComponent.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.scheduler; + +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.ThingsBoardThreadFactory; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +@Component +public class DefaultSchedulerComponent implements SchedulerComponent{ + + protected ScheduledExecutorService schedulerExecutor; + + @PostConstruct + public void init(){ + this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("queue-scheduler")); + } + + @PreDestroy + public void destroy() { + if (schedulerExecutor != null) { + schedulerExecutor.shutdownNow(); + } + } + + public ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit) { + return schedulerExecutor.schedule(command, delay, unit); + } + + public ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit) { + return schedulerExecutor.schedule(callable, delay, unit); + } + + public ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit) { + return schedulerExecutor.scheduleAtFixedRate(command, initialDelay, period, unit); + } + + public ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit) { + return schedulerExecutor.scheduleWithFixedDelay(command, initialDelay, delay, unit); + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java new file mode 100644 index 0000000000..3c12be43a5 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/scheduler/SchedulerComponent.java @@ -0,0 +1,32 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.scheduler; + +import java.util.concurrent.Callable; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +public interface SchedulerComponent { + + ScheduledFuture schedule(Runnable command, long delay, TimeUnit unit); + + ScheduledFuture schedule(Callable callable, long delay, TimeUnit unit); + + ScheduledFuture scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); + + ScheduledFuture scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java new file mode 100644 index 0000000000..87eb5e767a --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.usagestats; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; + +import javax.annotation.PostConstruct; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicLong; + +@Component +public class DefaultTbUsageStatsClient implements TbUsageStatsClient { + + @Value("${usage.stats.report.enabled:true}") + private boolean enabled; + @Value("${usage.stats.report.interval:600}") + private int interval; + + private final ConcurrentMap[] values = new ConcurrentMap[UsageRecordKey.values().length]; + private final PartitionService partitionService; + private final SchedulerComponent scheduler; + private final TbQueueProducerProvider producerProvider; + private TbQueueProducer> msgProducer; + + public DefaultTbUsageStatsClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { + this.partitionService = partitionService; + this.scheduler = scheduler; + this.producerProvider = producerProvider; + } + + @PostConstruct + private void init() { + if (enabled) { + msgProducer = this.producerProvider.getTbUsageStatsMsgProducer(); + for (UsageRecordKey key : UsageRecordKey.values()) { + values[key.ordinal()] = new ConcurrentHashMap<>(); + } + scheduler.scheduleWithFixedDelay(this::reportStats, new Random().nextInt(interval), interval, TimeUnit.SECONDS); + } + } + + private void reportStats() { + ConcurrentMap report = new ConcurrentHashMap<>(); + + for (UsageRecordKey key : UsageRecordKey.values()) { + values[key.ordinal()].forEach(((tenantId, atomicLong) -> { + long value = atomicLong.getAndSet(0); + if (value > 0) { + ToUsageStatsServiceMsg.Builder msgBuilder = report.computeIfAbsent(tenantId, id -> { + ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); + msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); + msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); + return msg; + }); + msgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); + } + })); + } + + report.forEach(((tenantId, builder) -> { + //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); + msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); + })); + } + + @Override + public void report(TenantId tenantId, UsageRecordKey key, long value) { + if (enabled) { + ConcurrentMap map = values[key.ordinal()]; + AtomicLong atomicValue = map.computeIfAbsent(tenantId, id -> new AtomicLong()); + atomicValue.addAndGet(value); + } + } + +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java new file mode 100644 index 0000000000..e4e659b5c5 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.usagestats; + +import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.id.TenantId; + +public interface TbUsageStatsClient { + + void report(TenantId tenantId, UsageRecordKey key, long value); + + +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index 01d8ca4524..f716bd90ae 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -532,3 +532,16 @@ message ToTransportMsg { EntityDeleteMsg entityDeleteMsg = 9; ProvisionDeviceResponseMsg provisionResponse = 10; } + +message UsageStatsKVProto{ + string key = 1; + int64 value = 2; +} + +message ToUsageStatsServiceMsg { + int64 tenantIdMSB = 1; + int64 tenantIdLSB = 2; + int64 entityIdMSB = 3; + int64 entityIdLSB = 4; + repeated UsageStatsKVProto values = 5; +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 3723b19f6d..2c4b2db7df 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -57,6 +57,7 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.transport.mqtt.adaptors.MqttTransportAdaptor; import org.thingsboard.server.transport.mqtt.session.DeviceSessionCtx; import org.thingsboard.server.transport.mqtt.session.GatewaySessionHandler; @@ -97,6 +98,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement private final UUID sessionId; private final MqttTransportContext context; private final TransportService transportService; + private final SchedulerComponent scheduler; private final SslHandler sslHandler; private final ConcurrentMap mqttQoSMap; @@ -108,6 +110,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement this.sessionId = UUID.randomUUID(); this.context = context; this.transportService = context.getTransportService(); + this.scheduler = context.getScheduler(); this.sslHandler = sslHandler; this.mqttQoSMap = new ConcurrentHashMap<>(); this.deviceSessionCtx = new DeviceSessionCtx(sessionId, mqttQoSMap, context); @@ -333,7 +336,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement } else { deviceSessionCtx.getContext().getProtoMqttAdaptor().convertToPublish(deviceSessionCtx, provisionResponseMsg).ifPresent(deviceSessionCtx.getChannel()::writeAndFlush); } - transportService.getSchedulerExecutor().schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); + scheduler.schedule(() -> processDisconnect(ctx), 60, TimeUnit.SECONDS); } catch (Exception e) { log.trace("[{}] Failed to convert device attributes response to MQTT msg", sessionId, e); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java index 66809f9e61..4b7677dd69 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportContext.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -44,6 +45,8 @@ public abstract class TransportContext { private TransportService transportService; @Autowired private TbServiceInfoProvider serviceInfoProvider; + @Autowired + private SchedulerComponent scheduler; @Getter private ExecutorService executor; diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 8f57db3897..4872d8e477 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -17,16 +17,14 @@ package org.thingsboard.server.common.transport; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitType; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; @@ -42,8 +40,6 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCre import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; -import java.util.concurrent.ScheduledExecutorService; - /** * Created by ashvayka on 04.10.18. */ @@ -92,8 +88,6 @@ public interface TransportService { void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback); - ScheduledExecutorService getSchedulerExecutor(); - void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener); void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 3f37685383..774a437e39 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -23,7 +23,6 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DeviceProfile; @@ -53,7 +52,6 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.limits.TransportRateLimit; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; @@ -79,6 +77,7 @@ import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -94,7 +93,6 @@ import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -126,6 +124,7 @@ public class DefaultTransportService implements TransportService { private final TransportTenantProfileCache tenantProfileCache; private final TransportRateLimitService rateLimitService; private final DataDecodingEncodingService dataDecodingEncodingService; + private final SchedulerComponent scheduler; protected TbQueueRequestTemplate, TbProtoQueueMsg> transportApiRequestTemplate; protected TbQueueProducer> ruleEngineMsgProducer; @@ -136,7 +135,6 @@ public class DefaultTransportService implements TransportService { protected MessagesStats tbCoreProducerStats; protected MessagesStats transportApiStats; - protected ScheduledExecutorService schedulerExecutor; protected ExecutorService transportCallbackExecutor; private ExecutorService mainConsumerExecutor; @@ -152,7 +150,7 @@ public class DefaultTransportService implements TransportService { StatsFactory statsFactory, TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, - TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService) { + TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; this.producerProvider = producerProvider; @@ -162,6 +160,7 @@ public class DefaultTransportService implements TransportService { this.tenantProfileCache = tenantProfileCache; this.rateLimitService = rateLimitService; this.dataDecodingEncodingService = dataDecodingEncodingService; + this.scheduler = scheduler; } @PostConstruct @@ -169,9 +168,8 @@ public class DefaultTransportService implements TransportService { this.ruleEngineProducerStats = statsFactory.createMessagesStats(StatsType.RULE_ENGINE.getName() + ".producer"); this.tbCoreProducerStats = statsFactory.createMessagesStats(StatsType.CORE.getName() + ".producer"); this.transportApiStats = statsFactory.createMessagesStats(StatsType.TRANSPORT.getName() + ".producer"); - this.schedulerExecutor = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("transport-scheduler")); this.transportCallbackExecutor = Executors.newWorkStealingPool(20); - this.schedulerExecutor.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); + this.scheduler.scheduleAtFixedRate(this::checkInactivityAndReportActivity, new Random().nextInt((int) sessionReportTimeout), sessionReportTimeout, TimeUnit.MILLISECONDS); transportApiRequestTemplate = queueProvider.createTransportApiRequestTemplate(); transportApiRequestTemplate.setMessagesStats(transportApiStats); ruleEngineMsgProducer = producerProvider.getRuleEngineMsgProducer(); @@ -217,9 +215,6 @@ public class DefaultTransportService implements TransportService { if (transportNotificationsConsumer != null) { transportNotificationsConsumer.unsubscribe(); } - if (schedulerExecutor != null) { - schedulerExecutor.shutdownNow(); - } if (transportCallbackExecutor != null) { transportCallbackExecutor.shutdownNow(); } @@ -231,11 +226,6 @@ public class DefaultTransportService implements TransportService { } } - @Override - public ScheduledExecutorService getSchedulerExecutor() { - return this.schedulerExecutor; - } - @Override public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); @@ -488,7 +478,7 @@ public class DefaultTransportService implements TransportService { sendToRuleEngine(tenantId, tbMsg, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); toServerRpcPendingMap.put(requestId, new RpcRequestMetadata(sessionId, msg.getRequestId())); - schedulerExecutor.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); + scheduler.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); } } @@ -561,7 +551,7 @@ public class DefaultTransportService implements TransportService { SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); sessions.putIfAbsent(toSessionId(sessionInfo), currentSession); - ScheduledFuture executorFuture = schedulerExecutor.schedule(() -> { + ScheduledFuture executorFuture = scheduler.schedule(() -> { listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); deregisterSession(sessionInfo); }, timeout, TimeUnit.MILLISECONDS); From 5eb91c17f6790ed973d8c44a23d7d5a88284a8c8 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 18:25:34 +0300 Subject: [PATCH 009/102] Usage records --- .../queue/DefaultTbCoreConsumerService.java | 79 +++++++++++++++++-- .../DefaultTbUsageStatsService.java | 15 ++++ .../usagestats/TbUsageStatsService.java | 10 +++ 3 files changed, 99 insertions(+), 5 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 5d240790f2..3f03b65416 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,20 +17,21 @@ package org.thingsboard.server.service.queue; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.server.actors.ActorSystemContext; -import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; -import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.dao.util.mapping.JacksonUtil; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; @@ -61,6 +62,7 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; +import org.thingsboard.server.service.usagestats.TbUsageStatsService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -70,6 +72,8 @@ import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.function.Function; import java.util.stream.Collectors; @@ -88,32 +92,50 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> mainConsumer; private final DeviceStateService stateService; + private final TbUsageStatsService statsService; private final TbLocalSubscriptionService localSubscriptionService; private final SubscriptionManagerService subscriptionManagerService; private final TbCoreDeviceRpcService tbCoreDeviceRpcService; private final TbCoreConsumerStats stats; + protected final TbQueueConsumer> usageStatsConsumer; + + protected volatile ExecutorService usageStatsExecutor; + public DefaultTbCoreConsumerService(TbCoreQueueFactory tbCoreQueueFactory, ActorSystemContext actorContext, DeviceStateService stateService, TbLocalSubscriptionService localSubscriptionService, SubscriptionManagerService subscriptionManagerService, DataDecodingEncodingService encodingService, - TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache) { + TbCoreDeviceRpcService tbCoreDeviceRpcService, StatsFactory statsFactory, TbDeviceProfileCache deviceProfileCache, + TbUsageStatsService statsService) { super(actorContext, encodingService, deviceProfileCache, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); + this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.stateService = stateService; this.localSubscriptionService = localSubscriptionService; this.subscriptionManagerService = subscriptionManagerService; this.tbCoreDeviceRpcService = tbCoreDeviceRpcService; this.stats = new TbCoreConsumerStats(statsFactory); + this.statsService = statsService; } @PostConstruct public void init() { super.init("tb-core-consumer", "tb-core-notifications-consumer"); + this.usageStatsExecutor = Executors.newCachedThreadPool(ThingsBoardThreadFactory.forName("tb-core-usage-stats-consumer")); } @PreDestroy public void destroy() { super.destroy(); + if (usageStatsExecutor != null) { + usageStatsExecutor.shutdownNow(); + } + } + + @EventListener(ApplicationReadyEvent.class) + public void onApplicationEvent(ApplicationReadyEvent event) { + super.onApplicationEvent(event); + launchUsageStatsConsumer(); } @Override @@ -223,6 +245,53 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService { + while (!stopped) { + try { + List> msgs = usageStatsConsumer.poll(getNotificationPollDuration()); + if (msgs.isEmpty()) { + continue; + } + ConcurrentMap> pendingMap = msgs.stream().collect( + Collectors.toConcurrentMap(s -> UUID.randomUUID(), Function.identity())); + CountDownLatch processingTimeoutLatch = new CountDownLatch(1); + TbPackProcessingContext> ctx = new TbPackProcessingContext<>( + processingTimeoutLatch, pendingMap, new ConcurrentHashMap<>()); + pendingMap.forEach((id, msg) -> { + log.trace("[{}] Creating usage stats callback for message: {}", id, msg.getValue()); + TbCallback callback = new TbPackCallback<>(id, ctx); + try { + handleUsageStats(msg, callback); + } catch (Throwable e) { + log.warn("[{}] Failed to process usge stats: {}", id, msg, e); + callback.onFailure(e); + } + }); + if (!processingTimeoutLatch.await(getNotificationPackProcessingTimeout(), TimeUnit.MILLISECONDS)) { + ctx.getAckMap().forEach((id, msg) -> log.warn("[{}] Timeout to process usage stats: {}", id, msg.getValue())); + ctx.getFailedMap().forEach((id, msg) -> log.warn("[{}] Failed to process usage stats: {}", id, msg.getValue())); + } + usageStatsConsumer.commit(); + } catch (Exception e) { + if (!stopped) { + log.warn("Failed to obtain usage stats from queue.", e); + try { + Thread.sleep(getNotificationPollDuration()); + } catch (InterruptedException e2) { + log.trace("Failed to wait until the server has capacity to handle new usage stats", e2); + } + } + } + } + log.info("TB Usage Stats Consumer stopped."); + }); + } + + private void handleUsageStats(TbProtoQueueMsg msg, TbCallback callback) { + statsService.process(msg, callback); + } + private void forwardToCoreRpcService(FromDeviceRPCResponseProto proto, TbCallback callback) { RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null; FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB()) diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java new file mode 100644 index 0000000000..6611507531 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java @@ -0,0 +1,15 @@ +package org.thingsboard.server.service.usagestats; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +@Service +public class DefaultTbUsageStatsService implements TbUsageStatsService { + @Override + public void process(TbProtoQueueMsg msg, TbCallback callback) { + + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java new file mode 100644 index 0000000000..35a7051896 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java @@ -0,0 +1,10 @@ +package org.thingsboard.server.service.usagestats; + +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +public interface TbUsageStatsService { + + void process(TbProtoQueueMsg msg, TbCallback callback); +} From b598a7395492151598f8fe956d0022d5b4de33c3 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 19 Oct 2020 19:51:58 +0300 Subject: [PATCH 010/102] Renaming of UsageRecord to ApiUsageState --- .../apiusage/DefaultTbUsageStatsService.java | 30 +++++ .../service/apiusage/TbUsageStatsService.java | 25 ++++ .../queue/DefaultTbCoreConsumerService.java | 4 +- .../DefaultTbUsageStatsService.java | 15 --- .../usagestats/TbUsageStatsService.java | 10 -- ...Service.java => ApiUsageStateService.java} | 10 +- ...eRecordKey.java => ApiUsageRecordKey.java} | 2 +- .../{UsageRecord.java => ApiUsageState.java} | 11 +- .../server/common/data/EntityType.java | 2 +- ...sageRecordId.java => ApiUsageStateId.java} | 10 +- .../usagestats/DefaultTbUsageStatsClient.java | 11 +- .../queue/usagestats/TbUsageStatsClient.java | 4 +- .../server/dao/model/ModelConstants.java | 8 +- ...rdEntity.java => ApiUsageStateEntity.java} | 31 ++--- ...tory.java => ApiUsageStateRepository.java} | 15 +-- ...ecordDao.java => JpaApiUsageStateDao.java} | 30 ++--- .../server/dao/tenant/TenantServiceImpl.java | 8 +- .../ApiApiUsageStateServiceImpl.java | 91 ++++++++++++++ ...geRecordDao.java => ApiUsageStateDao.java} | 15 +-- .../usagerecord/UsageRecordServiceImpl.java | 112 ------------------ .../resources/sql/schema-entities-hsql.sql | 4 +- .../main/resources/sql/schema-entities.sql | 4 +- .../dao/service/AbstractServiceTest.java | 8 +- ...java => BaseApiUsageStateServiceTest.java} | 15 +-- ....java => ApiUsageStateServiceSqlTest.java} | 5 +- .../resources/sql/hsql/drop-all-tables.sql | 2 +- .../resources/sql/psql/drop-all-tables.sql | 2 +- .../sql/timescale/drop-all-tables.sql | 2 +- 28 files changed, 234 insertions(+), 252 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java rename common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/{UsageRecordService.java => ApiUsageStateService.java} (73%) rename common/data/src/main/java/org/thingsboard/server/common/data/{UsageRecordKey.java => ApiUsageRecordKey.java} (96%) rename common/data/src/main/java/org/thingsboard/server/common/data/{UsageRecord.java => ApiUsageState.java} (83%) rename common/data/src/main/java/org/thingsboard/server/common/data/id/{UsageRecordId.java => ApiUsageStateId.java} (77%) rename dao/src/main/java/org/thingsboard/server/dao/model/sql/{UsageRecordEntity.java => ApiUsageStateEntity.java} (67%) rename dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/{UsageRecordRepository.java => ApiUsageStateRepository.java} (64%) rename dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/{JpaUsageRecordDao.java => JpaApiUsageStateDao.java} (51%) create mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java rename dao/src/main/java/org/thingsboard/server/dao/usagerecord/{UsageRecordDao.java => ApiUsageStateDao.java} (69%) delete mode 100644 dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java rename dao/src/test/java/org/thingsboard/server/dao/service/{BaseUsageRecordServiceTest.java => BaseApiUsageStateServiceTest.java} (68%) rename dao/src/test/java/org/thingsboard/server/dao/service/sql/{UsageRecordServiceSqlTest.java => ApiUsageStateServiceSqlTest.java} (77%) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java new file mode 100644 index 0000000000..e2679d6433 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +@Service +public class DefaultTbUsageStatsService implements TbUsageStatsService { + @Override + public void process(TbProtoQueueMsg msg, TbCallback callback) { + + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java new file mode 100644 index 0000000000..3c54479eaf --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; + +public interface TbUsageStatsService { + + void process(TbProtoQueueMsg msg, TbCallback callback); +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 3f03b65416..a389eefd1a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -62,7 +62,7 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -import org.thingsboard.server.service.usagestats.TbUsageStatsService; +import org.thingsboard.server.service.apiusage.TbUsageStatsService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java deleted file mode 100644 index 6611507531..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.thingsboard.server.service.usagestats; - -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; - -@Service -public class DefaultTbUsageStatsService implements TbUsageStatsService { - @Override - public void process(TbProtoQueueMsg msg, TbCallback callback) { - - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java deleted file mode 100644 index 35a7051896..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.thingsboard.server.service.usagestats; - -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; - -public interface TbUsageStatsService { - - void process(TbProtoQueueMsg msg, TbCallback callback); -} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java similarity index 73% rename from common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java index 55adc257ad..ad6c73f63e 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java @@ -15,14 +15,14 @@ */ package org.thingsboard.server.dao.usagerecord; -import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; -public interface UsageRecordService { +public interface ApiUsageStateService { - UsageRecord findTenantUsageRecord(TenantId tenantId); + ApiUsageState findTenantApiUsageState(TenantId tenantId); - void deleteUsageRecordsByTenantId(TenantId tenantId); + void deleteApiUsageStateByTenantId(TenantId tenantId); - void createDefaultUsageRecord(TenantId id); + void createDefaultApiUsageState(TenantId id); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java similarity index 96% rename from common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index 74a54cf713..9c8690a88f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -15,7 +15,7 @@ */ package org.thingsboard.server.common.data; -public enum UsageRecordKey { +public enum ApiUsageRecordKey { MSG_COUNT, MSG_BYTES_COUNT, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java similarity index 83% rename from common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index e96cf2e452..796e434c8a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/UsageRecord.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -19,27 +19,26 @@ import lombok.EqualsAndHashCode; import lombok.ToString; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UsageRecordId; -import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.id.ApiUsageStateId; @ToString @EqualsAndHashCode(callSuper = true) -public class UsageRecord extends BaseData implements HasTenantId { +public class ApiUsageState extends BaseData implements HasTenantId { private static final long serialVersionUID = 8250339805336035966L; private TenantId tenantId; private EntityId entityId; - public UsageRecord() { + public ApiUsageState() { super(); } - public UsageRecord(UsageRecordId id) { + public ApiUsageState(ApiUsageStateId id) { super(id); } - public UsageRecord(UsageRecord ur) { + public ApiUsageState(ApiUsageState ur) { super(ur); this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java index 11bf45be76..9f7fb711e8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EntityType.java @@ -19,5 +19,5 @@ package org.thingsboard.server.common.data; * @author Andrew Shvayka */ public enum EntityType { - TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, USAGE_RECORD; + TENANT, CUSTOMER, USER, DASHBOARD, ASSET, DEVICE, ALARM, RULE_CHAIN, RULE_NODE, ENTITY_VIEW, WIDGETS_BUNDLE, WIDGET_TYPE, TENANT_PROFILE, DEVICE_PROFILE, API_USAGE_STATE; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java similarity index 77% rename from common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java rename to common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java index 76cfaaedf6..87983f519a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/UsageRecordId.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/ApiUsageStateId.java @@ -22,21 +22,21 @@ import org.thingsboard.server.common.data.EntityType; import java.util.UUID; -public class UsageRecordId extends UUIDBased implements EntityId { +public class ApiUsageStateId extends UUIDBased implements EntityId { @JsonCreator - public UsageRecordId(@JsonProperty("id") UUID id) { + public ApiUsageStateId(@JsonProperty("id") UUID id) { super(id); } - public static UsageRecordId fromString(String userId) { - return new UsageRecordId(UUID.fromString(userId)); + public static ApiUsageStateId fromString(String userId) { + return new ApiUsageStateId(UUID.fromString(userId)); } @JsonIgnore @Override public EntityType getEntityType() { - return EntityType.USAGE_RECORD; + return EntityType.API_USAGE_STATE; } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java index 87eb5e767a..534937031c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java @@ -17,13 +17,12 @@ package org.thingsboard.server.queue.usagestats; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; -import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; @@ -46,7 +45,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { @Value("${usage.stats.report.interval:600}") private int interval; - private final ConcurrentMap[] values = new ConcurrentMap[UsageRecordKey.values().length]; + private final ConcurrentMap[] values = new ConcurrentMap[ApiUsageRecordKey.values().length]; private final PartitionService partitionService; private final SchedulerComponent scheduler; private final TbQueueProducerProvider producerProvider; @@ -62,7 +61,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { private void init() { if (enabled) { msgProducer = this.producerProvider.getTbUsageStatsMsgProducer(); - for (UsageRecordKey key : UsageRecordKey.values()) { + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { values[key.ordinal()] = new ConcurrentHashMap<>(); } scheduler.scheduleWithFixedDelay(this::reportStats, new Random().nextInt(interval), interval, TimeUnit.SECONDS); @@ -72,7 +71,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { private void reportStats() { ConcurrentMap report = new ConcurrentHashMap<>(); - for (UsageRecordKey key : UsageRecordKey.values()) { + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { values[key.ordinal()].forEach(((tenantId, atomicLong) -> { long value = atomicLong.getAndSet(0); if (value > 0) { @@ -95,7 +94,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { } @Override - public void report(TenantId tenantId, UsageRecordKey key, long value) { + public void report(TenantId tenantId, ApiUsageRecordKey key, long value) { if (enabled) { ConcurrentMap map = values[key.ordinal()]; AtomicLong atomicValue = map.computeIfAbsent(tenantId, id -> new AtomicLong()); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java index e4e659b5c5..4ed850a018 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.queue.usagestats; -import org.thingsboard.server.common.data.UsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; public interface TbUsageStatsClient { - void report(TenantId tenantId, UsageRecordKey key, long value); + void report(TenantId tenantId, ApiUsageRecordKey key, long value); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 3ee942b0be..a0f5b45f02 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -441,10 +441,10 @@ public class ModelConstants { /** * Usage Record constants. */ - public static final String UR_TABLE_NAME = "usage_record"; - public static final String UR_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; - public static final String UR_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; - public static final String UR_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; + public static final String API_USAGE_STATE_TABLE_NAME = "api_usage_state"; + public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; + public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; + public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java similarity index 67% rename from dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java rename to dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index be5cc40621..a06ec142d3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/UsageRecordEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -15,29 +15,20 @@ */ package org.thingsboard.server.dao.model.sql; -import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; import lombok.EqualsAndHashCode; -import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UsageRecordId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.dao.model.BaseEntity; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; -import javax.persistence.EnumType; -import javax.persistence.Enumerated; import javax.persistence.Table; import java.util.UUID; @@ -48,22 +39,22 @@ import java.util.UUID; @EqualsAndHashCode(callSuper = true) @Entity @TypeDef(name = "json", typeClass = JsonStringType.class) -@Table(name = ModelConstants.UR_TABLE_NAME) -public class UsageRecordEntity extends BaseSqlEntity implements BaseEntity { +@Table(name = ModelConstants.API_USAGE_STATE_TABLE_NAME) +public class ApiUsageStateEntity extends BaseSqlEntity implements BaseEntity { - @Column(name = ModelConstants.UR_TENANT_ID_COLUMN) + @Column(name = ModelConstants.API_USAGE_STATE_TENANT_ID_COLUMN) private UUID tenantId; - @Column(name = ModelConstants.UR_ENTITY_TYPE_COLUMN) + @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_TYPE_COLUMN) private String entityType; - @Column(name = ModelConstants.UR_ENTITY_ID_COLUMN) + @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN) private UUID entityId; - public UsageRecordEntity() { + public ApiUsageStateEntity() { } - public UsageRecordEntity(UsageRecord ur) { + public ApiUsageStateEntity(ApiUsageState ur) { if (ur.getId() != null) { this.setUuid(ur.getId().getId()); } @@ -78,8 +69,8 @@ public class UsageRecordEntity extends BaseSqlEntity implements Bas } @Override - public UsageRecord toData() { - UsageRecord ur = new UsageRecord(new UsageRecordId(this.getUuid())); + public ApiUsageState toData() { + ApiUsageState ur = new ApiUsageState(new ApiUsageStateId(this.getUuid())); ur.setCreatedTime(createdTime); if (tenantId != null) { ur.setTenantId(new TenantId(tenantId)); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java similarity index 64% rename from dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java index dc2b0d9ad7..122bb9d966 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/UsageRecordRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/ApiUsageStateRepository.java @@ -20,24 +20,21 @@ import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.query.Param; import org.springframework.transaction.annotation.Transactional; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.model.sql.UsageRecordEntity; -import org.thingsboard.server.dao.model.sql.UserEntity; +import org.thingsboard.server.dao.model.sql.ApiUsageStateEntity; import java.util.UUID; /** * @author Valerii Sosliuk */ -public interface UsageRecordRepository extends CrudRepository { +public interface ApiUsageStateRepository extends CrudRepository { - @Query("SELECT ur FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId " + + @Query("SELECT ur FROM ApiUsageStateEntity ur WHERE ur.tenantId = :tenantId " + "AND ur.entityId = :tenantId AND ur.entityType = 'TENANT' ") - UsageRecordEntity findByTenantId(@Param("tenantId") UUID tenantId); + ApiUsageStateEntity findByTenantId(@Param("tenantId") UUID tenantId); @Transactional @Modifying - @Query("DELETE FROM UsageRecordEntity ur WHERE ur.tenantId = :tenantId") - void deleteUsageRecordsByTenantId(@Param("tenantId") UUID tenantId); + @Query("DELETE FROM ApiUsageStateEntity ur WHERE ur.tenantId = :tenantId") + void deleteApiUsageStateByTenantId(@Param("tenantId") UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java similarity index 51% rename from dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java rename to dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java index 8d54e6b53b..2c6b2e924d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaUsageRecordDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/usagerecord/JpaApiUsageStateDao.java @@ -17,12 +17,12 @@ package org.thingsboard.server.dao.sql.usagerecord; import org.springframework.data.repository.CrudRepository; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.UsageRecord; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.DaoUtil; -import org.thingsboard.server.dao.model.sql.UsageRecordEntity; +import org.thingsboard.server.dao.model.sql.ApiUsageStateEntity; import org.thingsboard.server.dao.sql.JpaAbstractDao; -import org.thingsboard.server.dao.usagerecord.UsageRecordDao; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateDao; import java.util.UUID; @@ -30,31 +30,31 @@ import java.util.UUID; * @author Andrii Shvaika */ @Component -public class JpaUsageRecordDao extends JpaAbstractDao implements UsageRecordDao { +public class JpaApiUsageStateDao extends JpaAbstractDao implements ApiUsageStateDao { - private final UsageRecordRepository usageRecordRepository; + private final ApiUsageStateRepository apiUsageStateRepository; - public JpaUsageRecordDao(UsageRecordRepository usageRecordRepository) { - this.usageRecordRepository = usageRecordRepository; + public JpaApiUsageStateDao(ApiUsageStateRepository apiUsageStateRepository) { + this.apiUsageStateRepository = apiUsageStateRepository; } @Override - protected Class getEntityClass() { - return UsageRecordEntity.class; + protected Class getEntityClass() { + return ApiUsageStateEntity.class; } @Override - protected CrudRepository getCrudRepository() { - return usageRecordRepository; + protected CrudRepository getCrudRepository() { + return apiUsageStateRepository; } @Override - public UsageRecord findTenantUsageRecord(UUID tenantId) { - return DaoUtil.getData(usageRecordRepository.findByTenantId(tenantId)); + public ApiUsageState findTenantApiUsageState(UUID tenantId) { + return DaoUtil.getData(apiUsageStateRepository.findByTenantId(tenantId)); } @Override - public void deleteUsageRecordsByTenantId(TenantId tenantId) { - usageRecordRepository.deleteUsageRecordsByTenantId(tenantId.getId()); + public void deleteApiUsageStateByTenantId(TenantId tenantId) { + apiUsageStateRepository.deleteApiUsageStateByTenantId(tenantId.getId()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java index bd9ee321e0..3a0edf5eea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantServiceImpl.java @@ -39,7 +39,7 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.usagerecord.UsageRecordService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -74,7 +74,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe private DeviceProfileService deviceProfileService; @Autowired - private UsageRecordService usageRecordService; + private ApiUsageStateService apiUsageStateService; @Autowired private EntityViewService entityViewService; @@ -121,7 +121,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe Tenant savedTenant = tenantDao.save(tenant.getId(), tenant); if (tenant.getId() == null) { deviceProfileService.createDefaultDeviceProfile(savedTenant.getId()); - usageRecordService.createDefaultUsageRecord(savedTenant.getId()); + apiUsageStateService.createDefaultApiUsageState(savedTenant.getId()); } return savedTenant; } @@ -139,7 +139,7 @@ public class TenantServiceImpl extends AbstractEntityService implements TenantSe deviceProfileService.deleteDeviceProfilesByTenantId(tenantId); userService.deleteTenantAdmins(tenantId); ruleChainService.deleteRuleChainsByTenantId(tenantId); - usageRecordService.deleteUsageRecordsByTenantId(tenantId); + apiUsageStateService.deleteApiUsageStateByTenantId(tenantId); tenantDao.removeById(tenantId, tenantId.getId()); deleteEntityRelations(tenantId, tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java new file mode 100644 index 0000000000..9e543a8f7a --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -0,0 +1,91 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.usagerecord; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.dao.entity.AbstractEntityService; +import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.service.DataValidator; +import org.thingsboard.server.dao.tenant.TenantDao; + +import static org.thingsboard.server.dao.service.Validator.validateId; + +@Service +@Slf4j +public class ApiApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; + + private final ApiUsageStateDao apiUsageStateDao; + private final TenantDao tenantDao; + + public ApiApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { + this.tenantDao = tenantDao; + this.apiUsageStateDao = apiUsageStateDao; + } + + @Override + public void deleteApiUsageStateByTenantId(TenantId tenantId) { + log.trace("Executing deleteUsageRecordsByTenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + apiUsageStateDao.deleteApiUsageStateByTenantId(tenantId); + } + + @Override + public void createDefaultApiUsageState(TenantId tenantId) { + log.trace("Executing createDefaultUsageRecord [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + ApiUsageState apiUsageState = new ApiUsageState(); + apiUsageState.setTenantId(tenantId); + apiUsageState.setEntityId(tenantId); + apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); + apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + } + + @Override + public ApiUsageState findTenantApiUsageState(TenantId tenantId) { + log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return apiUsageStateDao.findTenantApiUsageState(tenantId.getId()); + } + + private DataValidator apiUsageStateValidator = + new DataValidator() { + @Override + protected void validateDataImpl(TenantId requestTenantId, ApiUsageState apiUsageState) { + if (apiUsageState.getTenantId() == null) { + throw new DataValidationException("ApiUsageState should be assigned to tenant!"); + } else { + Tenant tenant = tenantDao.findById(requestTenantId, apiUsageState.getTenantId().getId()); + if (tenant == null) { + throw new DataValidationException("Asset is referencing to non-existent tenant!"); + } + } + if (apiUsageState.getEntityId() == null) { + throw new DataValidationException("UsageRecord should be assigned to entity!"); + } else if (!EntityType.TENANT.equals(apiUsageState.getEntityId().getEntityType())) { + throw new DataValidationException("Only Tenant Usage Records are supported!"); + } else if (!apiUsageState.getTenantId().getId().equals(apiUsageState.getEntityId().getId())) { + throw new DataValidationException("Can't assign one Usage Record to multiple tenants!"); + } + } + }; + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateDao.java similarity index 69% rename from dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java rename to dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateDao.java index 3b71e4ce20..1521da525e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateDao.java @@ -15,24 +15,21 @@ */ package org.thingsboard.server.dao.usagerecord; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import java.util.UUID; -public interface UsageRecordDao extends Dao { +public interface ApiUsageStateDao extends Dao { /** * Save or update usage record object * - * @param usageRecord the usage record + * @param apiUsageState the usage record * @return saved usage record entity */ - UsageRecord save(TenantId tenantId, UsageRecord usageRecord); + ApiUsageState save(TenantId tenantId, ApiUsageState apiUsageState); /** * Find usage record by tenantId. @@ -40,12 +37,12 @@ public interface UsageRecordDao extends Dao { * @param tenantId the tenantId * @return the corresponding usage record */ - UsageRecord findTenantUsageRecord(UUID tenantId); + ApiUsageState findTenantApiUsageState(UUID tenantId); /** * Delete usage record by tenantId. * * @param tenantId the tenantId */ - void deleteUsageRecordsByTenantId(TenantId tenantId); + void deleteApiUsageStateByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java deleted file mode 100644 index fdaa38a1a2..0000000000 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/UsageRecordServiceImpl.java +++ /dev/null @@ -1,112 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.dao.usagerecord; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.RandomStringUtils; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Service; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserCredentialsId; -import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.dao.entity.AbstractEntityService; -import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.IncorrectParameterException; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.service.DataValidator; -import org.thingsboard.server.dao.tenant.TenantDao; - -import java.util.HashMap; -import java.util.Map; - -import static org.thingsboard.server.dao.service.Validator.validateId; -import static org.thingsboard.server.dao.service.Validator.validatePageLink; -import static org.thingsboard.server.dao.service.Validator.validateString; - -@Service -@Slf4j -public class UsageRecordServiceImpl extends AbstractEntityService implements UsageRecordService { - public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; - - private final UsageRecordDao usageRecordDao; - private final TenantDao tenantDao; - - public UsageRecordServiceImpl(TenantDao tenantDao, UsageRecordDao usageRecordDao) { - this.tenantDao = tenantDao; - this.usageRecordDao = usageRecordDao; - } - - @Override - public void deleteUsageRecordsByTenantId(TenantId tenantId) { - log.trace("Executing deleteUsageRecordsByTenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - usageRecordDao.deleteUsageRecordsByTenantId(tenantId); - } - - @Override - public void createDefaultUsageRecord(TenantId tenantId) { - log.trace("Executing createDefaultUsageRecord [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - UsageRecord usageRecord = new UsageRecord(); - usageRecord.setTenantId(tenantId); - usageRecord.setEntityId(tenantId); - usageRecordValidator.validate(usageRecord, UsageRecord::getTenantId); - usageRecordDao.save(usageRecord.getTenantId(), usageRecord); - } - - @Override - public UsageRecord findTenantUsageRecord(TenantId tenantId) { - log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); - validateId(tenantId, INCORRECT_TENANT_ID + tenantId); - return usageRecordDao.findTenantUsageRecord(tenantId.getId()); - } - - private DataValidator usageRecordValidator = - new DataValidator() { - @Override - protected void validateDataImpl(TenantId requestTenantId, UsageRecord usageRecord) { - if (usageRecord.getTenantId() == null) { - throw new DataValidationException("UsageRecord should be assigned to tenant!"); - } else { - Tenant tenant = tenantDao.findById(requestTenantId, usageRecord.getTenantId().getId()); - if (tenant == null) { - throw new DataValidationException("Asset is referencing to non-existent tenant!"); - } - } - if (usageRecord.getEntityId() == null) { - throw new DataValidationException("UsageRecord should be assigned to entity!"); - } else if (!EntityType.TENANT.equals(usageRecord.getEntityId().getEntityType())) { - throw new DataValidationException("Only Tenant Usage Records are supported!"); - } else if (!usageRecord.getTenantId().getId().equals(usageRecord.getEntityId().getId())) { - throw new DataValidationException("Can't assign one Usage Record to multiple tenants!"); - } - } - }; - -} diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index e3cbbda351..c671cd2e19 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -405,11 +405,11 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); -CREATE TABLE IF NOT EXISTS usage_record ( +CREATE TABLE IF NOT EXISTS api_usage_state ( id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid, entity_type varchar(32), entity_id uuid, - CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) + CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 6cd3adb337..d8539d9af9 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -431,13 +431,13 @@ CREATE TABLE IF NOT EXISTS oauth2_client_registration_template ( CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id) ); -CREATE TABLE IF NOT EXISTS usage_record ( +CREATE TABLE IF NOT EXISTS api_usage_state ( id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY, created_time bigint NOT NULL, tenant_id uuid, entity_type varchar(32), entity_id uuid, - CONSTRAINT usage_record_unq_key UNIQUE (tenant_id, entity_id) + CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); CREATE OR REPLACE PROCEDURE cleanup_events_by_ttl(IN ttl bigint, IN debug_ttl bigint, INOUT deleted bigint) diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java index 8da1727631..2a99e868a9 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java @@ -27,7 +27,6 @@ import org.springframework.test.annotation.DirtiesContext; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.support.AnnotationConfigContextLoader; -import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileType; import org.thingsboard.server.common.data.DeviceTransportType; @@ -38,10 +37,7 @@ import org.thingsboard.server.common.data.device.profile.DefaultDeviceProfileTra import org.thingsboard.server.common.data.device.profile.DeviceProfileData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.HasId; -import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UUIDBased; -import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.audit.AuditLogLevelFilter; @@ -61,7 +57,7 @@ import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; -import org.thingsboard.server.dao.usagerecord.UsageRecordService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; @@ -87,7 +83,7 @@ public abstract class AbstractServiceTest { protected UserService userService; @Autowired - protected UsageRecordService usageRecordService; + protected ApiUsageStateService apiUsageStateService; @Autowired protected AdminSettingsService adminSettingsService; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java similarity index 68% rename from dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java index 28e0a6d54a..5f56506997 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseUsageRecordServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java @@ -19,17 +19,12 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.UsageRecord; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.service.AbstractServiceTest; -import org.thingsboard.server.dao.service.DaoSqlTest; -public abstract class BaseUsageRecordServiceTest extends AbstractServiceTest { +public abstract class BaseApiUsageStateServiceTest extends AbstractServiceTest { private TenantId tenantId; @@ -48,9 +43,9 @@ public abstract class BaseUsageRecordServiceTest extends AbstractServiceTest { } @Test - public void testFindUsageRecordByTenantId() { - UsageRecord usageRecord = usageRecordService.findTenantUsageRecord(tenantId); - Assert.assertNotNull(usageRecord); + public void testFindApiUsageStateByTenantId() { + ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); + Assert.assertNotNull(apiUsageState); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/sql/ApiUsageStateServiceSqlTest.java similarity index 77% rename from dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java rename to dao/src/test/java/org/thingsboard/server/dao/service/sql/ApiUsageStateServiceSqlTest.java index 8275b92044..bbaae0a5a9 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/sql/UsageRecordServiceSqlTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/sql/ApiUsageStateServiceSqlTest.java @@ -15,10 +15,9 @@ */ package org.thingsboard.server.dao.service.sql; -import org.thingsboard.server.dao.service.BaseUsageRecordServiceTest; -import org.thingsboard.server.dao.service.BaseUserServiceTest; +import org.thingsboard.server.dao.service.BaseApiUsageStateServiceTest; import org.thingsboard.server.dao.service.DaoSqlTest; @DaoSqlTest -public class UsageRecordServiceSqlTest extends BaseUsageRecordServiceTest { +public class ApiUsageStateServiceSqlTest extends BaseApiUsageStateServiceTest { } diff --git a/dao/src/test/resources/sql/hsql/drop-all-tables.sql b/dao/src/test/resources/sql/hsql/drop-all-tables.sql index 13c1eba943..b5629b0560 100644 --- a/dao/src/test/resources/sql/hsql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/hsql/drop-all-tables.sql @@ -27,5 +27,5 @@ DROP TABLE IF EXISTS rule_chain; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS usage_record; +DROP TABLE IF EXISTS api_usage_state; DROP FUNCTION IF EXISTS to_uuid; diff --git a/dao/src/test/resources/sql/psql/drop-all-tables.sql b/dao/src/test/resources/sql/psql/drop-all-tables.sql index b8ab69c758..34985883d7 100644 --- a/dao/src/test/resources/sql/psql/drop-all-tables.sql +++ b/dao/src/test/resources/sql/psql/drop-all-tables.sql @@ -28,4 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS usage_record; \ No newline at end of file +DROP TABLE IF EXISTS api_usage_state; \ No newline at end of file diff --git a/dao/src/test/resources/sql/timescale/drop-all-tables.sql b/dao/src/test/resources/sql/timescale/drop-all-tables.sql index 4333bc33ee..211cebb2ad 100644 --- a/dao/src/test/resources/sql/timescale/drop-all-tables.sql +++ b/dao/src/test/resources/sql/timescale/drop-all-tables.sql @@ -28,4 +28,4 @@ DROP TABLE IF EXISTS tb_schema_settings; DROP TABLE IF EXISTS oauth2_client_registration; DROP TABLE IF EXISTS oauth2_client_registration_info; DROP TABLE IF EXISTS oauth2_client_registration_template; -DROP TABLE IF EXISTS usage_record; \ No newline at end of file +DROP TABLE IF EXISTS api_usage_state; \ No newline at end of file From ef29aba24c935dc963382a5103e6c04944f6a486 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 10:11:26 +0300 Subject: [PATCH 011/102] created usage stats queue consumer and producer --- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbUsageStatsService.java | 15 ++++++ .../usagestats/TbUsageStatsService.java | 15 ++++++ .../src/main/resources/thingsboard.yml | 1 + .../provider/AwsSqsMonolithQueueFactory.java | 45 ++++++++++------ .../provider/AwsSqsTbCoreQueueFactory.java | 12 +++++ .../AwsSqsTbRuleEngineQueueFactory.java | 6 +++ .../provider/AwsSqsTransportQueueFactory.java | 6 +++ .../InMemoryMonolithQueueFactory.java | 12 +++++ .../InMemoryTbTransportQueueFactory.java | 7 +++ .../provider/KafkaMonolithQueueFactory.java | 23 ++++++++ .../provider/KafkaTbCoreQueueFactory.java | 23 ++++++++ .../KafkaTbRuleEngineQueueFactory.java | 11 ++++ .../KafkaTbTransportQueueFactory.java | 11 ++++ .../provider/PubSubMonolithQueueFactory.java | 12 +++++ .../provider/PubSubTbCoreQueueFactory.java | 12 +++++ .../PubSubTbRuleEngineQueueFactory.java | 6 +++ .../provider/PubSubTransportQueueFactory.java | 6 +++ .../RabbitMqMonolithQueueFactory.java | 52 +++++++++++++------ .../provider/RabbitMqTbCoreQueueFactory.java | 12 +++++ .../RabbitMqTbRuleEngineQueueFactory.java | 6 +++ .../RabbitMqTransportQueueFactory.java | 6 +++ .../ServiceBusMonolithQueueFactory.java | 12 +++++ .../ServiceBusTbCoreQueueFactory.java | 23 ++++++-- .../ServiceBusTbRuleEngineQueueFactory.java | 17 ++++-- .../ServiceBusTransportQueueFactory.java | 36 ++++++++----- .../queue/provider/TbCoreQueueFactory.java | 7 +-- .../TbUsageStatsClientQueueFactory.java | 5 +- .../queue/settings/TbQueueCoreSettings.java | 3 ++ 29 files changed, 337 insertions(+), 67 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 3f03b65416..e806eccf66 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java index 6611507531..bf20419670 100644 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/DefaultTbUsageStatsService.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.service.usagestats; import org.springframework.stereotype.Service; diff --git a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java index 35a7051896..e735c5e2e2 100644 --- a/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/usagestats/TbUsageStatsService.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.service.usagestats; import org.thingsboard.server.common.msg.queue.TbCallback; diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index deb28ffd99..f0f4b9dd54 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -673,6 +673,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:2000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}" diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java index c1d724c207..0299720005 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsMonolithQueueFactory.java @@ -22,7 +22,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.*; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -91,64 +91,64 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng } @Override - public TbQueueProducer> createTransportNotificationsMsgProducer() { + public TbQueueProducer> createTransportNotificationsMsgProducer() { return new TbAwsSqsProducerTemplate<>(notificationAdmin, sqsSettings, transportNotificationSettings.getNotificationsTopic()); } @Override - public TbQueueProducer> createRuleEngineMsgProducer() { + public TbQueueProducer> createRuleEngineMsgProducer() { return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbAwsSqsProducerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreMsgProducer() { + public TbQueueProducer> createTbCoreMsgProducer() { return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic()); } @Override - public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { + public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { return new TbAwsSqsConsumerTemplate<>(ruleEngineAdmin, sqsSettings, ruleEngineSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { + public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings, partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreMsgConsumer() { + public TbQueueConsumer> createToCoreMsgConsumer() { return new TbAwsSqsConsumerTemplate<>(coreAdmin, sqsSettings, coreSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { + public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { return new TbAwsSqsConsumerTemplate<>(notificationAdmin, sqsSettings, partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createTransportApiRequestConsumer() { + public TbQueueConsumer> createTransportApiRequestConsumer() { return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, transportApiSettings.getRequestsTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createTransportApiResponseProducer() { + public TbQueueProducer> createTransportApiResponseProducer() { return new TbAwsSqsProducerTemplate<>(transportApiAdmin, sqsSettings, transportApiSettings.getResponsesTopic()); } @@ -175,6 +175,17 @@ public class AwsSqsMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java index 4889a7182d..4625b9d4fc 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbCoreQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; @@ -163,6 +164,17 @@ public class AwsSqsTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbAwsSqsConsumerTemplate<>(transportApiAdmin, sqsSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java index cf43bc5fe4..851b8cc40e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTbRuleEngineQueueFactory.java @@ -143,6 +143,11 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { @@ -158,4 +163,5 @@ public class AwsSqsTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory notificationAdmin.destroy(); } } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java index 8106ee084b..103711f36b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/AwsSqsTransportQueueFactory.java @@ -18,6 +18,7 @@ package org.thingsboard.server.queue.provider; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -109,6 +110,11 @@ public class AwsSqsTransportQueueFactory implements TbTransportQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbAwsSqsProducerTemplate<>(coreAdmin, sqsSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java index 8ebda6e7b9..f7a47ecf3a 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryMonolithQueueFactory.java @@ -125,8 +125,20 @@ public class InMemoryMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return null; } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new InMemoryTbQueueConsumer<>(coreSettings.getUsageStatsTopic()); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic()); + } + @Scheduled(fixedRateString = "${queue.in_memory.stats.print-interval-ms:60000}") private void printInMemoryStats() { storage.printStats(); } + + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java index 9e8e326f0d..9ddf72534c 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/InMemoryTbTransportQueueFactory.java @@ -18,6 +18,7 @@ package org.thingsboard.server.queue.provider; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; @@ -96,4 +97,10 @@ public class InMemoryTbTransportQueueFactory implements TbTransportQueueFactory public TbQueueConsumer> createTransportNotificationsConsumer() { return new InMemoryTbQueueConsumer<>(transportNotificationSettings.getNotificationsTopic() + "." + serviceInfoProvider.getServiceId()); } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new InMemoryTbQueueProducer<>(coreSettings.getUsageStatsTopic()); + } + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java index 31ef4d3e45..538d91aa0f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaMonolithQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -249,6 +250,28 @@ public class KafkaMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEngi return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(coreSettings.getUsageStatsTopic()); + consumerBuilder.clientId("monolith-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("monolith-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(coreAdmin); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("monolith-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java index bf2ec6751e..5e1b398e91 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbCoreQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -219,6 +220,28 @@ public class KafkaTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + TbKafkaConsumerTemplate.TbKafkaConsumerTemplateBuilder> consumerBuilder = TbKafkaConsumerTemplate.builder(); + consumerBuilder.settings(kafkaSettings); + consumerBuilder.topic(coreSettings.getUsageStatsTopic()); + consumerBuilder.clientId("tb-core-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.groupId("tb-core-us-consumer-" + serviceInfoProvider.getServiceId()); + consumerBuilder.decoder(msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + consumerBuilder.admin(coreAdmin); + return consumerBuilder.build(); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-core-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java index dc44caf3f0..118f0f3316 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbRuleEngineQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -192,6 +193,16 @@ public class KafkaTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory { return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("tb-rule-engine-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java index 1cf8bca4b2..7961bb97bb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/KafkaTbTransportQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -138,6 +139,16 @@ public class KafkaTbTransportQueueFactory implements TbTransportQueueFactory { return responseBuilder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + TbKafkaProducerTemplate.TbKafkaProducerTemplateBuilder> requestBuilder = TbKafkaProducerTemplate.builder(); + requestBuilder.settings(kafkaSettings); + requestBuilder.clientId("transport-node-us-producer-" + serviceInfoProvider.getServiceId()); + requestBuilder.defaultTopic(coreSettings.getUsageStatsTopic()); + requestBuilder.admin(coreAdmin); + return requestBuilder.build(); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java index 29a60af1e2..d7c7542626 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubMonolithQueueFactory.java @@ -27,6 +27,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -183,6 +184,17 @@ public class PubSubMonolithQueueFactory implements TbCoreQueueFactory, TbRuleEng return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java index 7d85b5c3ba..71a1ceaf9b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbCoreQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -157,6 +158,17 @@ public class PubSubTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbPubSubConsumerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java index a6105fea0c..ad679039e4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTbRuleEngineQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -146,6 +147,11 @@ public class PubSubTbRuleEngineQueueFactory implements TbRuleEngineQueueFactory return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java index 72665c5163..efde055271 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/PubSubTransportQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -114,6 +115,11 @@ public class PubSubTransportQueueFactory implements TbTransportQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbPubSubProducerTemplate<>(coreAdmin, pubSubSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java index 1f812ea13c..e6eff652be 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqMonolithQueueFactory.java @@ -22,7 +22,14 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -91,64 +98,64 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE } @Override - public TbQueueProducer> createTransportNotificationsMsgProducer() { + public TbQueueProducer> createTransportNotificationsMsgProducer() { return new TbRabbitMqProducerTemplate<>(notificationAdmin, rabbitMqSettings, transportNotificationSettings.getNotificationsTopic()); } @Override - public TbQueueProducer> createRuleEngineMsgProducer() { + public TbQueueProducer> createRuleEngineMsgProducer() { return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbRabbitMqProducerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreMsgProducer() { + public TbQueueProducer> createTbCoreMsgProducer() { return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic()); } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic()); } @Override - public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { + public TbQueueConsumer> createToRuleEngineMsgConsumer(TbRuleEngineQueueConfiguration configuration) { return new TbRabbitMqConsumerTemplate<>(ruleEngineAdmin, rabbitMqSettings, ruleEngineSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { + public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { return new TbRabbitMqConsumerTemplate<>(notificationAdmin, rabbitMqSettings, partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreMsgConsumer() { + public TbQueueConsumer> createToCoreMsgConsumer() { return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { + public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { return new TbRabbitMqConsumerTemplate<>(notificationAdmin, rabbitMqSettings, partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueConsumer> createTransportApiRequestConsumer() { + public TbQueueConsumer> createTransportApiRequestConsumer() { return new TbRabbitMqConsumerTemplate<>(transportApiAdmin, rabbitMqSettings, transportApiSettings.getRequestsTopic(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiRequestMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override - public TbQueueProducer> createTransportApiResponseProducer() { + public TbQueueProducer> createTransportApiResponseProducer() { return new TbRabbitMqProducerTemplate<>(transportApiAdmin, rabbitMqSettings, transportApiSettings.getResponsesTopic()); } @@ -175,6 +182,17 @@ public class RabbitMqMonolithQueueFactory implements TbCoreQueueFactory, TbRuleE return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java index 65d582c4bc..e3064dd62d 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbCoreQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -163,6 +164,17 @@ public class RabbitMqTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbRabbitMqConsumerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java index 26abf78164..a1097049db 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTbRuleEngineQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -144,6 +145,11 @@ public class RabbitMqTbRuleEngineQueueFactory implements TbRuleEngineQueueFactor return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java index 03cd6eb557..b28a2cb7e2 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/RabbitMqTransportQueueFactory.java @@ -21,6 +21,7 @@ import org.springframework.stereotype.Component; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -111,6 +112,11 @@ public class RabbitMqTransportQueueFactory implements TbTransportQueueFactory { msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbRabbitMqProducerTemplate<>(coreAdmin, rabbitMqSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java index 3db6496b7d..a205f9b061 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusMonolithQueueFactory.java @@ -26,6 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMs import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -180,6 +181,17 @@ public class ServiceBusMonolithQueueFactory implements TbCoreQueueFactory, TbRul return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java index a3ba577f06..f00aee331b 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbCoreQueueFactory.java @@ -21,10 +21,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; @@ -101,7 +103,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic()); } @@ -111,7 +113,7 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic()); } @@ -122,10 +124,10 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { } @Override - public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { + public TbQueueConsumer> createToCoreNotificationsMsgConsumer() { return new TbServiceBusConsumerTemplate<>(notificationAdmin, serviceBusSettings, partitionService.getNotificationsTopic(ServiceType.TB_CORE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToCoreNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override @@ -162,6 +164,17 @@ public class ServiceBusTbCoreQueueFactory implements TbCoreQueueFactory { return builder.build(); } + @Override + public TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { + return new TbServiceBusConsumerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic(), + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToUsageStatsServiceMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java index e4f6dbfbb4..40a183a254 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTbRuleEngineQueueFactory.java @@ -21,10 +21,12 @@ import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; @@ -93,7 +95,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact } @Override - public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> createRuleEngineNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(ruleEngineAdmin, serviceBusSettings, ruleEngineSettings.getTopic()); } @@ -103,7 +105,7 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact } @Override - public TbQueueProducer> createTbCoreNotificationsMsgProducer() { + public TbQueueProducer> createTbCoreNotificationsMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic()); } @@ -114,10 +116,10 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact } @Override - public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { + public TbQueueConsumer> createToRuleEngineNotificationsMsgConsumer() { return new TbServiceBusConsumerTemplate<>(notificationAdmin, serviceBusSettings, partitionService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, serviceInfoProvider.getServiceId()).getFullTopicName(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToRuleEngineNotificationMsg.parseFrom(msg.getData()), msg.getHeaders())); } @Override @@ -143,6 +145,11 @@ public class ServiceBusTbRuleEngineQueueFactory implements TbRuleEngineQueueFact return builder.build(); } + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); + } + @PreDestroy private void destroy() { if (coreAdmin != null) { diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java index 1e900f37e5..4781d46583 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/ServiceBusTransportQueueFactory.java @@ -18,19 +18,24 @@ package org.thingsboard.server.queue.provider; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Component; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.azure.servicebus.TbServiceBusAdmin; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusConsumerTemplate; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusProducerTemplate; import org.thingsboard.server.queue.azure.servicebus.TbServiceBusQueueConfigs; +import org.thingsboard.server.queue.azure.servicebus.TbServiceBusSettings; import org.thingsboard.server.queue.common.DefaultTbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; -import org.thingsboard.server.queue.azure.servicebus.TbServiceBusConsumerTemplate; -import org.thingsboard.server.queue.azure.servicebus.TbServiceBusProducerTemplate; -import org.thingsboard.server.queue.azure.servicebus.TbServiceBusSettings; import org.thingsboard.server.queue.settings.TbQueueCoreSettings; import org.thingsboard.server.queue.settings.TbQueueTransportApiSettings; import org.thingsboard.server.queue.settings.TbQueueTransportNotificationSettings; @@ -69,17 +74,17 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory } @Override - public TbQueueRequestTemplate, TbProtoQueueMsg> createTransportApiRequestTemplate() { - TbQueueProducer> producerTemplate = + public TbQueueRequestTemplate, TbProtoQueueMsg> createTransportApiRequestTemplate() { + TbQueueProducer> producerTemplate = new TbServiceBusProducerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getRequestsTopic()); - TbQueueConsumer> consumerTemplate = + TbQueueConsumer> consumerTemplate = new TbServiceBusConsumerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getResponsesTopic() + "." + serviceInfoProvider.getServiceId(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportApiResponseMsg.parseFrom(msg.getData()), msg.getHeaders())); DefaultTbQueueRequestTemplate.DefaultTbQueueRequestTemplateBuilder - , TbProtoQueueMsg> templateBuilder = DefaultTbQueueRequestTemplate.builder(); + , TbProtoQueueMsg> templateBuilder = DefaultTbQueueRequestTemplate.builder(); templateBuilder.queueAdmin(transportApiAdmin); templateBuilder.requestTemplate(producerTemplate); templateBuilder.responseTemplate(consumerTemplate); @@ -90,20 +95,25 @@ public class ServiceBusTransportQueueFactory implements TbTransportQueueFactory } @Override - public TbQueueProducer> createRuleEngineMsgProducer() { + public TbQueueProducer> createRuleEngineMsgProducer() { return new TbServiceBusProducerTemplate<>(transportApiAdmin, serviceBusSettings, transportApiSettings.getRequestsTopic()); } @Override - public TbQueueProducer> createTbCoreMsgProducer() { + public TbQueueProducer> createTbCoreMsgProducer() { return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getTopic()); } @Override - public TbQueueConsumer> createTransportNotificationsConsumer() { + public TbQueueConsumer> createTransportNotificationsConsumer() { return new TbServiceBusConsumerTemplate<>(notificationAdmin, serviceBusSettings, transportNotificationSettings.getNotificationsTopic() + "." + serviceInfoProvider.getServiceId(), - msg -> new TbProtoQueueMsg<>(msg.getKey(), TransportProtos.ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); + msg -> new TbProtoQueueMsg<>(msg.getKey(), ToTransportMsg.parseFrom(msg.getData()), msg.getHeaders())); + } + + @Override + public TbQueueProducer> createToUsageStatsServiceMsgProducer() { + return new TbServiceBusProducerTemplate<>(coreAdmin, serviceBusSettings, coreSettings.getUsageStatsTopic()); } @PreDestroy diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java index d009757997..7c19a1ba2f 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueFactory.java @@ -16,12 +16,12 @@ package org.thingsboard.server.queue.provider; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.queue.TbQueueConsumer; @@ -83,10 +83,7 @@ public interface TbCoreQueueFactory extends TbUsageStatsClientQueueFactory { * * @return */ - default TbQueueConsumer> createToUsageStatsServiceMsgConsumer() { - //TODO: implement - return null; - } + TbQueueConsumer> createToUsageStatsServiceMsgConsumer(); /** * Used to consume high priority messages by TB Core Service diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java index 1811877338..f2ce1a9fe5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbUsageStatsClientQueueFactory.java @@ -21,9 +21,6 @@ import org.thingsboard.server.queue.common.TbProtoQueueMsg; public interface TbUsageStatsClientQueueFactory { - default TbQueueProducer> createToUsageStatsServiceMsgProducer() { - //TODO: implement - return null; - } + TbQueueProducer> createToUsageStatsServiceMsgProducer(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java index 1c260c754d..d406139dbb 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java @@ -26,6 +26,9 @@ public class TbQueueCoreSettings { @Value("${queue.core.topic}") private String topic; + @Value("${queue.core.usage-stats-topic}") + private String usageStatsTopic; + @Value("${queue.core.partitions}") private int partitions; } From cc2446442d7423a7038cb3a95e239e0a4e7c276d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 20 Oct 2020 12:00:35 +0300 Subject: [PATCH 012/102] Api Usage Stats flow --- .../DefaultTbApiUsageStateService.java | 105 ++++++++++++++++++ .../apiusage/DefaultTbUsageStatsService.java | 30 ----- ...rvice.java => TbApiUsageStateService.java} | 2 +- .../service/apiusage/TenantApiUsageState.java | 47 ++++++++ .../queue/DefaultTbCoreConsumerService.java | 6 +- .../usagestats/DefaultTbUsageStatsClient.java | 47 ++++---- .../service/DefaultTransportService.java | 36 +++++- 7 files changed, 217 insertions(+), 56 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java delete mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java rename application/src/main/java/org/thingsboard/server/service/apiusage/{TbUsageStatsService.java => TbApiUsageStateService.java} (95%) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java new file mode 100644 index 0000000000..8a50c9d62b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -0,0 +1,105 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import lombok.extern.slf4j.Slf4j; +import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.util.TbCoreComponent; + +import java.time.LocalDate; +import java.time.ZoneId; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ExecutionException; + +@Slf4j +@TbCoreComponent +@Service +public class DefaultTbApiUsageStateService implements TbApiUsageStateService { + + private final ApiUsageStateService apiUsageStateService; + private final TimeseriesService tsService; + private final ZoneId zoneId; + private final Map tenantStates = new ConcurrentHashMap<>(); + + public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService) { + this.apiUsageStateService = apiUsageStateService; + this.tsService = tsService; + this.zoneId = SchedulerUtils.getZoneId("UTC"); + } + + @Override + public void process(TbProtoQueueMsg msg, TbCallback callback) { + ToUsageStatsServiceMsg statsMsg = msg.getValue(); + TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); + TenantApiUsageState tenantState = getOrFetchState(tenantId); + long ts = tenantState.getCurrentMonthTs(); + List updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); + for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { + ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); + long newValue = tenantState.add(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + } + tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); + } + + private TenantApiUsageState getOrFetchState(TenantId tenantId) { + TenantApiUsageState tenantState = tenantStates.get(tenantId); + if (tenantState == null) { + long currentMonthTs = LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); + ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); + tenantState = new TenantApiUsageState(currentMonthTs, dbStateEntity.getEntityId()); + try { + List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + TsKvEntry keyEntry = null; + for (TsKvEntry tsKvEntry : dbValues) { + if (tsKvEntry.getKey().equals(key.name())) { + keyEntry = tsKvEntry; + break; + } + } + if (keyEntry != null) { + tenantState.put(key, keyEntry.getLongValue().get()); + } else { + tenantState.put(key, 0L); + } + } + tenantStates.put(tenantId, tenantState); + } catch (InterruptedException | ExecutionException e) { + log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); + } + } + return tenantState; + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java deleted file mode 100644 index e2679d6433..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbUsageStatsService.java +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.apiusage; - -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; - -@Service -public class DefaultTbUsageStatsService implements TbUsageStatsService { - @Override - public void process(TbProtoQueueMsg msg, TbCallback callback) { - - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java rename to application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 3c54479eaf..b5aeedeb06 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbUsageStatsService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -19,7 +19,7 @@ import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -public interface TbUsageStatsService { +public interface TbApiUsageStateService { void process(TbProtoQueueMsg msg, TbCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java new file mode 100644 index 0000000000..a8be19914b --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.apiusage; + +import lombok.Getter; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.id.EntityId; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +public class TenantApiUsageState { + + private final Map values = new ConcurrentHashMap<>(); + @Getter + private final EntityId entityId; + @Getter + private volatile long currentMonthTs; + + public TenantApiUsageState(long currentMonthTs, EntityId entityId) { + this.entityId = entityId; + this.currentMonthTs = currentMonthTs; + } + + public void put(ApiUsageRecordKey key, Long value) { + values.put(key, value); + } + + public long add(ApiUsageRecordKey key, long value) { + long result = values.getOrDefault(key, 0L) + value; + values.put(key, result); + return result; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index a389eefd1a..51fce853db 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -62,7 +62,7 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -import org.thingsboard.server.service.apiusage.TbUsageStatsService; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -92,7 +92,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> mainConsumer; private final DeviceStateService stateService; - private final TbUsageStatsService statsService; + private final TbApiUsageStateService statsService; private final TbLocalSubscriptionService localSubscriptionService; private final SubscriptionManagerService subscriptionManagerService; private final TbCoreDeviceRpcService tbCoreDeviceRpcService; @@ -106,7 +106,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService[] values = new ConcurrentMap[ApiUsageRecordKey.values().length]; @@ -69,28 +71,33 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { } private void reportStats() { - ConcurrentMap report = new ConcurrentHashMap<>(); + try { + ConcurrentMap report = new ConcurrentHashMap<>(); - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - values[key.ordinal()].forEach(((tenantId, atomicLong) -> { - long value = atomicLong.getAndSet(0); - if (value > 0) { - ToUsageStatsServiceMsg.Builder msgBuilder = report.computeIfAbsent(tenantId, id -> { - ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); - msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); - msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); - return msg; - }); - msgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); - } + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + values[key.ordinal()].forEach(((tenantId, atomicLong) -> { + long value = atomicLong.getAndSet(0); + if (value > 0) { + ToUsageStatsServiceMsg.Builder msgBuilder = report.computeIfAbsent(tenantId, id -> { + ToUsageStatsServiceMsg.Builder msg = ToUsageStatsServiceMsg.newBuilder(); + msg.setTenantIdMSB(tenantId.getId().getMostSignificantBits()); + msg.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); + return msg; + }); + msgBuilder.addValues(UsageStatsKVProto.newBuilder().setKey(key.name()).setValue(value).build()); + } + })); + } + + report.forEach(((tenantId, builder) -> { + //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); + msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); })); + log.info("Report statistics for: {} tenants", report.size()); + } catch (Exception e) { + log.warn("Failed to report statistics: ", e); } - - report.forEach(((tenantId, builder) -> { - //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); - msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); - })); } @Override diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 774a437e39..010a3cd899 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; @@ -78,6 +79,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -122,6 +124,7 @@ public class DefaultTransportService implements TransportService { private final StatsFactory statsFactory; private final TransportDeviceProfileCache deviceProfileCache; private final TransportTenantProfileCache tenantProfileCache; + private final TbUsageStatsClient apiUsageStatsClient; private final TransportRateLimitService rateLimitService; private final DataDecodingEncodingService dataDecodingEncodingService; private final SchedulerComponent scheduler; @@ -150,7 +153,8 @@ public class DefaultTransportService implements TransportService { StatsFactory statsFactory, TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, - TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { + TbUsageStatsClient apiUsageStatsClient, TransportRateLimitService rateLimitService, + DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; this.producerProvider = producerProvider; @@ -158,6 +162,7 @@ public class DefaultTransportService implements TransportService { this.statsFactory = statsFactory; this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; + this.apiUsageStatsClient = apiUsageStatsClient; this.rateLimitService = rateLimitService; this.dataDecodingEncodingService = dataDecodingEncodingService; this.scheduler = scheduler; @@ -362,7 +367,7 @@ public class DefaultTransportService implements TransportService { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), callback); + MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), new ApiStatsProxyCallback<>(tenantId, dataPoints, callback)); for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); @@ -806,4 +811,31 @@ public class DefaultTransportService implements TransportService { callback.onError(t); } } + + private class ApiStatsProxyCallback implements TransportServiceCallback { + private final TenantId tenantId; + private final int dataPoints; + private final TransportServiceCallback callback; + + public ApiStatsProxyCallback(TenantId tenantId, int dataPoints, TransportServiceCallback callback) { + this.tenantId = tenantId; + this.dataPoints = dataPoints; + this.callback = callback; + } + + @Override + public void onSuccess(T msg) { + try { + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.MSG_COUNT, 1); + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.DP_TRANSPORT_COUNT, dataPoints); + } finally { + callback.onSuccess(msg); + } + } + + @Override + public void onError(Throwable e) { + callback.onError(e); + } + } } From 70fe28a5ac172bbc7aa093fdee04e8151118485e Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 13:59:14 +0300 Subject: [PATCH 013/102] added usage stats consumer and producer to queue providers --- .../queue/DefaultTbCoreConsumerService.java | 20 +++++++---- .../common/msg/queue/TopicPartitionInfo.java | 4 +++ .../provider/TbCoreQueueProducerProvider.java | 34 ++++++++++++------ .../provider/TbQueueProducerProvider.java | 8 ++--- .../TbRuleEngineProducerProvider.java | 35 ++++++++++++------- .../TbTransportQueueProducerProvider.java | 25 +++++++------ .../usagestats/DefaultTbUsageStatsClient.java | 2 +- 7 files changed, 81 insertions(+), 47 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 51fce853db..929d5000f0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -30,28 +30,29 @@ import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.dao.util.mapping.JacksonUtil; -import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.DeviceStateServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.FromDeviceRPCResponseProto; import org.thingsboard.server.gen.transport.TransportProtos.LocalSubscriptionServiceMsgProto; import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionMgrMsgProto; -import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; -import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto; -import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmDeleteProto; +import org.thingsboard.server.gen.transport.TransportProtos.TbAlarmUpdateProto; +import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeDeleteProto; +import org.thingsboard.server.gen.transport.TransportProtos.TbAttributeUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.TbSubscriptionCloseProto; import org.thingsboard.server.gen.transport.TransportProtos.TbTimeSeriesUpdateProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.provider.TbCoreQueueFactory; -import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; @@ -62,7 +63,6 @@ import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; -import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -143,6 +143,12 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService tpi.newByTopic(usageStatsConsumer.getTopic())) + .collect(Collectors.toSet())); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java index bf0fffe404..c2dec02612 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TopicPartitionInfo.java @@ -50,6 +50,10 @@ public class TopicPartitionInfo { this.fullTopicName = tmp; } + public TopicPartitionInfo newByTopic(String topic) { + return new TopicPartitionInfo(topic, this.tenantId, this.partition, this.myPartition); + } + public String getTopic() { return topic; } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java index 9fe5de0bcc..e620259960 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbCoreQueueProducerProvider.java @@ -17,7 +17,12 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -28,11 +33,12 @@ import javax.annotation.PostConstruct; public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { private final TbCoreQueueFactory tbQueueProvider; - private TbQueueProducer> toTransport; - private TbQueueProducer> toRuleEngine; - private TbQueueProducer> toTbCore; - private TbQueueProducer> toRuleEngineNotifications; - private TbQueueProducer> toTbCoreNotifications; + private TbQueueProducer> toTransport; + private TbQueueProducer> toRuleEngine; + private TbQueueProducer> toTbCore; + private TbQueueProducer> toRuleEngineNotifications; + private TbQueueProducer> toTbCoreNotifications; + private TbQueueProducer> toUsageStats; public TbCoreQueueProducerProvider(TbCoreQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -45,30 +51,36 @@ public class TbCoreQueueProducerProvider implements TbQueueProducerProvider { this.toRuleEngine = tbQueueProvider.createRuleEngineMsgProducer(); this.toRuleEngineNotifications = tbQueueProvider.createRuleEngineNotificationsMsgProducer(); this.toTbCoreNotifications = tbQueueProvider.createTbCoreNotificationsMsgProducer(); + this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); } @Override - public TbQueueProducer> getTransportNotificationsMsgProducer() { + public TbQueueProducer> getTransportNotificationsMsgProducer() { return toTransport; } @Override - public TbQueueProducer> getRuleEngineMsgProducer() { + public TbQueueProducer> getRuleEngineMsgProducer() { return toRuleEngine; } @Override - public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { return toRuleEngineNotifications; } @Override - public TbQueueProducer> getTbCoreMsgProducer() { + public TbQueueProducer> getTbCoreMsgProducer() { return toTbCore; } @Override - public TbQueueProducer> getTbCoreNotificationsMsgProducer() { + public TbQueueProducer> getTbCoreNotificationsMsgProducer() { return toTbCoreNotifications; } + + @Override + public TbQueueProducer> getTbUsageStatsMsgProducer() { + return toUsageStats; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java index fa20f11960..8c3a0b6565 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbQueueProducerProvider.java @@ -15,12 +15,12 @@ */ package org.thingsboard.server.queue.provider; -import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -69,9 +69,5 @@ public interface TbQueueProducerProvider { * * @return */ - default TbQueueProducer> getTbUsageStatsMsgProducer() { - //TODO: implement - return null; - } - + TbQueueProducer> getTbUsageStatsMsgProducer(); } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java index 69de7ca3e2..309def1269 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbRuleEngineProducerProvider.java @@ -17,7 +17,12 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -28,12 +33,12 @@ import javax.annotation.PostConstruct; public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { private final TbRuleEngineQueueFactory tbQueueProvider; - private TbQueueProducer> toTransport; - private TbQueueProducer> toRuleEngine; - private TbQueueProducer> toTbCore; - private TbQueueProducer> toRuleEngineNotifications; - private TbQueueProducer> toTbCoreNotifications; - + private TbQueueProducer> toTransport; + private TbQueueProducer> toRuleEngine; + private TbQueueProducer> toTbCore; + private TbQueueProducer> toRuleEngineNotifications; + private TbQueueProducer> toTbCoreNotifications; + private TbQueueProducer> toUsageStats; public TbRuleEngineProducerProvider(TbRuleEngineQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -46,30 +51,36 @@ public class TbRuleEngineProducerProvider implements TbQueueProducerProvider { this.toRuleEngine = tbQueueProvider.createRuleEngineMsgProducer(); this.toRuleEngineNotifications = tbQueueProvider.createRuleEngineNotificationsMsgProducer(); this.toTbCoreNotifications = tbQueueProvider.createTbCoreNotificationsMsgProducer(); + this.toUsageStats = tbQueueProvider.createToUsageStatsServiceMsgProducer(); } @Override - public TbQueueProducer> getTransportNotificationsMsgProducer() { + public TbQueueProducer> getTransportNotificationsMsgProducer() { return toTransport; } @Override - public TbQueueProducer> getRuleEngineMsgProducer() { + public TbQueueProducer> getRuleEngineMsgProducer() { return toRuleEngine; } @Override - public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { return toRuleEngineNotifications; } @Override - public TbQueueProducer> getTbCoreMsgProducer() { + public TbQueueProducer> getTbCoreMsgProducer() { return toTbCore; } @Override - public TbQueueProducer> getTbCoreNotificationsMsgProducer() { + public TbQueueProducer> getTbCoreNotificationsMsgProducer() { return toTbCoreNotifications; } + + @Override + public TbQueueProducer> getTbUsageStatsMsgProducer() { + return toUsageStats; + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java index 9d11aa727b..dee6508ef4 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/provider/TbTransportQueueProducerProvider.java @@ -17,7 +17,12 @@ package org.thingsboard.server.queue.provider; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToCoreNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.TbQueueProducer; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -28,9 +33,9 @@ import javax.annotation.PostConstruct; public class TbTransportQueueProducerProvider implements TbQueueProducerProvider { private final TbTransportQueueFactory tbQueueProvider; - private TbQueueProducer> toRuleEngine; - private TbQueueProducer> toTbCore; - private TbQueueProducer> toUsageStats; + private TbQueueProducer> toRuleEngine; + private TbQueueProducer> toTbCore; + private TbQueueProducer> toUsageStats; public TbTransportQueueProducerProvider(TbTransportQueueFactory tbQueueProvider) { this.tbQueueProvider = tbQueueProvider; @@ -44,32 +49,32 @@ public class TbTransportQueueProducerProvider implements TbQueueProducerProvider } @Override - public TbQueueProducer> getTransportNotificationsMsgProducer() { + public TbQueueProducer> getTransportNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } @Override - public TbQueueProducer> getRuleEngineMsgProducer() { + public TbQueueProducer> getRuleEngineMsgProducer() { return toRuleEngine; } @Override - public TbQueueProducer> getTbCoreMsgProducer() { + public TbQueueProducer> getTbCoreMsgProducer() { return toTbCore; } @Override - public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { + public TbQueueProducer> getRuleEngineNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } @Override - public TbQueueProducer> getTbCoreNotificationsMsgProducer() { + public TbQueueProducer> getTbCoreNotificationsMsgProducer() { throw new RuntimeException("Not Implemented! Should not be used by Transport!"); } @Override - public TbQueueProducer> getTbUsageStatsMsgProducer() { + public TbQueueProducer> getTbUsageStatsMsgProducer() { return toUsageStats; } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java index 35bb0c2a6a..5a94cd505e 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java @@ -91,7 +91,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { report.forEach(((tenantId, builder) -> { //TODO: figure out how to minimize messages into the queue. Maybe group by 100s of messages? - TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId); + TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).newByTopic(msgProducer.getDefaultTopic()); msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); })); log.info("Report statistics for: {} tenants", report.size()); From 9ec4b776728a8da85bc552a5eefb448133ae83bc Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 20 Oct 2020 15:50:04 +0300 Subject: [PATCH 014/102] Improvements to ApiUsageState persistence --- .../DefaultTbApiUsageStateService.java | 119 ++++++++++++++---- .../apiusage/TbApiUsageStateService.java | 12 +- .../service/apiusage/TenantApiUsageState.java | 49 ++++++-- .../dao/usagerecord/ApiUsageStateService.java | 2 +- .../data/plugin/ComponentLifecycleEvent.java | 2 +- .../common/msg/tools/SchedulerUtils.java | 48 +++++++ .../ApiApiUsageStateServiceImpl.java | 4 +- 7 files changed, 202 insertions(+), 34 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 8a50c9d62b..1b6cdf3cff 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -16,9 +16,11 @@ package org.thingsboard.server.service.apiusage; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; @@ -30,69 +32,144 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.service.profile.TbTenantProfileCache; -import java.time.LocalDate; -import java.time.ZoneId; +import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; @Slf4j @TbCoreComponent @Service public class DefaultTbApiUsageStateService implements TbApiUsageStateService { + public static final String HOURLY = "HOURLY_"; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; - private final ZoneId zoneId; + private final SchedulerComponent scheduler; + private final TbTenantProfileCache tenantProfileCache; private final Map tenantStates = new ConcurrentHashMap<>(); - public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService) { + @Value("${usage.stats.report.enabled:true}") + private boolean enabled; + + @Value("${usage.stats.check.cycle:60000}") + private long nextCycleCheckInterval; + + private final Lock updateLock = new ReentrantLock(); + + public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; - this.zoneId = SchedulerUtils.getZoneId("UTC"); + this.scheduler = scheduler; + this.tenantProfileCache = tenantProfileCache; + } + + @PostConstruct + public void init() { + if (enabled) { + scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); + } } @Override public void process(TbProtoQueueMsg msg, TbCallback callback) { ToUsageStatsServiceMsg statsMsg = msg.getValue(); TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); - TenantApiUsageState tenantState = getOrFetchState(tenantId); - long ts = tenantState.getCurrentMonthTs(); - List updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); - for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { - ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); - long newValue = tenantState.add(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + TenantApiUsageState tenantState; + List updatedEntries; + updateLock.lock(); + try { + tenantState = getOrFetchState(tenantId); + long ts = tenantState.getCurrentCycleTs(); + long hourTs = tenantState.getCurrentHourTs(); + long newHourTs = SchedulerUtils.getStartOfCurrentHour(); + if (newHourTs != hourTs) { + tenantState.setHour(newHourTs); + } + updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); + for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { + ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); + long newValue = tenantState.add(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + newValue = tenantState.addToHourly(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newValue))); + } + } finally { + updateLock.unlock(); } tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); } + @Override + public TenantApiUsageState getApiUsageState(TenantId tenantId) { + return null; + } + + @Override + public void onAddedToAllowList(TenantId tenantId) { + + } + + @Override + public void onAddedToDenyList(TenantId tenantId) { + + } + + private void checkStartOfNextCycle() { + updateLock.lock(); + try { + long now = System.currentTimeMillis(); + tenantStates.values().forEach(state -> { + if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { + state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); + } + }); + } finally { + updateLock.unlock(); + } + } + private TenantApiUsageState getOrFetchState(TenantId tenantId) { TenantApiUsageState tenantState = tenantStates.get(tenantId); if (tenantState == null) { - long currentMonthTs = LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); - tenantState = new TenantApiUsageState(currentMonthTs, dbStateEntity.getEntityId()); + if (dbStateEntity == null) { + try { + dbStateEntity = apiUsageStateService.createDefaultApiUsageState(tenantId); + } catch (Exception e) { + dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); + } + } + tenantState = new TenantApiUsageState(dbStateEntity.getEntityId()); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - TsKvEntry keyEntry = null; + boolean cycleEntryFound = false; + boolean hourlyEntryFound = false; for (TsKvEntry tsKvEntry : dbValues) { if (tsKvEntry.getKey().equals(key.name())) { - keyEntry = tsKvEntry; + cycleEntryFound = true; + tenantState.put(key, tsKvEntry.getLongValue().get()); + } else if (tsKvEntry.getKey().equals(HOURLY + key.name())) { + hourlyEntryFound = true; + if (tsKvEntry.getTs() == tenantState.getCurrentHourTs()) { + tenantState.putHourly(key, tsKvEntry.getLongValue().get()); + } + } + if (cycleEntryFound && hourlyEntryFound) { break; } } - if (keyEntry != null) { - tenantState.put(key, keyEntry.getLongValue().get()); - } else { - tenantState.put(key, 0L); - } } tenantStates.put(tenantId, tenantState); } catch (InterruptedException | ExecutionException e) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index b5aeedeb06..e79b79d069 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -15,11 +15,19 @@ */ package org.thingsboard.server.service.apiusage; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.TbCallback; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; public interface TbApiUsageStateService { - void process(TbProtoQueueMsg msg, TbCallback callback); + void process(TbProtoQueueMsg msg, TbCallback callback); + + TenantApiUsageState getApiUsageState(TenantId tenantId); + + void onAddedToAllowList(TenantId tenantId); + + void onAddedToDenyList(TenantId tenantId); + } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index a8be19914b..29335c3326 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -18,30 +18,65 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.msg.tools.SchedulerUtils; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; public class TenantApiUsageState { - private final Map values = new ConcurrentHashMap<>(); + private final Map currentCycleValues = new ConcurrentHashMap<>(); + private final Map currentHourValues = new ConcurrentHashMap<>(); + @Getter private final EntityId entityId; @Getter - private volatile long currentMonthTs; + private volatile long currentCycleTs; + @Getter + private volatile long nextCycleTs; + @Getter + private volatile long currentHourTs; - public TenantApiUsageState(long currentMonthTs, EntityId entityId) { + public TenantApiUsageState(EntityId entityId) { this.entityId = entityId; - this.currentMonthTs = currentMonthTs; + this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); + this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); + this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); } public void put(ApiUsageRecordKey key, Long value) { - values.put(key, value); + currentCycleValues.put(key, value); + } + + public void putHourly(ApiUsageRecordKey key, Long value) { + currentHourValues.put(key, value); } public long add(ApiUsageRecordKey key, long value) { - long result = values.getOrDefault(key, 0L) + value; - values.put(key, result); + long result = currentCycleValues.getOrDefault(key, 0L) + value; + currentCycleValues.put(key, result); return result; } + + public long addToHourly(ApiUsageRecordKey key, long value) { + long result = currentHourValues.getOrDefault(key, 0L) + value; + currentHourValues.put(key, result); + return result; + } + + public void setHour(long currentHourTs) { + this.currentHourTs = currentHourTs; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + currentHourValues.put(key, 0L); + } + } + + public void setCycles(long currentCycleTs, long nextCycleTs) { + this.currentCycleTs = currentCycleTs; + this.nextCycleTs = nextCycleTs; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + currentCycleValues.put(key, 0L); + } + } + } diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java index ad6c73f63e..c39e2a69dc 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java @@ -24,5 +24,5 @@ public interface ApiUsageStateService { void deleteApiUsageStateByTenantId(TenantId tenantId); - void createDefaultApiUsageState(TenantId id); + ApiUsageState createDefaultApiUsageState(TenantId id); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java index 87adc31713..1f242a0f88 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/plugin/ComponentLifecycleEvent.java @@ -21,5 +21,5 @@ import java.io.Serializable; * @author Andrew Shvayka */ public enum ComponentLifecycleEvent implements Serializable { - CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED + CREATED, STARTED, ACTIVATED, SUSPENDED, UPDATED, STOPPED, DELETED, ADDED_TO_ALLOW_LIST, ADDED_TO_DENY_LIST } \ No newline at end of file diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java index fea6fcb337..421066b30b 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/tools/SchedulerUtils.java @@ -15,16 +15,64 @@ */ package org.thingsboard.server.common.msg.tools; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAdjuster; +import java.time.temporal.TemporalAdjusters; +import java.time.temporal.TemporalUnit; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import static java.time.temporal.ChronoField.DAY_OF_MONTH; +import static java.time.temporal.ChronoUnit.MONTHS; + public class SchedulerUtils { + private final static ZoneId UTC = ZoneId.of("UTC"); private static final ConcurrentMap tzMap = new ConcurrentHashMap<>(); public static ZoneId getZoneId(String tz) { return tzMap.computeIfAbsent(tz == null || tz.isEmpty() ? "UTC" : tz, ZoneId::of); } + public static long getStartOfCurrentHour() { + return getStartOfCurrentHour(UTC); + } + + public static long getStartOfCurrentHour(ZoneId zoneId) { + return LocalDateTime.now(ZoneOffset.UTC).atZone(zoneId).truncatedTo(ChronoUnit.HOURS).toInstant().toEpochMilli(); + } + + public static long getStartOfCurrentMonth() { + return getStartOfCurrentMonth(UTC); + } + + public static long getStartOfCurrentMonth(ZoneId zoneId) { + return LocalDate.now().withDayOfMonth(1).atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + public static long getStartOfNextMonth() { + return getStartOfNextMonth(UTC); + } + + public static long getStartOfNextMonth(ZoneId zoneId) { + return LocalDate.now().with(TemporalAdjusters.firstDayOfNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + public static long getStartOfNextNextMonth() { + return getStartOfNextNextMonth(UTC); + } + + public static long getStartOfNextNextMonth(ZoneId zoneId) { + return LocalDate.now().with(firstDayOfNextNextMonth()).atStartOfDay(zoneId).toInstant().toEpochMilli(); + } + + public static TemporalAdjuster firstDayOfNextNextMonth() { + return (temporal) -> temporal.with(DAY_OF_MONTH, 1).plus(2, MONTHS); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java index 9e543a8f7a..6be56a1815 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -49,14 +49,14 @@ public class ApiApiUsageStateServiceImpl extends AbstractEntityService implement } @Override - public void createDefaultApiUsageState(TenantId tenantId) { + public ApiUsageState createDefaultApiUsageState(TenantId tenantId) { log.trace("Executing createDefaultUsageRecord [{}]", tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(tenantId); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); - apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); } @Override From 7e8837603016db5c3ab79bd0408f6b7df14710db Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 18:55:52 +0300 Subject: [PATCH 015/102] created alias ApiUsageState --- .../DefaultTbApiUsageStateService.java | 6 ++-- .../service/apiusage/TenantApiUsageState.java | 7 +++-- .../queue/DefaultTbCoreConsumerService.java | 3 ++ .../service/security/AccessValidator.java | 27 ++++++++++++++++++ .../service/security/permission/Resource.java | 3 +- .../permission/TenantAdminPermissions.java | 1 + .../dao/usagerecord/ApiUsageStateService.java | 3 ++ .../common/data/id/EntityIdFactory.java | 2 ++ .../data/query/ApiUsageStateFilter.java | 28 +++++++++++++++++++ .../common/data/query/EntityFilter.java | 1 + .../common/data/query/EntityFilterType.java | 3 +- .../query/DefaultEntityQueryRepository.java | 4 +++ .../ApiApiUsageStateServiceImpl.java | 8 ++++++ ui-ngx/src/app/core/http/entity.service.ts | 3 ++ ui-ngx/src/app/shared/models/alias.models.ts | 9 +++++- .../assets/locale/locale.constant-en_US.json | 1 + 16 files changed, 100 insertions(+), 9 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 1b6cdf3cff..185637e543 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -62,7 +62,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Value("${usage.stats.report.enabled:true}") private boolean enabled; - @Value("${usage.stats.check.cycle:60000}") + @Value("${usage.stats.check.cycle:6000}") private long nextCycleCheckInterval; private final Lock updateLock = new ReentrantLock(); @@ -107,7 +107,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); + tsService.save(tenantId, tenantState.getId(), updatedEntries, 0L); } @Override @@ -150,7 +150,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); } } - tenantState = new TenantApiUsageState(dbStateEntity.getEntityId()); + tenantState = new TenantApiUsageState(dbStateEntity.getId()); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 29335c3326..2c483cea29 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; @@ -29,7 +30,7 @@ public class TenantApiUsageState { private final Map currentHourValues = new ConcurrentHashMap<>(); @Getter - private final EntityId entityId; + private final ApiUsageStateId id; @Getter private volatile long currentCycleTs; @Getter @@ -37,8 +38,8 @@ public class TenantApiUsageState { @Getter private volatile long currentHourTs; - public TenantApiUsageState(EntityId entityId) { - this.entityId = entityId; + public TenantApiUsageState(ApiUsageStateId id) { + this.id = id; this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 929d5000f0..ee2aa71e73 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -396,6 +396,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService callback) { + if (currentUser.isSystemAdmin()) { + callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); + } else { + ApiUsageState apiUsageState = apiUsageStateService.findApiUsageStateById(currentUser.getTenantId(), new ApiUsageStateId(entityId.getId())); + if (apiUsageState == null) { + callback.onSuccess(ValidationResult.entityNotFound("Api Usage State with requested id wasn't found!")); + } else { + try { + accessControlService.checkPermission(currentUser, Resource.API_USAGE_STATE, operation, entityId, apiUsageState); + } catch (ThingsboardException e) { + callback.onSuccess(ValidationResult.accessDenied(e.getMessage())); + } + callback.onSuccess(ValidationResult.ok(apiUsageState)); + } + } + } + private void validateAsset(final SecurityUser currentUser, Operation operation, EntityId entityId, FutureCallback callback) { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java index 08534dc2d4..9d16760464 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/Resource.java @@ -35,7 +35,8 @@ public enum Resource { OAUTH2_CONFIGURATION_INFO(), OAUTH2_CONFIGURATION_TEMPLATE(), TENANT_PROFILE(EntityType.TENANT_PROFILE), - DEVICE_PROFILE(EntityType.DEVICE_PROFILE); + DEVICE_PROFILE(EntityType.DEVICE_PROFILE), + API_USAGE_STATE(EntityType.API_USAGE_STATE); private final EntityType entityType; diff --git a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java index 4124292c95..e0c34200f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java +++ b/application/src/main/java/org/thingsboard/server/service/security/permission/TenantAdminPermissions.java @@ -40,6 +40,7 @@ public class TenantAdminPermissions extends AbstractPermissions { put(Resource.WIDGETS_BUNDLE, widgetsPermissionChecker); put(Resource.WIDGET_TYPE, widgetsPermissionChecker); put(Resource.DEVICE_PROFILE, tenantEntityPermissionChecker); + put(Resource.API_USAGE_STATE, tenantEntityPermissionChecker); } public static final PermissionChecker tenantEntityPermissionChecker = new PermissionChecker() { diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java index c39e2a69dc..8131291fbb 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.dao.usagerecord; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; public interface ApiUsageStateService { @@ -25,4 +26,6 @@ public interface ApiUsageStateService { void deleteApiUsageStateByTenantId(TenantId tenantId); ApiUsageState createDefaultApiUsageState(TenantId id); + + ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index dc1589814f..8a1a769c5e 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -66,6 +66,8 @@ public class EntityIdFactory { return new DeviceProfileId(uuid); case TENANT_PROFILE: return new TenantProfileId(uuid); + case API_USAGE_STATE: + return new ApiUsageStateId(uuid); } throw new IllegalArgumentException("EntityType " + type + " is not supported!"); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java new file mode 100644 index 0000000000..436aa44230 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/ApiUsageStateFilter.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.query; + +import lombok.Data; +import org.thingsboard.server.common.data.id.EntityId; + +@Data +public class ApiUsageStateFilter implements EntityFilter { + @Override + public EntityFilterType getType() { + return EntityFilterType.API_USAGE_STATE; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java index d3b9c39530..f2a6dde626 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilter.java @@ -32,6 +32,7 @@ import com.fasterxml.jackson.annotation.JsonTypeInfo; @JsonSubTypes.Type(value = AssetTypeFilter.class, name = "assetType"), @JsonSubTypes.Type(value = DeviceTypeFilter.class, name = "deviceType"), @JsonSubTypes.Type(value = EntityViewTypeFilter.class, name = "entityViewType"), + @JsonSubTypes.Type(value = ApiUsageStateFilter.class, name = "apiUsageState"), @JsonSubTypes.Type(value = RelationsQueryFilter.class, name = "relationsQuery"), @JsonSubTypes.Type(value = AssetSearchQueryFilter.class, name = "assetSearchQuery"), @JsonSubTypes.Type(value = DeviceSearchQueryFilter.class, name = "deviceSearchQuery"), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java index 0c05fdc8e4..12860b8453 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/query/EntityFilterType.java @@ -25,7 +25,8 @@ public enum EntityFilterType { RELATIONS_QUERY("relationsQuery"), ASSET_SEARCH_QUERY("assetSearchQuery"), DEVICE_SEARCH_QUERY("deviceSearchQuery"), - ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"); + ENTITY_VIEW_SEARCH_QUERY("entityViewSearchQuery"), + API_USAGE_STATE("apiUsageState"); private final String label; diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 75f7658a5a..380e166aa5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -210,6 +210,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.CUSTOMER, "customer"); entityTableMap.put(EntityType.USER, "tb_user"); entityTableMap.put(EntityType.TENANT, "tenant"); + entityTableMap.put(EntityType.API_USAGE_STATE, "(select aus.id, aus.created_time, aus.tenant_id, '' as name, '' as additional_info from api_usage_state as aus)"); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ @@ -431,6 +432,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { case DEVICE_SEARCH_QUERY: case ASSET_SEARCH_QUERY: case ENTITY_VIEW_SEARCH_QUERY: + case API_USAGE_STATE: return ""; default: throw new RuntimeException("Not implemented!"); @@ -682,6 +684,8 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { return EntityType.ENTITY_VIEW; case RELATIONS_QUERY: return ((RelationsQueryFilter) entityFilter).getRootEntity().getEntityType(); + case API_USAGE_STATE: + return EntityType.API_USAGE_STATE; default: throw new RuntimeException("Not implemented!"); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java index 6be56a1815..00ecde9d95 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -20,6 +20,7 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -66,6 +67,13 @@ public class ApiApiUsageStateServiceImpl extends AbstractEntityService implement return apiUsageStateDao.findTenantApiUsageState(tenantId.getId()); } + @Override + public ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id) { + log.trace("Executing findApiUsageStateById, tenantId [{}], apiUsageStateId [{}]", tenantId, id); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateId(id, "Incorrect apiUsageStateId " + id); + return apiUsageStateDao.findById(tenantId, id.getId()); } + private DataValidator apiUsageStateValidator = new DataValidator() { @Override diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index a510735855..acd89e3c1b 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -753,6 +753,9 @@ export class EntityService { case AliasFilterType.entityViewType: result.entityFilter = deepClone(filter); return of(result); + case AliasFilterType.apiUsageState: + result.entityFilter = deepClone(filter); + return of(result); case AliasFilterType.relationsQuery: result.stateEntity = filter.rootStateEntity; let rootEntityType; diff --git a/ui-ngx/src/app/shared/models/alias.models.ts b/ui-ngx/src/app/shared/models/alias.models.ts index 35b52435a2..f437931f27 100644 --- a/ui-ngx/src/app/shared/models/alias.models.ts +++ b/ui-ngx/src/app/shared/models/alias.models.ts @@ -28,6 +28,7 @@ export enum AliasFilterType { assetType = 'assetType', deviceType = 'deviceType', entityViewType = 'entityViewType', + apiUsageState = 'apiUsageState', relationsQuery = 'relationsQuery', assetSearchQuery = 'assetSearchQuery', deviceSearchQuery = 'deviceSearchQuery', @@ -43,6 +44,7 @@ export const aliasFilterTypeTranslationMap = new Map( [ AliasFilterType.assetType, 'alias.filter-type-asset-type' ], [ AliasFilterType.deviceType, 'alias.filter-type-device-type' ], [ AliasFilterType.entityViewType, 'alias.filter-type-entity-view-type' ], + [ AliasFilterType.apiUsageState, 'alias.filter-type-apiUsageState' ], [ AliasFilterType.relationsQuery, 'alias.filter-type-relations-query' ], [ AliasFilterType.assetSearchQuery, 'alias.filter-type-asset-search-query' ], [ AliasFilterType.deviceSearchQuery, 'alias.filter-type-device-search-query' ], @@ -106,6 +108,10 @@ export interface EntitySearchQueryFilter { fetchLastLevelOnly?: boolean; } +export interface ApiUsageStateFilter { + +} + export interface AssetSearchQueryFilter extends EntitySearchQueryFilter { assetTypes?: string[]; } @@ -129,7 +135,8 @@ export type EntityFilters = RelationsQueryFilter & AssetSearchQueryFilter & DeviceSearchQueryFilter & - EntityViewSearchQueryFilter; + EntityViewSearchQueryFilter & + EntitySearchQueryFilter; export interface EntityAliasFilter extends EntityFilters { type?: AliasFilterType; diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..082c2241b4 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -288,6 +288,7 @@ "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", "filter-type-entity-view-search-query": "Entity view search query", "filter-type-entity-view-search-query-description": "Entity views with types {{entityViewTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}", + "filter-type-apiUsageState": "Api Usage State", "entity-filter": "Entity filter", "resolve-multiple": "Resolve as multiple entities", "filter-type": "Filter type", From f7aa8df607f494444ec0f8261a37721d5335f8ba Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 20 Oct 2020 18:57:08 +0300 Subject: [PATCH 016/102] refactored --- .../server/service/apiusage/DefaultTbApiUsageStateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 185637e543..1a7d1cfae1 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -62,7 +62,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Value("${usage.stats.report.enabled:true}") private boolean enabled; - @Value("${usage.stats.check.cycle:6000}") + @Value("${usage.stats.check.cycle:60000}") private long nextCycleCheckInterval; private final Lock updateLock = new ReentrantLock(); From 9d14a38966c5351edd16c01b339a9ff3c19e88e7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 20 Oct 2020 18:57:15 +0300 Subject: [PATCH 017/102] Tenant Profile updates for ApiUsageStateService --- .../server/actors/app/AppActor.java | 9 +- .../DefaultTbApiUsageStateService.java | 37 +++++- .../apiusage/TbApiUsageStateService.java | 6 +- .../service/apiusage/TenantApiUsageState.java | 122 +++++++++++++++++- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbRuleEngineConsumerService.java | 4 +- .../processing/AbstractConsumerService.java | 18 ++- .../dao/usagerecord/ApiUsageStateService.java | 5 +- .../server/common/data/ApiUsageState.java | 30 ++--- .../ApiApiUsageStateServiceImpl.java | 8 ++ 10 files changed, 197 insertions(+), 44 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 826596db56..fea5558540 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -40,8 +40,6 @@ import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.dao.model.ModelConstants; -import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; @@ -150,15 +148,12 @@ public class AppActor extends ContextAwareActor { private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { TbActorRef target = null; if (TenantId.SYS_TENANT_ID.equals(msg.getTenantId())) { - if (msg.getEntityId().getEntityType() == EntityType.TENANT_PROFILE) { - tenantProfileCache.evict(new TenantProfileId(msg.getEntityId().getId())); - } else { + if (!EntityType.TENANT_PROFILE.equals(msg.getEntityId().getEntityType())) { log.warn("Message has system tenant id: {}", msg); } } else { - if (msg.getEntityId().getEntityType() == EntityType.TENANT) { + if (EntityType.TENANT.equals(msg.getEntityId().getEntityType())) { TenantId tenantId = new TenantId(msg.getEntityId().getId()); - tenantProfileCache.evict(tenantId); if (msg.getEvent() == ComponentLifecycleEvent.DELETED) { log.info("[{}] Handling tenant deleted notification: {}", msg.getTenantId(), msg); deletedTenants.add(tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 1b6cdf3cff..84801224e1 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -22,6 +22,7 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; @@ -87,6 +88,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantApiUsageState tenantState; List updatedEntries; + boolean stateUpdated = false; updateLock.lock(); try { tenantState = getOrFetchState(tenantId); @@ -101,13 +103,21 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); long newValue = tenantState.add(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); - newValue = tenantState.addToHourly(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newValue))); + long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); + updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue))); + stateUpdated |= tenantState.checkStateUpdatedDueToThreshold(recordKey); } } finally { updateLock.unlock(); } tsService.save(tenantId, tenantState.getEntityId(), updatedEntries, 0L); + if (stateUpdated) { + // Save new state into the database; + apiUsageStateService.update(tenantState.getApiUsageState()); + //TODO: clear cache on cluster repartition. + //TODO: update profiles on tenant and profile updates. + //TODO: broadcast to everyone notifications about enabled/disabled features. + } } @Override @@ -116,12 +126,26 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } @Override - public void onAddedToAllowList(TenantId tenantId) { - + public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { + TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); + updateLock.lock(); + try { + tenantStates.values().forEach(state -> { + if (tenantProfile.getId().equals(state.getTenantProfileId())) { + state.setTenantProfileData(tenantProfile.getProfileData()); + if (state.checkStateUpdatedDueToThresholds()) { + apiUsageStateService.update(state.getApiUsageState()); + //TODO: send notification to cluster; + } + } + }); + } finally { + updateLock.unlock(); + } } @Override - public void onAddedToDenyList(TenantId tenantId) { + public void onTenantUpdate(TenantId tenantId) { } @@ -150,7 +174,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); } } - tenantState = new TenantApiUsageState(dbStateEntity.getEntityId()); + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); try { List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index e79b79d069..d17bd9a3dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.apiusage; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; @@ -26,8 +27,7 @@ public interface TbApiUsageStateService { TenantApiUsageState getApiUsageState(TenantId tenantId); - void onAddedToAllowList(TenantId tenantId); - - void onAddedToDenyList(TenantId tenantId); + void onTenantProfileUpdate(TenantProfileId tenantProfileId); + void onTenantUpdate(TenantId tenantId); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 29335c3326..0492ba9793 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -16,8 +16,13 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; +import lombok.Setter; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; import java.util.Map; @@ -29,7 +34,13 @@ public class TenantApiUsageState { private final Map currentHourValues = new ConcurrentHashMap<>(); @Getter - private final EntityId entityId; + @Setter + private TenantProfileId tenantProfileId; + @Getter + @Setter + private TenantProfileData tenantProfileData; + @Getter + private final ApiUsageState apiUsageState; @Getter private volatile long currentCycleTs; @Getter @@ -37,8 +48,10 @@ public class TenantApiUsageState { @Getter private volatile long currentHourTs; - public TenantApiUsageState(EntityId entityId) { - this.entityId = entityId; + public TenantApiUsageState(TenantProfile tenantProfile, ApiUsageState apiUsageState) { + this.tenantProfileId = tenantProfile.getId(); + this.tenantProfileData = tenantProfile.getProfileData(); + this.apiUsageState = apiUsageState; this.currentCycleTs = SchedulerUtils.getStartOfCurrentMonth(); this.nextCycleTs = SchedulerUtils.getStartOfNextMonth(); this.currentHourTs = SchedulerUtils.getStartOfCurrentHour(); @@ -58,6 +71,10 @@ public class TenantApiUsageState { return result; } + public long get(ApiUsageRecordKey key) { + return currentCycleValues.getOrDefault(key, 0L); + } + public long addToHourly(ApiUsageRecordKey key, long value) { long result = currentHourValues.getOrDefault(key, 0L) + value; currentHourValues.put(key, result); @@ -79,4 +96,103 @@ public class TenantApiUsageState { } } + public long getProfileThreshold(ApiUsageRecordKey key) { + Object threshold = tenantProfileData.getProperties().get(key.name()); + if (threshold != null) { + if (threshold instanceof String) { + return Long.parseLong((String) threshold); + } else if (threshold instanceof Long) { + return (Long) threshold; + } + } + return 0L; + } + + public EntityId getEntityId() { + return apiUsageState.getEntityId(); + } + + public boolean isTransportEnabled() { + return apiUsageState.isTransportEnabled(); + } + + public boolean isDbStorageEnabled() { + return apiUsageState.isDbStorageEnabled(); + } + + public boolean isRuleEngineEnabled() { + return apiUsageState.isRuleEngineEnabled(); + } + + public boolean isJsExecEnabled() { + return apiUsageState.isJsExecEnabled(); + } + + public void setTransportEnabled(boolean transportEnabled) { + apiUsageState.setTransportEnabled(transportEnabled); + } + + public void setDbStorageEnabled(boolean dbStorageEnabled) { + apiUsageState.setDbStorageEnabled(dbStorageEnabled); + } + + public void setRuleEngineEnabled(boolean ruleEngineEnabled) { + apiUsageState.setRuleEngineEnabled(ruleEngineEnabled); + } + + public void setJsExecEnabled(boolean jsExecEnabled) { + apiUsageState.setJsExecEnabled(jsExecEnabled); + } + + public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { + switch (recordKey) { + case MSG_COUNT: + case MSG_BYTES_COUNT: + case DP_TRANSPORT_COUNT: + return isTransportEnabled(); + case RE_EXEC_COUNT: + return isRuleEngineEnabled(); + case DP_STORAGE_COUNT: + return isDbStorageEnabled(); + case JS_EXEC_COUNT: + return isJsExecEnabled(); + default: + return true; + } + } + + public boolean setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { + boolean currentValue = isFeatureEnabled(recordKey); + switch (recordKey) { + case MSG_COUNT: + case MSG_BYTES_COUNT: + case DP_TRANSPORT_COUNT: + setTransportEnabled(value); + break; + case RE_EXEC_COUNT: + setRuleEngineEnabled(value); + break; + case DP_STORAGE_COUNT: + setDbStorageEnabled(value); + break; + case JS_EXEC_COUNT: + setJsExecEnabled(value); + break; + } + return currentValue == value; + } + + public boolean checkStateUpdatedDueToThresholds() { + boolean update = false; + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + update |= checkStateUpdatedDueToThreshold(key); + } + return update; + } + + public boolean checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { + long value = get(recordKey); + long threshold = getProfileThreshold(recordKey); + return setFeatureValue(recordKey, threshold == 0 || value < threshold); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 929d5000f0..4ad073ead8 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -107,7 +107,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> nfConsumer; public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, - TbDeviceProfileCache deviceProfileCache, TbQueueConsumer> nfConsumer) { + TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbApiUsageStateService apiUsageStateService, TbQueueConsumer> nfConsumer) { this.actorContext = actorContext; this.encodingService = encodingService; + this.tenantProfileCache = tenantProfileCache; this.deviceProfileCache = deviceProfileCache; + this.apiUsageStateService = apiUsageStateService; this.nfConsumer = nfConsumer; } @@ -143,7 +150,14 @@ public abstract class AbstractConsumerService implements HasTenan private static final long serialVersionUID = 8250339805336035966L; + @Getter @Setter private TenantId tenantId; + @Getter @Setter private EntityId entityId; + @Getter @Setter + private boolean transportEnabled; + @Getter @Setter + private boolean dbStorageEnabled; + @Getter @Setter + private boolean ruleEngineEnabled; + @Getter @Setter + private boolean jsExecEnabled; public ApiUsageState() { super(); @@ -43,22 +55,4 @@ public class ApiUsageState extends BaseData implements HasTenan this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); } - - @Override - public TenantId getTenantId() { - return tenantId; - } - - public void setTenantId(TenantId tenantId) { - this.tenantId = tenantId; - } - - public EntityId getEntityId() { - return entityId; - } - - public void setEntityId(EntityId entityId) { - this.entityId = entityId; - } - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java index 6be56a1815..7dc390046e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java @@ -59,6 +59,14 @@ public class ApiApiUsageStateServiceImpl extends AbstractEntityService implement return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); } + @Override + public ApiUsageState update(ApiUsageState apiUsageState) { + log.trace("Executing save [{}]", apiUsageState.getTenantId()); + validateId(apiUsageState.getTenantId(), INCORRECT_TENANT_ID + apiUsageState.getTenantId()); + validateId(apiUsageState.getId(), "Can't save new usage state. Only update is allowed!"); + return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + } + @Override public ApiUsageState findTenantApiUsageState(TenantId tenantId) { log.trace("Executing findTenantUsageRecord, tenantId [{}]", tenantId); From 9ab92ba73cddfb584bebfd3166e41908ae989236 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 21 Oct 2020 10:21:01 +0300 Subject: [PATCH 018/102] validateApiUsageState improvements --- .../thingsboard/server/service/security/AccessValidator.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index 46de4f150e..57c192235e 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -250,6 +250,9 @@ public class AccessValidator { if (currentUser.isSystemAdmin()) { callback.onSuccess(ValidationResult.accessDenied(SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION)); } else { + if (!operation.equals(Operation.READ_TELEMETRY)) { + callback.onSuccess(ValidationResult.accessDenied("Allowed only READ_TELEMETRY operation!")); + } ApiUsageState apiUsageState = apiUsageStateService.findApiUsageStateById(currentUser.getTenantId(), new ApiUsageStateId(entityId.getId())); if (apiUsageState == null) { callback.onSuccess(ValidationResult.entityNotFound("Api Usage State with requested id wasn't found!")); From a922b8227f299de8b7c0bb48a6cf322f2bc6a464 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 21 Oct 2020 10:35:15 +0300 Subject: [PATCH 019/102] Usage records compile fix --- .../server/service/queue/DefaultTbCoreConsumerService.java | 3 +++ .../service/queue/DefaultTbRuleEngineConsumerService.java | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 4ad073ead8..33c21a5b5f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -54,6 +54,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; +import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; @@ -104,6 +105,8 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Wed, 21 Oct 2020 11:13:48 +0300 Subject: [PATCH 020/102] Cleanup of the map on repartition --- .../DefaultTbApiUsageStateService.java | 41 +++++++++++++++---- .../apiusage/TbApiUsageStateService.java | 6 ++- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 84801224e1..003ee72807 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; import org.thingsboard.server.dao.timeseries.TimeseriesService; @@ -33,6 +34,8 @@ import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionChangeEvent; +import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; @@ -54,6 +57,7 @@ import java.util.concurrent.locks.ReentrantLock; public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "HOURLY_"; + private final PartitionService partitionService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; private final SchedulerComponent scheduler; @@ -68,7 +72,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); - public DefaultTbApiUsageStateService(ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { + public DefaultTbApiUsageStateService(PartitionService partitionService, ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { + this.partitionService = partitionService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; this.scheduler = scheduler; @@ -120,8 +125,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @Override + public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { + if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { + tenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + } + } + @Override public TenantApiUsageState getApiUsageState(TenantId tenantId) { + //We should always get it from the map of from the database; return null; } @@ -132,11 +145,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { try { tenantStates.values().forEach(state -> { if (tenantProfile.getId().equals(state.getTenantProfileId())) { - state.setTenantProfileData(tenantProfile.getProfileData()); - if (state.checkStateUpdatedDueToThresholds()) { - apiUsageStateService.update(state.getApiUsageState()); - //TODO: send notification to cluster; - } + updateTenantState(state, tenantProfile); } }); } finally { @@ -146,7 +155,25 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantUpdate(TenantId tenantId) { + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + updateLock.lock(); + try { + TenantApiUsageState state = tenantStates.get(tenantId); + if (state != null && !state.getTenantProfileId().equals(tenantProfile.getId())) { + updateTenantState(state, tenantProfile); + } + } finally { + updateLock.unlock(); + } + } + + private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { + state.setTenantProfileData(tenantProfile.getProfileData()); + if (state.checkStateUpdatedDueToThresholds()) { + apiUsageStateService.update(state.getApiUsageState()); + //TODO: send notification to cluster; + } } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index d17bd9a3dd..4816dc1037 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -15,13 +15,15 @@ */ package org.thingsboard.server.service.apiusage; +import org.springframework.context.ApplicationListener; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.PartitionChangeEvent; -public interface TbApiUsageStateService { +public interface TbApiUsageStateService extends ApplicationListener { void process(TbProtoQueueMsg msg, TbCallback callback); From 41212e11c01130eee0ca99127c5f12f49b60f9d4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Thu, 22 Oct 2020 15:33:34 +0300 Subject: [PATCH 021/102] created tenant profile configuration --- .../DefaultTbApiUsageStateService.java | 2 +- .../apiusage/TbApiUsageStateService.java | 2 +- .../service/apiusage/TenantApiUsageState.java | 2 +- .../DefaultSystemDataLoaderService.java | 12 +- .../BaseTenantProfileControllerTest.java | 2 +- .../server/common/data/TenantProfile.java | 1 + .../server/common/data/TenantProfileType.java | 20 +++ .../DefaultTenantProfileConfiguration.java | 45 ++++++ .../profile/TenantProfileConfiguration.java | 36 +++++ .../profile}/TenantProfileData.java | 4 +- .../DefaultTransportRateLimitService.java | 2 +- .../dao/model/sql/TenantProfileEntity.java | 3 +- .../dao/tenant/TenantProfileServiceImpl.java | 4 +- .../service/BaseTenantProfileServiceTest.java | 11 +- .../home/components/home-components.module.ts | 4 + .../tenant-profile-autocomplete.component.ts | 1 - .../tenant-profile-data.component.html | 16 +- .../profile/tenant-profile-data.component.ts | 6 +- .../profile/tenant-profile.component.ts | 15 +- ...enant-profile-configuration.component.html | 141 ++++++++++++++++++ ...-tenant-profile-configuration.component.ts | 115 ++++++++++++++ ...enant-profile-configuration.component.html | 27 ++++ .../tenant-profile-configuration.component.ts | 103 +++++++++++++ ui-ngx/src/app/shared/models/tenant.model.ts | 53 ++++++- .../assets/locale/locale.constant-en_US.json | 33 +++- 25 files changed, 628 insertions(+), 32 deletions(-) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java rename common/data/src/main/java/org/thingsboard/server/common/data/{ => tenant/profile}/TenantProfileData.java (91%) create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html create mode 100644 ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index df7e5013d2..2dd06c9957 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 4816dc1037..dcd9d4823e 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 0492ba9793..9c39047742 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -20,7 +20,7 @@ import lombok.Setter; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 1a0f9eca65..eeef99fff6 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -30,7 +30,8 @@ import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.CustomerId; @@ -127,6 +128,10 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { public void createDefaultTenantProfiles() throws Exception { tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID); + TenantProfileData tenantProfileData = new TenantProfileData(); + DefaultTenantProfileConfiguration configuration = new DefaultTenantProfileConfiguration(); + tenantProfileData.setConfiguration(configuration); + TenantProfile isolatedTbCoreProfile = new TenantProfile(); isolatedTbCoreProfile.setDefault(false); isolatedTbCoreProfile.setName("Isolated TB Core"); @@ -134,6 +139,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile"); isolatedTbCoreProfile.setIsolatedTbCore(true); isolatedTbCoreProfile.setIsolatedTbRuleEngine(false); + isolatedTbCoreProfile.setProfileData(tenantProfileData); try { tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreProfile); } catch (DataValidationException e) { @@ -147,6 +153,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile"); isolatedTbRuleEngineProfile.setIsolatedTbCore(false); isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true); + isolatedTbRuleEngineProfile.setProfileData(tenantProfileData); + try { tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbRuleEngineProfile); } catch (DataValidationException e) { @@ -160,6 +168,8 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile"); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true); + isolatedTbCoreAndTbRuleEngineProfile.setProfileData(tenantProfileData); + try { tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, isolatedTbCoreAndTbRuleEngineProfile); } catch (DataValidationException e) { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java index a4d615ae2b..311a93bf82 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java @@ -23,7 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index 32f620f869..5e5f4a09de 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -21,6 +21,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import java.io.ByteArrayInputStream; import java.io.IOException; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java new file mode 100644 index 0000000000..2b0ed502ac --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileType.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public enum TenantProfileType { + DEFAULT +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java new file mode 100644 index 0000000000..f361ec901c --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.tenant.profile; + +import lombok.Data; +import org.thingsboard.server.common.data.TenantProfileType; + +@Data +public class DefaultTenantProfileConfiguration implements TenantProfileConfiguration { + + private long maxDevices; + private long maxAssets; + + private String transportTenantMsgRateLimit; + private String transportTenantTelemetryMsgRateLimit; + private String transportTenantTelemetryDataPointsRateLimit; + private String transportDeviceMsgRateLimit; + private String transportDeviceTelemetryMsgRateLimit; + private String transportDeviceTelemetryDataPointsRateLimit; + + private long maxTransportMessages; + private long maxTransportDataPoints; + private long maxREExecutions; + private long maxJSExecutions; + private long maxDPStorageDays; + private int maxRuleNodeExecutionsPerMessage; + + @Override + public TenantProfileType getType() { + return TenantProfileType.DEFAULT; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java new file mode 100644 index 0000000000..07b184b389 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.tenant.profile; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonSubTypes; +import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.TenantProfileType; + +@JsonIgnoreProperties(ignoreUnknown = true) +@JsonTypeInfo( + use = JsonTypeInfo.Id.NAME, + include = JsonTypeInfo.As.PROPERTY, + property = "type") +@JsonSubTypes({ + @JsonSubTypes.Type(value = DefaultTenantProfileConfiguration.class, name = "DEFAULT")}) +public interface TenantProfileConfiguration { + + @JsonIgnore + TenantProfileType getType(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java similarity index 91% rename from common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileData.java rename to common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java index 54319276f1..6336cb4ace 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.common.data; +package org.thingsboard.server.common.data.tenant.profile; import com.fasterxml.jackson.annotation.JsonAnyGetter; import com.fasterxml.jackson.annotation.JsonAnySetter; @@ -26,6 +26,8 @@ import java.util.Map; @Data public class TenantProfileData { + private TenantProfileConfiguration configuration; + @JsonIgnore private Map properties = new HashMap<>(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 569f5446db..8e811a282f 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -18,9 +18,9 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.queue.util.TbTransportComponent; diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java index 7c69f58935..7663682496 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/TenantProfileEntity.java @@ -22,14 +22,13 @@ import lombok.EqualsAndHashCode; import org.hibernate.annotations.Type; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.model.BaseSqlEntity; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.SearchTextEntity; import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.dao.util.mapping.JsonBinaryType; -import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 939e2b7426..864679ce2a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -23,20 +23,18 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.Cacheable; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.Arrays; import java.util.Collections; diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java index a9917126df..22830bed8c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java @@ -21,13 +21,12 @@ import org.junit.Test; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import java.util.ArrayList; import java.util.Collections; @@ -188,8 +187,8 @@ public class BaseTenantProfileServiceTest extends AbstractServiceTest { Assert.assertTrue(pageData.getData().isEmpty()); tenantProfiles.addAll(pageData.getData()); - for (int i=0;i<28;i++) { - TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i); + for (int i = 0; i < 28; i++) { + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i); tenantProfiles.add(tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile)); } @@ -224,8 +223,8 @@ public class BaseTenantProfileServiceTest extends AbstractServiceTest { List tenantProfiles = new ArrayList<>(); - for (int i=0;i<28;i++) { - TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile"+i); + for (int i = 0; i < 28; i++) { + TenantProfile tenantProfile = this.createTenantProfile("Tenant Profile" + i); tenantProfiles.add(tenantProfileService.saveTenantProfile(TenantId.SYS_TENANT_ID, tenantProfile)); } diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index d2e7cb0a28..ec7ccea660 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/home-components.module.ts @@ -113,6 +113,8 @@ import { AlarmScheduleInfoComponent } from './profile/alarm/alarm-schedule-info. import { AlarmScheduleDialogComponent } from '@home/components/profile/alarm/alarm-schedule-dialog.component'; import { EditAlarmDetailsDialogComponent } from './profile/alarm/edit-alarm-details-dialog.component'; import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alarm/alarm-rule-condition-dialog.component'; +import { DefaultTenantProfileConfigurationComponent } from './profile/tenant/default-tenant-profile-configuration.component'; +import { TenantProfileConfigurationComponent } from './profile/tenant/tenant-profile-configuration.component'; @NgModule({ declarations: @@ -182,6 +184,8 @@ import { AlarmRuleConditionDialogComponent } from '@home/components/profile/alar FilterUserInfoDialogComponent, FilterPredicateValueComponent, TenantProfileAutocompleteComponent, + DefaultTenantProfileConfigurationComponent, + TenantProfileConfigurationComponent, TenantProfileDataComponent, TenantProfileComponent, TenantProfileDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts index 3cdb0b3a4e..9553e23528 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts @@ -204,7 +204,6 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor, createTenantProfile($event: Event, profileName: string) { $event.preventDefault(); const tenantProfile: TenantProfile = { - id: null, name: profileName }; this.openTenantProfileDialog(tenantProfile, true); diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html index a3d504d0e0..3d853940f4 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.html @@ -16,9 +16,15 @@ -->
- - + + + +
tenant-profile.profile-configuration
+
+
+ + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts index 377b6f0978..cc6ce7a6ce 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-data.component.ts @@ -62,7 +62,7 @@ export class TenantProfileDataComponent implements ControlValueAccessor, OnInit ngOnInit() { this.tenantProfileDataFormGroup = this.fb.group({ - tenantProfileData: [null, Validators.required] + configuration: [null, Validators.required] }); this.tenantProfileDataFormGroup.valueChanges.subscribe(() => { this.updateModel(); @@ -79,13 +79,13 @@ export class TenantProfileDataComponent implements ControlValueAccessor, OnInit } writeValue(value: TenantProfileData | null): void { - this.tenantProfileDataFormGroup.get('tenantProfileData').patchValue(value, {emitEvent: false}); + this.tenantProfileDataFormGroup.patchValue({configuration: value?.configuration}, {emitEvent: false}); } private updateModel() { let tenantProfileData: TenantProfileData = null; if (this.tenantProfileDataFormGroup.valid) { - tenantProfileData = this.tenantProfileDataFormGroup.getRawValue().tenantProfileData; + tenantProfileData = this.tenantProfileDataFormGroup.getRawValue(); } this.propagateChange(tenantProfileData); } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts index 2aadb551de..7cca3ae414 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts @@ -18,7 +18,12 @@ import { Component, Inject, Input, Optional } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { TenantProfile } from '@app/shared/models/tenant.model'; +import { + createTenantProfileConfiguration, + TenantProfile, + TenantProfileData, + TenantProfileType +} from '@app/shared/models/tenant.model'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -56,7 +61,9 @@ export class TenantProfileComponent extends EntityComponent { name: [entity ? entity.name : '', [Validators.required]], isolatedTbCore: [entity ? entity.isolatedTbCore : false, []], isolatedTbRuleEngine: [entity ? entity.isolatedTbRuleEngine : false, []], - profileData: [entity && !this.isAdd ? entity.profileData : {}, []], + profileData: [entity && !this.isAdd ? entity.profileData : { + configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) + } as TenantProfileData, []], description: [entity ? entity.description : '', []], } ); @@ -66,7 +73,9 @@ export class TenantProfileComponent extends EntityComponent { this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({profileData: entity.profileData}); + this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData: { + configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) + } as TenantProfileData}); this.entityForm.patchValue({description: entity.description}); } diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html new file mode 100644 index 0000000000..cdb4febcc0 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -0,0 +1,141 @@ + +
+
+ + tenant-profile.maximum-devices + + + {{ 'tenant-profile.maximum-devices-required' | translate}} + + + {{ 'tenant-profile.maximum-devices-range' | translate}} + + + + tenant-profile.maximum-assets + + + {{ 'tenant-profile.maximum-assets-required' | translate}} + + + {{ 'tenant-profile.maximum-assets-range' | translate}} + + + + tenant-profile.max-transport-messages + + + {{ 'tenant-profile.max-transport-messages-range' | translate}} + + + {{ 'tenant-profile.max-transport-messages-required' | translate}} + + + + tenant-profile.max-transport-data-points + + + {{ 'tenant-profile.max-transport-data-points-required' | translate}} + + + {{ 'tenant-profile.max-transport-data-points-range' | translate}} + + + + tenant-profile.max-r-e-executions + + + {{ 'tenant-profile.max-r-e-executions-required' | translate}} + + + {{ 'tenant-profile.max-r-e-executions-range' | translate}} + + + + tenant-profile.max-j-s-executions + + + {{ 'tenant-profile.max-j-s-executions-required' | translate}} + + + {{ 'tenant-profile.max-j-s-executions-range' | translate}} + + + + tenant-profile.max-d-p-storage-days + + + {{ 'tenant-profile.max-d-p-storage-days-required' | translate}} + + + {{ 'tenant-profile.max-d-p-storage-days-range' | translate}} + + + + tenant-profile.max-rule-node-executions-per-message + + + {{ 'tenant-profile.max-rule-node-executions-per-message-required' | translate}} + + + {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} + + + + tenant-profile.transport-tenant-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-data-points-rate-limit + + + + tenant-profile.transport-device-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-data-points-rate-limit + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts new file mode 100644 index 0000000000..f83b59c35a --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -0,0 +1,115 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { + DefaultTenantProfileConfiguration, + TenantProfileConfiguration, + TenantProfileType +} from '@shared/models/tenant.model'; +import { isDefinedAndNotNull } from '@core/utils'; + +@Component({ + selector: 'tb-default-tenant-profile-configuration', + templateUrl: './default-tenant-profile-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => DefaultTenantProfileConfigurationComponent), + multi: true + }] +}) +export class DefaultTenantProfileConfigurationComponent implements ControlValueAccessor, OnInit { + + defaultTenantProfileConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.defaultTenantProfileConfigurationFormGroup = this.fb.group({ + configuration: this.fb.group({ + maxDevices: [null, [Validators.required, Validators.min(0)]], + maxAssets: [null, [Validators.required, Validators.min(0)]], + transportTenantMsgRateLimit: [null, []], + transportTenantTelemetryMsgRateLimit: [null, []], + transportTenantTelemetryDataPointsRateLimit: [null, []], + transportDeviceMsgRateLimit: [null, []], + transportDeviceTelemetryMsgRateLimit: [null, []], + transportDeviceTelemetryDataPointsRateLimit: [null, []], + maxTransportMessages: [null, [Validators.required, Validators.min(0)]], + maxTransportDataPoints: [null, [Validators.required, Validators.min(0)]], + maxREExecutions: [null, [Validators.required, Validators.min(0)]], + maxJSExecutions: [null, [Validators.required, Validators.min(0)]], + maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], + maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] + }) + }); + this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.defaultTenantProfileConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.defaultTenantProfileConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: DefaultTenantProfileConfiguration | null): void { + if (isDefinedAndNotNull(value)) { + this.defaultTenantProfileConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + } + } + + private updateModel() { + let configuration: TenantProfileConfiguration = null; + if (this.defaultTenantProfileConfigurationFormGroup.valid) { + configuration = this.defaultTenantProfileConfigurationFormGroup.getRawValue().configuration; + configuration.type = TenantProfileType.DEFAULT; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html new file mode 100644 index 0000000000..3204b7d2fb --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.html @@ -0,0 +1,27 @@ + +
+
+ + + + +
+
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts new file mode 100644 index 0000000000..4f5fa989d4 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/tenant-profile-configuration.component.ts @@ -0,0 +1,103 @@ +/// +/// Copyright © 2016-2020 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Validators } from '@angular/forms'; +import { Store } from '@ngrx/store'; +import { AppState } from '@app/core/core.state'; +import { coerceBooleanProperty } from '@angular/cdk/coercion'; +import { deepClone } from '@core/utils'; +import { TenantProfileConfiguration, TenantProfileType } from '@shared/models/tenant.model'; + +@Component({ + selector: 'tb-tenant-profile-configuration', + templateUrl: './tenant-profile-configuration.component.html', + styleUrls: [], + providers: [{ + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TenantProfileConfigurationComponent), + multi: true + }] +}) +export class TenantProfileConfigurationComponent implements ControlValueAccessor, OnInit { + + tenantProfileType = TenantProfileType; + + tenantProfileConfigurationFormGroup: FormGroup; + + private requiredValue: boolean; + get required(): boolean { + return this.requiredValue; + } + @Input() + set required(value: boolean) { + this.requiredValue = coerceBooleanProperty(value); + } + + @Input() + disabled: boolean; + + type: TenantProfileType; + + private propagateChange = (v: any) => { }; + + constructor(private store: Store, + private fb: FormBuilder) { + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + ngOnInit() { + this.tenantProfileConfigurationFormGroup = this.fb.group({ + configuration: [null, Validators.required] + }); + this.tenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.tenantProfileConfigurationFormGroup.disable({emitEvent: false}); + } else { + this.tenantProfileConfigurationFormGroup.enable({emitEvent: false}); + } + } + + writeValue(value: TenantProfileConfiguration | null): void { + this.type = value?.type; + const configuration = deepClone(value); + if (configuration) { + delete configuration.type; + } + this.tenantProfileConfigurationFormGroup.patchValue({configuration}, {emitEvent: false}); + } + + private updateModel() { + let configuration: TenantProfileConfiguration = null; + if (this.tenantProfileConfigurationFormGroup.valid) { + configuration = this.tenantProfileConfigurationFormGroup.getRawValue().configuration; + configuration.type = this.type; + } + this.propagateChange(configuration); + } +} diff --git a/ui-ngx/src/app/shared/models/tenant.model.ts b/ui-ngx/src/app/shared/models/tenant.model.ts index 378dca7c25..ad946efdb7 100644 --- a/ui-ngx/src/app/shared/models/tenant.model.ts +++ b/ui-ngx/src/app/shared/models/tenant.model.ts @@ -19,8 +19,59 @@ import { TenantId } from './id/tenant-id'; import { TenantProfileId } from '@shared/models/id/tenant-profile-id'; import { BaseData } from '@shared/models/base-data'; +export enum TenantProfileType { + DEFAULT = 'DEFAULT' +} + +export interface DefaultTenantProfileConfiguration { + maxDevices: number; + maxAssets: number; + + transportTenantMsgRateLimit?: string; + transportTenantTelemetryMsgRateLimit?: string; + transportTenantTelemetryDataPointsRateLimit?: string; + transportDeviceMsgRateLimit?: string; + transportDeviceTelemetryMsgRateLimit?: string; + transportDeviceTelemetryDataPointsRateLimit?: string; + + maxTransportMessages: number; + maxTransportDataPoints: number; + maxREExecutions: number; + maxJSExecutions: number; + maxDPStorageDays: number; + maxRuleNodeExecutionsPerMessage: number; +} + +export type TenantProfileConfigurations = DefaultTenantProfileConfiguration; + +export interface TenantProfileConfiguration extends TenantProfileConfigurations { + type: TenantProfileType; +} + +export function createTenantProfileConfiguration(type: TenantProfileType): TenantProfileConfiguration { + let configuration: TenantProfileConfiguration = null; + if (type) { + switch (type) { + case TenantProfileType.DEFAULT: + const defaultConfiguration: DefaultTenantProfileConfiguration = { + maxDevices: 0, + maxAssets: 0, + maxTransportMessages: 0, + maxTransportDataPoints: 0, + maxREExecutions: 0, + maxJSExecutions: 0, + maxDPStorageDays: 0, + maxRuleNodeExecutionsPerMessage: 0 + }; + configuration = {...defaultConfiguration, type: TenantProfileType.DEFAULT}; + break; + } + } + return configuration; +} + export interface TenantProfileData { - [key: string]: string; + configuration: TenantProfileConfiguration; } export interface TenantProfile extends BaseData { diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 082c2241b4..ca88616110 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1923,6 +1923,7 @@ "name": "Name", "name-required": "Name is required.", "data": "Profile data", + "profile-configuration": "Profile configuration", "description": "Description", "default": "Default", "delete-tenant-profile-title": "Are you sure you want to delete the tenant profile '{{tenantProfileName}}'?", @@ -1932,7 +1933,37 @@ "set-default-tenant-profile-title": "Are you sure you want to make the tenant profile '{{tenantProfileName}}' default?", "set-default-tenant-profile-text": "After the confirmation the tenant profile will be marked as default and will be used for new tenants with no profile specified.", "no-tenant-profiles-found": "No tenant profiles found.", - "create-new-tenant-profile": "Create a new one!" + "create-new-tenant-profile": "Create a new one!", + "maximum-devices": "Maximum number of devices (0 - unlimited)", + "maximum-devices-required": "Maximum number of devices is required.", + "maximum-devices-range": "Minimum number of devices can't be negative", + "maximum-assets": "Maximum number of assets (0 - unlimited)", + "maximum-assets-required": "Maximum number of assets is required.", + "maximum-assets-range": "Maximum number of assets can't be negative", + "transport-tenant-msg-rate-limit": "Transport tenant messages rate limit.", + "transport-tenant-telemetry-msg-rate-limit": "Transport tenant telemetry messages rate limit.", + "transport-tenant-telemetry-data-points-rate-limit": "Transport tenant telemetry data points rate limit.", + "transport-device-msg-rate-limit": "Transport device messages rate limit.", + "transport-device-telemetry-msg-rate-limit": "Transport device telemetry messages rate limit.", + "transport-device-telemetry-data-points-rate-limit": "Transport device telemetry data points rate limit.", + "max-transport-messages": "Maximum number of transport messages (0 - unlimited)", + "max-transport-messages-required": "Maximum number of transport messages is required.", + "max-transport-messages-range": "Maximum number of transport messages can't be negative", + "max-transport-data-points": "Maximum number of transport data points (0 - unlimited)", + "max-transport-data-points-required": "Maximum number of transport data points is required.", + "max-transport-data-points-range": "Minimum number of transport data points can't be negative", + "max-r-e-executions": "Maximum number of Rule Engine executions (0 - unlimited)", + "max-r-e-executions-required": "Maximum number of Rule Engine executions is required.", + "max-r-e-executions-range": "Minimum number of Rule Engine executions can't be negative", + "max-j-s-executions": "Maximum number of JavaScript executions (0 - unlimited)", + "max-j-s-executions-required": "Maximum number of JavaScript executions is required.", + "max-j-s-executions-range": "Minimum number of JavaScript executions can't be negative", + "max-d-p-storage-days": "Maximum number of data points storage days (0 - unlimited)", + "max-d-p-storage-days-required": "Maximum number of data points storage days is required.", + "max-d-p-storage-days-range": "Minimum number of data points storage days can't be negative", + "max-rule-node-executions-per-message": "Maximum number of rule node executions per message (0 - unlimited)", + "max-rule-node-executions-per-message-required": "Maximum number of rule node executions per message is required.", + "max-rule-node-executions-per-message-range": "Minimum number of rule node executions per message can't be negative" }, "timeinterval": { "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", From 0e0ab6ca3982d7e20c47267e1967010ad9e607f7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 22 Oct 2020 15:56:31 +0300 Subject: [PATCH 022/102] Improvements to API State --- .../server/service/apiusage/ApiFeature.java | 5 + .../DefaultTbApiUsageStateService.java | 92 ++++++++++++++----- .../apiusage/TbApiUsageStateService.java | 7 +- .../service/apiusage/TenantApiUsageState.java | 43 ++++++--- .../queue/DefaultTbClusterService.java | 27 ++++-- .../service/queue/TbClusterService.java | 3 + .../processing/AbstractConsumerService.java | 4 +- .../transport/DefaultTransportApiService.java | 22 +++-- .../server/common/data/ApiUsageRecordKey.java | 1 - .../server/common/data/ApiUsageState.java | 30 ++++-- .../MultipleTbQueueCallbackWrapper.java | 45 +++++++++ common/queue/src/main/proto/queue.proto | 1 + .../transport/mqtt/MqttTransportHandler.java | 9 +- .../common/transport/TransportService.java | 4 +- .../DefaultTransportRateLimitService.java | 11 ++- .../limits/TransportRateLimitService.java | 1 + .../limits/TransportRateLimitType.java | 3 +- .../service/DefaultTransportService.java | 17 ++-- .../DefaultTransportTenantProfileCache.java | 26 ++++-- .../server/dao/model/ModelConstants.java | 4 + .../dao/model/sql/ApiUsageStateEntity.java | 19 ++++ ...mpl.java => ApiUsageStateServiceImpl.java} | 4 +- .../resources/sql/schema-entities-hsql.sql | 4 + .../main/resources/sql/schema-entities.sql | 4 + .../service/BaseApiUsageStateServiceTest.java | 13 +++ 25 files changed, 310 insertions(+), 89 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java create mode 100644 common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java rename dao/src/main/java/org/thingsboard/server/dao/usagerecord/{ApiApiUsageStateServiceImpl.java => ApiUsageStateServiceImpl.java} (96%) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java new file mode 100644 index 0000000000..a893a55873 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java @@ -0,0 +1,5 @@ +package org.thingsboard.server.service.apiusage; + +public enum ApiFeature { + TRANSPORT, DB, RE, JS +} diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index df7e5013d2..c5e6cab16d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.apiusage; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; @@ -33,15 +34,18 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; +import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.service.queue.TbClusterService; import javax.annotation.PostConstruct; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; @@ -57,12 +61,17 @@ import java.util.concurrent.locks.ReentrantLock; public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "HOURLY_"; + private final TbClusterService clusterService; private final PartitionService partitionService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; - private final Map tenantStates = new ConcurrentHashMap<>(); + + // Tenants that should be processed on this server + private final Map myTenantStates = new ConcurrentHashMap<>(); + // Tenants that should be processed on other servers + private final Map otherTenantStates = new ConcurrentHashMap<>(); @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -72,7 +81,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private final Lock updateLock = new ReentrantLock(); - public DefaultTbApiUsageStateService(PartitionService partitionService, ApiUsageStateService apiUsageStateService, TimeseriesService tsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { + public DefaultTbApiUsageStateService(TbClusterService clusterService, + PartitionService partitionService, + ApiUsageStateService apiUsageStateService, + TimeseriesService tsService, + SchedulerComponent scheduler, + TbTenantProfileCache tenantProfileCache) { + this.clusterService = clusterService; this.partitionService = partitionService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; @@ -93,7 +108,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantApiUsageState tenantState; List updatedEntries; - boolean stateUpdated = false; + Map result = new HashMap<>(); updateLock.lock(); try { tenantState = getOrFetchState(tenantId); @@ -110,32 +125,49 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue))); - stateUpdated |= tenantState.checkStateUpdatedDueToThreshold(recordKey); + Pair update = tenantState.checkStateUpdatedDueToThreshold(recordKey); + if (update != null) { + result.put(update.getFirst(), update.getSecond()); + } } } finally { updateLock.unlock(); } tsService.save(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L); - if (stateUpdated) { - // Save new state into the database; - apiUsageStateService.update(tenantState.getApiUsageState()); - //TODO: clear cache on cluster repartition. - //TODO: update profiles on tenant and profile updates. - //TODO: broadcast to everyone notifications about enabled/disabled features. + if (!result.isEmpty()) { + persistAndNotify(tenantState, result); } } @Override public void onApplicationEvent(PartitionChangeEvent partitionChangeEvent) { if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { - tenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); } } @Override - public TenantApiUsageState getApiUsageState(TenantId tenantId) { - //We should always get it from the map of from the database; - return null; + public ApiUsageState getApiUsageState(TenantId tenantId) { + if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { + TenantApiUsageState state = getOrFetchState(tenantId); + return state.getApiUsageState(); + } else { + ApiUsageState state = otherTenantStates.get(tenantId); + if (state == null) { + updateLock.lock(); + try { + state = otherTenantStates.get(tenantId); + if (state == null) { + state = apiUsageStateService.findTenantApiUsageState(tenantId); + otherTenantStates.put(tenantId, state); + } + } finally { + updateLock.unlock(); + } + } + return state; + } } @Override @@ -143,7 +175,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); updateLock.lock(); try { - tenantStates.values().forEach(state -> { + myTenantStates.values().forEach(state -> { if (tenantProfile.getId().equals(state.getTenantProfileId())) { updateTenantState(state, tenantProfile); } @@ -158,7 +190,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); updateLock.lock(); try { - TenantApiUsageState state = tenantStates.get(tenantId); + TenantApiUsageState state = myTenantStates.get(tenantId); if (state != null && !state.getTenantProfileId().equals(tenantProfile.getId())) { updateTenantState(state, tenantProfile); } @@ -167,12 +199,28 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } + @Override + public void onApiUsageStateUpdate(TenantId tenantId) { + + } private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { state.setTenantProfileData(tenantProfile.getProfileData()); - if (state.checkStateUpdatedDueToThresholds()) { - apiUsageStateService.update(state.getApiUsageState()); - //TODO: send notification to cluster; + Map result = state.checkStateUpdatedDueToThresholds(); + if (!result.isEmpty()) { + persistAndNotify(state, result); + } + } + + private void persistAndNotify(TenantApiUsageState state, Map result) { + // TODO: + // 1. Broadcast to everyone notifications about enabled/disabled features. + // 2. Report rule engine and js executor metrics + // 4. UI for configuration of the thresholds + // 5. Max rule node executions per message. + apiUsageStateService.update(state.getApiUsageState()); + if (result.containsKey(ApiFeature.TRANSPORT)) { + clusterService.onApiStateChange(state.getApiUsageState(), null); } } @@ -180,7 +228,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { updateLock.lock(); try { long now = System.currentTimeMillis(); - tenantStates.values().forEach(state -> { + myTenantStates.values().forEach(state -> { if ((state.getNextCycleTs() > now) && (state.getNextCycleTs() - now < TimeUnit.HOURS.toMillis(1))) { state.setCycles(state.getNextCycleTs(), SchedulerUtils.getStartOfNextNextMonth()); } @@ -191,7 +239,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } private TenantApiUsageState getOrFetchState(TenantId tenantId) { - TenantApiUsageState tenantState = tenantStates.get(tenantId); + TenantApiUsageState tenantState = myTenantStates.get(tenantId); if (tenantState == null) { ApiUsageState dbStateEntity = apiUsageStateService.findTenantApiUsageState(tenantId); if (dbStateEntity == null) { @@ -223,7 +271,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } } - tenantStates.put(tenantId, tenantState); + myTenantStates.put(tenantId, tenantState); } catch (InterruptedException | ExecutionException e) { log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index 4816dc1037..e19c0a4154 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -16,6 +16,7 @@ package org.thingsboard.server.service.apiusage; import org.springframework.context.ApplicationListener; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; @@ -27,9 +28,11 @@ public interface TbApiUsageStateService extends ApplicationListener msg, TbCallback callback); - TenantApiUsageState getApiUsageState(TenantId tenantId); + ApiUsageState getApiUsageState(TenantId tenantId); void onTenantProfileUpdate(TenantProfileId tenantProfileId); void onTenantUpdate(TenantId tenantId); + + void onApiUsageStateUpdate(TenantId tenantId); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 0492ba9793..71bc3f1f6d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,14 +17,17 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import lombok.Setter; +import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import java.util.HashMap; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; @@ -103,11 +106,17 @@ public class TenantApiUsageState { return Long.parseLong((String) threshold); } else if (threshold instanceof Long) { return (Long) threshold; + } else if (threshold instanceof Integer) { + return (Integer) threshold; } } return 0L; } + public TenantId getTenantId() { + return apiUsageState.getTenantId(); + } + public EntityId getEntityId() { return apiUsageState.getEntityId(); } @@ -121,7 +130,7 @@ public class TenantApiUsageState { } public boolean isRuleEngineEnabled() { - return apiUsageState.isRuleEngineEnabled(); + return apiUsageState.isReExecEnabled(); } public boolean isJsExecEnabled() { @@ -137,7 +146,7 @@ public class TenantApiUsageState { } public void setRuleEngineEnabled(boolean ruleEngineEnabled) { - apiUsageState.setRuleEngineEnabled(ruleEngineEnabled); + apiUsageState.setReExecEnabled(ruleEngineEnabled); } public void setJsExecEnabled(boolean jsExecEnabled) { @@ -147,7 +156,6 @@ public class TenantApiUsageState { public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { switch (recordKey) { case MSG_COUNT: - case MSG_BYTES_COUNT: case DP_TRANSPORT_COUNT: return isTransportEnabled(); case RE_EXEC_COUNT: @@ -161,38 +169,47 @@ public class TenantApiUsageState { } } - public boolean setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { + public ApiFeature setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { + ApiFeature feature = null; boolean currentValue = isFeatureEnabled(recordKey); switch (recordKey) { case MSG_COUNT: - case MSG_BYTES_COUNT: case DP_TRANSPORT_COUNT: + feature = ApiFeature.TRANSPORT; setTransportEnabled(value); break; case RE_EXEC_COUNT: + feature = ApiFeature.RE; setRuleEngineEnabled(value); break; case DP_STORAGE_COUNT: + feature = ApiFeature.DB; setDbStorageEnabled(value); break; case JS_EXEC_COUNT: + feature = ApiFeature.JS; setJsExecEnabled(value); break; } - return currentValue == value; + return currentValue == value ? null : feature; } - public boolean checkStateUpdatedDueToThresholds() { - boolean update = false; + public Map checkStateUpdatedDueToThresholds() { + Map result = new HashMap<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - update |= checkStateUpdatedDueToThreshold(key); + Pair featureUpdate = checkStateUpdatedDueToThreshold(key); + if (featureUpdate != null) { + result.put(featureUpdate.getFirst(), featureUpdate.getSecond()); + } } - return update; + return result; } - public boolean checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { + public Pair checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { long value = get(recordKey); long threshold = getProfileThreshold(recordKey); - return setFeatureValue(recordKey, threshold == 0 || value < threshold); + boolean featureValue = threshold == 0 || value < threshold; + ApiFeature feature = setFeatureValue(recordKey, featureValue); + return feature != null ? Pair.of(feature, featureValue) : null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 9451b58e9f..b26baf8d2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -21,6 +21,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; @@ -47,6 +48,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotifica import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.MultipleTbQueueCallbackWrapper; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; @@ -206,6 +208,12 @@ public class DefaultTbClusterService implements TbClusterService { onEntityChange(TenantId.SYS_TENANT_ID, tenant.getId(), tenant, callback); } + @Override + public void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback) { + onEntityChange(apiUsageState.getTenantId(), apiUsageState.getId(), apiUsageState, callback); + broadcast(new ComponentLifecycleMsg(apiUsageState.getTenantId(), apiUsageState.getId(), ComponentLifecycleEvent.UPDATED)); + } + @Override public void onDeviceProfileDelete(DeviceProfile entity, TbQueueCallback callback) { onEntityDelete(entity.getTenantId(), entity.getId(), entity.getName(), callback); @@ -221,13 +229,14 @@ public class DefaultTbClusterService implements TbClusterService { onEntityDelete(TenantId.SYS_TENANT_ID, entity.getId(), entity.getName(), callback); } - public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { - log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entity.getName()); + public void onEntityChange(TenantId tenantId, EntityId entityid, T entity, TbQueueCallback callback) { + String entityName = (entity instanceof HasName) ? ((HasName) entity).getName() : entity.getClass().getName(); + log.trace("[{}][{}][{}] Processing [{}] change event", tenantId, entityid.getEntityType(), entityid.getId(), entityName); TransportProtos.EntityUpdateMsg entityUpdateMsg = TransportProtos.EntityUpdateMsg.newBuilder() .setEntityType(entityid.getEntityType().name()) .setData(ByteString.copyFrom(encodingService.encode(entity))).build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityUpdateMsg(entityUpdateMsg).build(); - broadcast(transportMsg); + broadcast(transportMsg, callback); } private void onEntityDelete(TenantId tenantId, EntityId entityId, String name, TbQueueCallback callback) { @@ -238,15 +247,16 @@ public class DefaultTbClusterService implements TbClusterService { .setEntityIdLSB(entityId.getId().getLeastSignificantBits()) .build(); ToTransportMsg transportMsg = ToTransportMsg.newBuilder().setEntityDeleteMsg(entityDeleteMsg).build(); - broadcast(transportMsg); + broadcast(transportMsg, callback); } - private void broadcast(ToTransportMsg transportMsg) { + private void broadcast(ToTransportMsg transportMsg, TbQueueCallback callback) { TbQueueProducer> toTransportNfProducer = producerProvider.getTransportNotificationsMsgProducer(); Set tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT); + TbQueueCallback proxyCallback = callback != null ? new MultipleTbQueueCallbackWrapper(tbTransportServices.size(), callback) : null; for (String transportServiceId : tbTransportServices) { TopicPartitionInfo tpi = partitionService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId); - toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null); + toTransportNfProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), proxyCallback); toTransportNfs.incrementAndGet(); } } @@ -256,7 +266,8 @@ public class DefaultTbClusterService implements TbClusterService { TbQueueProducer> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); Set tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) - || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE)) { + || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE) + || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { TbQueueProducer> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); Set tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); for (String serviceId : tbCoreServices) { diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java index 838dbe0eef..b36af16716 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbClusterService.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.queue; import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; @@ -63,4 +64,6 @@ public interface TbClusterService { void onTenantChange(Tenant tenant, TbQueueCallback callback); void onTenantDelete(Tenant tenant, TbQueueCallback callback); + + void onApiStateChange(ApiUsageState apiUsageState, TbQueueCallback callback); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 0532d2f9c8..02fe877cd0 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -161,6 +161,8 @@ public abstract class AbstractConsumerService deviceCreationLocks = new ConcurrentHashMap<>(); public DefaultTransportApiService(TbDeviceProfileCache deviceProfileCache, - TbTenantProfileCache tenantProfileCache, DeviceService deviceService, + TbTenantProfileCache tenantProfileCache, TbApiUsageStateService apiUsageStateService, DeviceService deviceService, RelationService relationService, DeviceCredentialsService deviceCredentialsService, DeviceStateService deviceStateService, DbCallbackExecutorService dbCallbackExecutorService, TbClusterService tbClusterService, DataDecodingEncodingService dataDecodingEncodingService, DeviceProvisionService deviceProvisionService) { this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; + this.apiUsageStateService = apiUsageStateService; this.deviceService = deviceService; this.relationService = relationService; this.deviceCredentialsService = deviceCredentialsService; @@ -316,18 +319,21 @@ public class DefaultTransportApiService implements TransportApiService { private ListenableFuture handle(GetEntityProfileRequestMsg requestMsg) { EntityType entityType = EntityType.valueOf(requestMsg.getEntityType()); UUID entityUuid = new UUID(requestMsg.getEntityIdMSB(), requestMsg.getEntityIdLSB()); - ByteString data; + GetEntityProfileResponseMsg.Builder builder = GetEntityProfileResponseMsg.newBuilder(); if (entityType.equals(EntityType.DEVICE_PROFILE)) { DeviceProfileId deviceProfileId = new DeviceProfileId(entityUuid); DeviceProfile deviceProfile = deviceProfileCache.find(deviceProfileId); - data = ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile)); + builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile))); } else if (entityType.equals(EntityType.TENANT)) { - TenantProfile tenantProfile = tenantProfileCache.get(new TenantId(entityUuid)); - data = ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile)); + TenantId tenantId = new TenantId(entityUuid); + TenantProfile tenantProfile = tenantProfileCache.get(tenantId); + ApiUsageState state = apiUsageStateService.getApiUsageState(tenantId); + builder.setData(ByteString.copyFrom(dataDecodingEncodingService.encode(tenantProfile))); + builder.setApiState(ByteString.copyFrom(dataDecodingEncodingService.encode(state))); } else { throw new RuntimeException("Invalid entity profile request: " + entityType); } - return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(GetEntityProfileResponseMsg.newBuilder().setData(data).build()).build()); + return Futures.immediateFuture(TransportApiResponseMsg.newBuilder().setEntityProfileResponseMsg(builder).build()); } private ListenableFuture getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index 9c8690a88f..bc730e7e5a 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -18,7 +18,6 @@ package org.thingsboard.server.common.data; public enum ApiUsageRecordKey { MSG_COUNT, - MSG_BYTES_COUNT, DP_TRANSPORT_COUNT, DP_STORAGE_COUNT, RE_EXEC_COUNT, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 05186c7599..5144861790 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -29,18 +29,24 @@ public class ApiUsageState extends BaseData implements HasTenan private static final long serialVersionUID = 8250339805336035966L; - @Getter @Setter + @Getter + @Setter private TenantId tenantId; - @Getter @Setter + @Getter + @Setter private EntityId entityId; - @Getter @Setter - private boolean transportEnabled; - @Getter @Setter - private boolean dbStorageEnabled; - @Getter @Setter - private boolean ruleEngineEnabled; - @Getter @Setter - private boolean jsExecEnabled; + @Getter + @Setter + private boolean transportEnabled = true; + @Getter + @Setter + private boolean dbStorageEnabled = true; + @Getter + @Setter + private boolean reExecEnabled = true; + @Getter + @Setter + private boolean jsExecEnabled = true; public ApiUsageState() { super(); @@ -54,5 +60,9 @@ public class ApiUsageState extends BaseData implements HasTenan super(ur); this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); + this.transportEnabled = ur.isTransportEnabled(); + this.dbStorageEnabled = ur.isDbStorageEnabled(); + this.reExecEnabled = ur.isReExecEnabled(); + this.jsExecEnabled = ur.isJsExecEnabled(); } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java new file mode 100644 index 0000000000..ddedb3a606 --- /dev/null +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java @@ -0,0 +1,45 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.queue.common; + +import org.thingsboard.server.common.msg.queue.RuleEngineException; +import org.thingsboard.server.queue.TbQueueCallback; +import org.thingsboard.server.queue.TbQueueMsgMetadata; + +import java.util.concurrent.atomic.AtomicInteger; + +public class MultipleTbQueueCallbackWrapper implements TbQueueCallback { + + private final AtomicInteger tbQueueCallbackCount; + private final TbQueueCallback callback; + + public MultipleTbQueueCallbackWrapper(int tbQueueCallbackCount, TbQueueCallback callback) { + this.tbQueueCallbackCount = new AtomicInteger(tbQueueCallbackCount); + this.callback = callback; + } + + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + if (tbQueueCallbackCount.decrementAndGet() <= 0) { + callback.onSuccess(metadata); + } + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(new RuleEngineException(t.getMessage())); + } +} diff --git a/common/queue/src/main/proto/queue.proto b/common/queue/src/main/proto/queue.proto index f716bd90ae..4b5a6028e6 100644 --- a/common/queue/src/main/proto/queue.proto +++ b/common/queue/src/main/proto/queue.proto @@ -186,6 +186,7 @@ message GetEntityProfileRequestMsg { message GetEntityProfileResponseMsg { string entityType = 1; bytes data = 2; + bytes apiState = 3; } message EntityUpdateMsg { diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 2c4b2db7df..e70da7029e 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -45,6 +45,7 @@ import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.MqttTopics; import org.thingsboard.server.common.msg.EncryptionUtil; +import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.transport.SessionMsgListener; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportServiceCallback; @@ -630,7 +631,11 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void onError(Throwable e) { - log.warn("[{}] Failed to submit session event", sessionId, e); + if (e instanceof TbRateLimitsException) { + log.trace("[{}] Failed to submit session event", sessionId, e); + } else { + log.warn("[{}] Failed to submit session event", sessionId, e); + } ctx.writeAndFlush(createMqttConnAckMsg(MqttConnectReturnCode.CONNECTION_REFUSED_SERVER_UNAVAILABLE)); ctx.close(); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 4872d8e477..3b775b9f26 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -45,7 +45,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509Ce */ public interface TransportService { - GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg); + GetEntityProfileResponseMsg getEntityProfile(GetEntityProfileRequestMsg msg); void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback); @@ -62,8 +62,6 @@ public interface TransportService { void process(ProvisionDeviceRequestMsg msg, TransportServiceCallback callback); - void onProfileUpdate(DeviceProfile deviceProfile); - boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index 569f5446db..9663ae2e47 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -33,6 +33,7 @@ import java.util.concurrent.ConcurrentMap; @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { + private final ConcurrentMap tenantAllowed = new ConcurrentHashMap<>(); private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); @@ -46,6 +47,9 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @Override public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { + if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) { + return TransportRateLimitType.TENANT_ADDED_TO_DISABLED_LIST; + } TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); for (TransportRateLimitType limitType : limits) { @@ -85,6 +89,11 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi perDeviceLimits.remove(deviceId); } + @Override + public void update(TenantId tenantId, boolean allowed) { + tenantAllowed.put(tenantId, allowed); + } + private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); if (oldRateLimits == null) { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java index a97fbfc61d..2ab1ec38ac 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -31,4 +31,5 @@ public interface TransportRateLimitService { void remove(DeviceId deviceId); + void update(TenantId tenantId, boolean transportEnabled); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java index a3e6da6683..038de86e84 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -19,6 +19,7 @@ import lombok.Getter; public enum TransportRateLimitType { + TENANT_ADDED_TO_DISABLED_LIST("general.tenant.disabled", true, false), TENANT_MAX_MSGS("transport.tenant.msg", true, true), TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 010a3cd899..326f996d14 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -26,6 +26,7 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.EntityType; @@ -237,7 +238,7 @@ public class DefaultTransportService implements TransportService { } @Override - public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { + public TransportProtos.GetEntityProfileResponseMsg getEntityProfile(TransportProtos.GetEntityProfileRequestMsg msg) { TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { @@ -641,8 +642,7 @@ public class DefaultTransportService implements TransportService { onProfileUpdate(deviceProfile); } } else if (EntityType.TENANT_PROFILE.equals(entityType)) { - TenantProfileUpdateResult update = tenantProfileCache.put(msg.getData()); - rateLimitService.update(update); + rateLimitService.update(tenantProfileCache.put(msg.getData())); } else if (EntityType.TENANT.equals(entityType)) { Optional profileOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); if (profileOpt.isPresent()) { @@ -652,6 +652,12 @@ public class DefaultTransportService implements TransportService { rateLimitService.update(tenant.getId()); } } + } else if (EntityType.API_USAGE_STATE.equals(entityType)) { + Optional stateOpt = dataDecodingEncodingService.decode(msg.getData().toByteArray()); + if (stateOpt.isPresent()) { + ApiUsageState apiUsageState = stateOpt.get(); + rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); + } } } else if (toSessionMsg.hasEntityDeleteMsg()) { TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); @@ -673,8 +679,7 @@ public class DefaultTransportService implements TransportService { } } - @Override - public void onProfileUpdate(DeviceProfile deviceProfile) { + private void onProfileUpdate(DeviceProfile deviceProfile) { long deviceProfileIdMSB = deviceProfile.getId().getId().getMostSignificantBits(); long deviceProfileIdLSB = deviceProfile.getId().getId().getLeastSignificantBits(); sessions.forEach((id, md) -> { diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 784f7b3ae8..18c1512a27 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -18,21 +18,19 @@ package org.thingsboard.server.common.transport.service; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportTenantProfileCache; +import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.TenantRoutingInfo; -import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; import org.thingsboard.server.queue.util.TbTransportComponent; import java.util.Collections; @@ -54,8 +52,15 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil private final ConcurrentMap> tenantProfileIds = new ConcurrentHashMap<>(); private final DataDecodingEncodingService dataDecodingEncodingService; + private TransportRateLimitService rateLimitService; private TransportService transportService; + @Lazy + @Autowired + public void setRateLimitService(TransportRateLimitService rateLimitService) { + this.rateLimitService = rateLimitService; + } + @Lazy @Autowired public void setTransportService(TransportService transportService) { @@ -77,7 +82,8 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil if (profileOpt.isPresent()) { TenantProfile newProfile = profileOpt.get(); log.trace("[{}] put: {}", newProfile.getId(), newProfile); - return new TenantProfileUpdateResult(newProfile, tenantProfileIds.get(newProfile.getId())); + Set affectedTenants = tenantProfileIds.get(newProfile.getId()); + return new TenantProfileUpdateResult(newProfile, affectedTenants != null ? affectedTenants : Collections.emptySet()); } else { log.warn("Failed to decode profile: {}", profileBody.toString()); return new TenantProfileUpdateResult(null, Collections.emptySet()); @@ -127,8 +133,8 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil .setEntityIdMSB(tenantId.getId().getMostSignificantBits()) .setEntityIdLSB(tenantId.getId().getLeastSignificantBits()) .build(); - TransportProtos.GetEntityProfileResponseMsg routingInfo = transportService.getRoutingInfo(msg); - Optional profileOpt = dataDecodingEncodingService.decode(routingInfo.getData().toByteArray()); + TransportProtos.GetEntityProfileResponseMsg entityProfileMsg = transportService.getEntityProfile(msg); + Optional profileOpt = dataDecodingEncodingService.decode(entityProfileMsg.getData().toByteArray()); if (profileOpt.isPresent()) { profile = profileOpt.get(); TenantProfile existingProfile = profiles.get(profile.getId()); @@ -140,9 +146,11 @@ public class DefaultTransportTenantProfileCache implements TransportTenantProfil tenantProfileIds.computeIfAbsent(profile.getId(), id -> ConcurrentHashMap.newKeySet()).add(tenantId); tenantIds.put(tenantId, profile.getId()); } else { - log.warn("[{}] Can't decode tenant profile: {}", tenantId, routingInfo.getData()); + log.warn("[{}] Can't decode tenant profile: {}", tenantId, entityProfileMsg.getData()); throw new RuntimeException("Can't decode tenant profile!"); } + Optional apiStateOpt = dataDecodingEncodingService.decode(entityProfileMsg.getApiState().toByteArray()); + apiStateOpt.ifPresent(apiUsageState -> rateLimitService.update(tenantId, apiUsageState.isTransportEnabled())); } } finally { tenantProfileFetchLock.unlock(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index a0f5b45f02..a370fbe7f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -445,6 +445,10 @@ public class ModelConstants { public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; + public static final String API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN = "transport_enabled"; + public static final String API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN = "db_storage_enabled"; + public static final String API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN = "re_exec_enabled"; + public static final String API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN = "js_exec_enabled"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index a06ec142d3..262252517a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -17,6 +17,8 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -51,6 +53,15 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN) private UUID entityId; + @Column(name = ModelConstants.API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN) + private boolean transportEnabled = true; + @Column(name = ModelConstants.API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN) + private boolean dbStorageEnabled = true; + @Column(name = ModelConstants.API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN) + private boolean reExecEnabled = true; + @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN) + private boolean jsExecEnabled = true; + public ApiUsageStateEntity() { } @@ -66,6 +77,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.entityType = ur.getEntityId().getEntityType().name(); this.entityId = ur.getEntityId().getId(); } + this.transportEnabled = ur.isTransportEnabled(); + this.dbStorageEnabled = ur.isDbStorageEnabled(); + this.reExecEnabled = ur.isReExecEnabled(); + this.jsExecEnabled = ur.isJsExecEnabled(); } @Override @@ -78,6 +93,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements if (entityId != null) { ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); } + ur.setTransportEnabled(transportEnabled); + ur.setDbStorageEnabled(dbStorageEnabled); + ur.setReExecEnabled(reExecEnabled); + ur.setJsExecEnabled(jsExecEnabled); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java similarity index 96% rename from dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java rename to dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index e59c990346..15be73a0b8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -31,13 +31,13 @@ import static org.thingsboard.server.dao.service.Validator.validateId; @Service @Slf4j -public class ApiApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { +public class ApiUsageStateServiceImpl extends AbstractEntityService implements ApiUsageStateService { public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private final ApiUsageStateDao apiUsageStateDao; private final TenantDao tenantDao; - public ApiApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { + public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { this.tenantDao = tenantDao; this.apiUsageStateDao = apiUsageStateDao; } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index c671cd2e19..1d82e2d493 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -411,5 +411,9 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, + transport_enabled boolean, + db_storage_enabled boolean, + re_exec_enabled boolean, + js_exec_enabled boolean, CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index d8539d9af9..c26fce6080 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -437,6 +437,10 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, + transport_enabled boolean, + db_storage_enabled boolean, + re_exec_enabled boolean, + js_exec_enabled boolean, CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java index 5f56506997..a33e190a03 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java @@ -48,4 +48,17 @@ public abstract class BaseApiUsageStateServiceTest extends AbstractServiceTest { Assert.assertNotNull(apiUsageState); } + @Test + public void testUpdateApiUsageState(){ + ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); + Assert.assertNotNull(apiUsageState); + Assert.assertTrue(apiUsageState.isTransportEnabled()); + apiUsageState.setTransportEnabled(false); + apiUsageState = apiUsageStateService.update(apiUsageState); + Assert.assertNotNull(apiUsageState); + apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); + Assert.assertNotNull(apiUsageState); + Assert.assertFalse(apiUsageState.isTransportEnabled()); + } + } From 2ea2b57f3c4debfbb1cff09dec4c3ec25b3b1d39 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 8 Sep 2020 11:06:57 +0300 Subject: [PATCH 023/102] Top 5 rule nodes statistics (cherry picked from commit 445350b1f800b90193f50ee38556e5187acff9e7) --- .../actors/ruleChain/DefaultTbContext.java | 2 + .../RuleNodeActorMessageProcessor.java | 2 +- .../DefaultTbRuleEngineConsumerService.java | 4 +- .../service/queue/TbMsgPackCallback.java | 12 ++- .../queue/TbMsgPackProcessingContext.java | 60 ++++++++++++- .../service/queue/TbMsgProfilerInfo.java | 85 +++++++++++++++++++ .../service/queue/TbRuleNodeProfilerInfo.java | 75 ++++++++++++++++ .../BatchTbRuleEngineSubmitStrategy.java | 4 +- .../queue/TbMsgPackProcessingContextTest.java | 2 +- .../server/common/msg/queue/RuleNodeInfo.java | 4 + .../common/msg/queue/TbMsgCallback.java | 8 +- 11 files changed, 247 insertions(+), 11 deletions(-) create mode 100644 application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java create mode 100644 application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index e89daf4516..30747e8f1b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -105,6 +105,7 @@ class DefaultTbContext implements TbContext { if (nodeCtx.getSelf().isDebugMode()) { relationTypes.forEach(relationType -> mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, relationType, th)); } + msg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId()); nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), relationTypes, msg, th != null ? th.getMessage() : null)); } @@ -214,6 +215,7 @@ class DefaultTbContext implements TbContext { if (nodeCtx.getSelf().isDebugMode()) { mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), tbMsg, "ACK", null); } + tbMsg.getCallback().onProcessingEnd(nodeCtx.getSelf().getId()); tbMsg.getCallback().onSuccess(); } diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java index 7583ea553e..db55ff8edf 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java @@ -103,7 +103,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor submitExecutor.submit(() -> { log.trace("[{}] Creating callback for message: {}", id, msg.getValue()); ToRuleEngineMsg toRuleEngineMsg = msg.getValue(); @@ -194,6 +194,8 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService< if (!ctx.getFailedMap().isEmpty()) { printFirstOrAll(configuration, ctx, ctx.getFailedMap(), "Failed"); } + ctx.printProfilerStats(); + TbRuleEngineProcessingDecision decision = ackStrategy.analyze(result); if (statsEnabled) { stats.log(result, decision.isCommit()); diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java index c7925fd759..5712f29eac 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackCallback.java @@ -49,8 +49,14 @@ public class TbMsgPackCallback implements TbMsgCallback { } @Override - public void visit(RuleNodeInfo ruleNodeInfo) { - log.trace("[{}] ON PROCESS: {}", id, ruleNodeInfo); - ctx.visit(id, ruleNodeInfo); + public void onProcessingStart(RuleNodeInfo ruleNodeInfo) { + log.trace("[{}] ON PROCESSING START: {}", id, ruleNodeInfo); + ctx.onProcessingStart(id, ruleNodeInfo); + } + + @Override + public void onProcessingEnd(RuleNodeId ruleNodeId) { + log.trace("[{}] ON PROCESSING END: {}", id, ruleNodeId); + ctx.onProcessingEnd(id, ruleNodeId); } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java index 1b7ffd9c30..c85544ce81 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.queue; import lombok.Getter; +import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.RuleEngineException; @@ -24,6 +25,8 @@ import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; +import java.util.Comparator; +import java.util.Map; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; @@ -31,9 +34,13 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; +@Slf4j public class TbMsgPackProcessingContext { + private final String queueName; private final TbRuleEngineSubmitStrategy submitStrategy; + @Getter + private final boolean profilerEnabled; private final AtomicInteger pendingCount; private final CountDownLatch processingTimeoutLatch = new CountDownLatch(1); @Getter @@ -47,14 +54,20 @@ public class TbMsgPackProcessingContext { private final ConcurrentMap lastRuleNodeMap = new ConcurrentHashMap<>(); - public TbMsgPackProcessingContext(TbRuleEngineSubmitStrategy submitStrategy) { + public TbMsgPackProcessingContext(String queueName, TbRuleEngineSubmitStrategy submitStrategy) { + this.queueName = queueName; this.submitStrategy = submitStrategy; + this.profilerEnabled = log.isDebugEnabled(); this.pendingMap = submitStrategy.getPendingMap(); this.pendingCount = new AtomicInteger(pendingMap.size()); } public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException { - return processingTimeoutLatch.await(packProcessingTimeout, milliseconds); + boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds); + if (!success && profilerEnabled) { + msgProfilerMap.values().forEach(TbMsgProfilerInfo::onTimeout); + } + return success; } public void onSuccess(UUID id) { @@ -85,12 +98,53 @@ public class TbMsgPackProcessingContext { } } - public void visit(UUID id, RuleNodeInfo ruleNodeInfo) { + private final ConcurrentHashMap msgProfilerMap = new ConcurrentHashMap<>(); + private final ConcurrentHashMap ruleNodeProfilerMap = new ConcurrentHashMap<>(); + + public void onProcessingStart(UUID id, RuleNodeInfo ruleNodeInfo) { lastRuleNodeMap.put(id, ruleNodeInfo); + if (profilerEnabled) { + msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onStart(ruleNodeInfo.getRuleNodeId()); + ruleNodeProfilerMap.putIfAbsent(ruleNodeInfo.getRuleNodeId().getId(), new TbRuleNodeProfilerInfo(ruleNodeInfo)); + } + } + + public void onProcessingEnd(UUID id, RuleNodeId ruleNodeId) { + if (profilerEnabled) { + long processingTime = msgProfilerMap.computeIfAbsent(id, TbMsgProfilerInfo::new).onEnd(ruleNodeId); + if (processingTime > 0) { + ruleNodeProfilerMap.computeIfAbsent(ruleNodeId.getId(), TbRuleNodeProfilerInfo::new).record(processingTime); + } + } + } + + public void onTimeout(TbMsgProfilerInfo profilerInfo) { + Map.Entry ruleNodeInfo = profilerInfo.onTimeout(); + if (ruleNodeInfo != null) { + ruleNodeProfilerMap.computeIfAbsent(ruleNodeInfo.getKey(), TbRuleNodeProfilerInfo::new).record(ruleNodeInfo.getValue()); + } } public RuleNodeInfo getLastVisitedRuleNode(UUID id) { return lastRuleNodeMap.get(id); } + public void printProfilerStats() { + if (profilerEnabled) { + log.debug("Top Rule Nodes by max execution time:"); + ruleNodeProfilerMap.values().stream() + .sorted(Comparator.comparingLong(TbRuleNodeProfilerInfo::getMaxExecutionTime).reversed()).limit(5) + .forEach(info -> log.debug("[{}][{}] max execution time: {}. {}", queueName, info.getRuleNodeId(), info.getMaxExecutionTime(), info.getLabel())); + + log.info("Top Rule Nodes by avg execution time:"); + ruleNodeProfilerMap.values().stream() + .sorted(Comparator.comparingDouble(TbRuleNodeProfilerInfo::getAvgExecutionTime).reversed()).limit(5) + .forEach(info -> log.info("[{}][{}] avg execution time: {}. {}", queueName, info.getRuleNodeId(), info.getAvgExecutionTime(), info.getLabel())); + + log.info("Top Rule Nodes by execution count:"); + ruleNodeProfilerMap.values().stream() + .sorted(Comparator.comparingInt(TbRuleNodeProfilerInfo::getExecutionCount).reversed()).limit(5) + .forEach(info -> log.info("[{}][{}] execution count: {}. {}", queueName, info.getRuleNodeId(), info.getExecutionCount(), info.getLabel())); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java new file mode 100644 index 0000000000..f66cd1a50a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgProfilerInfo.java @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.queue; + +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.msg.queue.RuleNodeInfo; + +import java.util.AbstractMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicLong; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +public class TbMsgProfilerInfo { + private final UUID msgId; + private AtomicLong totalProcessingTime = new AtomicLong(); + private Lock stateLock = new ReentrantLock(); + private RuleNodeId currentRuleNodeId; + private long stateChangeTime; + + public TbMsgProfilerInfo(UUID msgId) { + this.msgId = msgId; + } + + public void onStart(RuleNodeId ruleNodeId) { + long currentTime = System.currentTimeMillis(); + stateLock.lock(); + try { + currentRuleNodeId = ruleNodeId; + stateChangeTime = currentTime; + } finally { + stateLock.unlock(); + } + } + + public long onEnd(RuleNodeId ruleNodeId) { + long currentTime = System.currentTimeMillis(); + stateLock.lock(); + try { + if (ruleNodeId.equals(currentRuleNodeId)) { + long processingTime = currentTime - stateChangeTime; + stateChangeTime = currentTime; + totalProcessingTime.addAndGet(processingTime); + currentRuleNodeId = null; + return processingTime; + } else { + log.trace("[{}] Invalid sequence of rule node processing detected. Expected [{}] but was [{}]", msgId, currentRuleNodeId, ruleNodeId); + return 0; + } + } finally { + stateLock.unlock(); + } + } + + public Map.Entry onTimeout() { + long currentTime = System.currentTimeMillis(); + stateLock.lock(); + try { + if (currentRuleNodeId != null && stateChangeTime > 0) { + long timeoutTime = currentTime - stateChangeTime; + totalProcessingTime.addAndGet(timeoutTime); + return new AbstractMap.SimpleEntry<>(currentRuleNodeId.getId(), timeoutTime); + } + } finally { + stateLock.unlock(); + } + return null; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java new file mode 100644 index 0000000000..c88532fbc3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbRuleNodeProfilerInfo.java @@ -0,0 +1,75 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.queue; + +import lombok.Getter; +import org.thingsboard.server.common.msg.queue.RuleNodeInfo; + +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicLong; + +public class TbRuleNodeProfilerInfo { + @Getter + private final UUID ruleNodeId; + @Getter + private final String label; + private AtomicInteger executionCount = new AtomicInteger(0); + private AtomicLong executionTime = new AtomicLong(0); + private AtomicLong maxExecutionTime = new AtomicLong(0); + + public TbRuleNodeProfilerInfo(RuleNodeInfo ruleNodeInfo) { + this.ruleNodeId = ruleNodeInfo.getRuleNodeId().getId(); + this.label = ruleNodeInfo.toString(); + } + + public TbRuleNodeProfilerInfo(UUID ruleNodeId) { + this.ruleNodeId = ruleNodeId; + this.label = ""; + } + + public void record(long processingTime) { + executionCount.incrementAndGet(); + executionTime.addAndGet(processingTime); + while (true) { + long value = maxExecutionTime.get(); + if (value >= processingTime) { + break; + } + if (maxExecutionTime.compareAndSet(value, processingTime)) { + break; + } + } + } + + int getExecutionCount() { + return executionCount.get(); + } + + long getMaxExecutionTime() { + return maxExecutionTime.get(); + } + + double getAvgExecutionTime() { + double executionCnt = (double) executionCount.get(); + if (executionCnt > 0) { + return executionTime.get() / executionCnt; + } else { + return 0.0; + } + } + +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java index b9741d2433..be299b39b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java @@ -68,18 +68,20 @@ public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS int listSize = orderedMsgList.size(); int startIdx = Math.min(packIdx.get() * batchSize, listSize); int endIdx = Math.min(startIdx + batchSize, listSize); + Map> tmpPack; synchronized (pendingPack) { pendingPack.clear(); for (int i = startIdx; i < endIdx; i++) { IdMsgPair pair = orderedMsgList.get(i); pendingPack.put(pair.uuid, pair.msg); } + tmpPack = new LinkedHashMap<>(pendingPack); } int submitSize = pendingPack.size(); if (log.isDebugEnabled() && submitSize > 0) { log.debug("[{}] submitting [{}] messages to rule engine", queueName, submitSize); } - pendingPack.forEach(msgConsumer); + tmpPack.forEach(msgConsumer); } } diff --git a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java index 43cd62ea97..595f64b3a4 100644 --- a/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java +++ b/application/src/test/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContextTest.java @@ -51,7 +51,7 @@ public class TbMsgPackProcessingContextTest { messages.put(UUID.randomUUID(), new TbProtoQueueMsg<>(UUID.randomUUID(), null)); } when(strategyMock.getPendingMap()).thenReturn(messages); - TbMsgPackProcessingContext context = new TbMsgPackProcessingContext(strategyMock); + TbMsgPackProcessingContext context = new TbMsgPackProcessingContext("Main", strategyMock); for (UUID uuid : messages.keySet()) { for (int i = 0; i < parallelCount; i++) { executorService.submit(() -> context.onSuccess(uuid)); diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java index 0c2d38b3b2..9af3f20363 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeInfo.java @@ -15,12 +15,16 @@ */ package org.thingsboard.server.common.msg.queue; +import lombok.Getter; import org.thingsboard.server.common.data.id.RuleNodeId; public class RuleNodeInfo { private final String label; + @Getter + private final RuleNodeId ruleNodeId; public RuleNodeInfo(RuleNodeId id, String ruleChainName, String ruleNodeName) { + this.ruleNodeId = id; this.label = "[RuleChain: " + ruleChainName + "|RuleNode: " + ruleNodeName + "(" + id + ")]"; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java index 4103a4efc1..3f6927adb1 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/queue/TbMsgCallback.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.msg.queue; +import org.thingsboard.server.common.data.id.RuleNodeId; + public interface TbMsgCallback { TbMsgCallback EMPTY = new TbMsgCallback() { @@ -34,7 +36,11 @@ public interface TbMsgCallback { void onFailure(RuleEngineException e); - default void visit(RuleNodeInfo ruleNodeInfo) { + default void onProcessingStart(RuleNodeInfo ruleNodeInfo) { + } + + default void onProcessingEnd(RuleNodeId ruleNodeId) { } + } From 04727e81b433857382655c52b9be005b34bbb88a Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 8 Sep 2020 11:39:12 +0300 Subject: [PATCH 024/102] Detecting rule nodes that cause timeout (cherry picked from commit 35feb2fea43bbb8d39071af63fa6426f25502dd5) --- .../server/service/queue/TbMsgPackProcessingContext.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java index c85544ce81..6d88ed204a 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/TbMsgPackProcessingContext.java @@ -65,7 +65,7 @@ public class TbMsgPackProcessingContext { public boolean await(long packProcessingTimeout, TimeUnit milliseconds) throws InterruptedException { boolean success = processingTimeoutLatch.await(packProcessingTimeout, milliseconds); if (!success && profilerEnabled) { - msgProfilerMap.values().forEach(TbMsgProfilerInfo::onTimeout); + msgProfilerMap.values().forEach(this::onTimeout); } return success; } From b38b47f4093b547502c3e47a72f7f88526e45032 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Thu, 22 Oct 2020 18:51:34 +0300 Subject: [PATCH 025/102] JS Invoke Service stats --- .../actors/ruleChain/DefaultTbContext.java | 2 +- .../controller/RuleChainController.java | 2 +- .../server/service/apiusage/ApiFeature.java | 15 ++ .../service/apiusage/TenantApiUsageState.java | 36 +-- .../queue/DefaultTbClusterService.java | 2 +- .../processing/AbstractConsumerService.java | 11 +- .../script/AbstractJsInvokeService.java | 13 +- .../AbstractNashornJsInvokeService.java | 11 +- .../service/script/JsExecutorService.java | 2 +- .../service/script/JsInvokeService.java | 5 +- .../script/NashornJsInvokeService.java | 5 + .../service/script/RemoteJsInvokeService.java | 5 + .../script/RuleNodeJsScriptEngine.java | 11 +- .../transport/DefaultTransportApiService.java | 2 +- .../script/RuleNodeJsScriptEngineTest.java | 250 ------------------ .../script/TestNashornJsInvokeService.java | 57 ---- .../server/common/data/ApiUsageRecordKey.java | 6 +- .../tenant/profile/TenantProfileData.java | 13 - .../MultipleTbQueueCallbackWrapper.java | 2 +- .../transport/mqtt/MqttTransportHandler.java | 2 +- .../common/transport/TransportService.java | 5 - .../DefaultTransportRateLimitFactory.java | 47 ---- .../DefaultTransportRateLimitService.java | 128 +++++---- ...ry.java => EntityTransportRateLimits.java} | 11 +- .../limits/SimpleTransportRateLimit.java | 5 + .../limits/TransportRateLimitService.java | 3 +- .../limits/TransportRateLimitType.java | 48 ---- .../service/DefaultTransportService.java | 32 +-- .../DefaultTransportTenantProfileCache.java | 2 +- .../dao/tenant/TenantProfileServiceImpl.java | 5 +- .../service/BaseTenantProfileServiceTest.java | 5 +- 31 files changed, 209 insertions(+), 534 deletions(-) delete mode 100644 application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java delete mode 100644 application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java rename common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/{TransportRateLimitFactory.java => EntityTransportRateLimits.java} (70%) delete mode 100644 common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index c8bb083577..e7a54385a7 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -312,7 +312,7 @@ class DefaultTbContext implements TbContext { @Override public ScriptEngine createJsScriptEngine(String script, String... argNames) { - return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); + return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); } @Override diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 6d80c05c05..2ca834d85b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -347,7 +347,7 @@ public class RuleChainController extends BaseController { String errorText = ""; ScriptEngine engine = null; try { - engine = new RuleNodeJsScriptEngine(jsInvokeService, getCurrentUser().getId(), script, argNames); + engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, getCurrentUser().getId(), script, argNames); TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); switch (scriptType) { case "update": diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java index a893a55873..8267d4addd 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java @@ -1,3 +1,18 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ package org.thingsboard.server.service.apiusage; public enum ApiFeature { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 0e68d02b71..5dcfb34ec3 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -21,6 +21,7 @@ import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; @@ -100,15 +101,18 @@ public class TenantApiUsageState { } public long getProfileThreshold(ApiUsageRecordKey key) { - Object threshold = tenantProfileData.getProperties().get(key.name()); - if (threshold != null) { - if (threshold instanceof String) { - return Long.parseLong((String) threshold); - } else if (threshold instanceof Long) { - return (Long) threshold; - } else if (threshold instanceof Integer) { - return (Integer) threshold; - } + DefaultTenantProfileConfiguration config = (DefaultTenantProfileConfiguration) tenantProfileData.getConfiguration(); + switch (key) { + case TRANSPORT_MSG_COUNT: + return config.getMaxTransportMessages(); + case TRANSPORT_DP_COUNT: + return config.getMaxTransportDataPoints(); + case JS_EXEC_COUNT: + return config.getMaxJSExecutions(); + case RE_EXEC_COUNT: + return config.getMaxREExecutions(); + case STORAGE_DP_COUNT: + return config.getMaxDPStorageDays(); } return 0L; } @@ -155,12 +159,12 @@ public class TenantApiUsageState { public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { switch (recordKey) { - case MSG_COUNT: - case DP_TRANSPORT_COUNT: + case TRANSPORT_MSG_COUNT: + case TRANSPORT_DP_COUNT: return isTransportEnabled(); case RE_EXEC_COUNT: return isRuleEngineEnabled(); - case DP_STORAGE_COUNT: + case STORAGE_DP_COUNT: return isDbStorageEnabled(); case JS_EXEC_COUNT: return isJsExecEnabled(); @@ -173,8 +177,8 @@ public class TenantApiUsageState { ApiFeature feature = null; boolean currentValue = isFeatureEnabled(recordKey); switch (recordKey) { - case MSG_COUNT: - case DP_TRANSPORT_COUNT: + case TRANSPORT_MSG_COUNT: + case TRANSPORT_DP_COUNT: feature = ApiFeature.TRANSPORT; setTransportEnabled(value); break; @@ -182,7 +186,7 @@ public class TenantApiUsageState { feature = ApiFeature.RE; setRuleEngineEnabled(value); break; - case DP_STORAGE_COUNT: + case STORAGE_DP_COUNT: feature = ApiFeature.DB; setDbStorageEnabled(value); break; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index b26baf8d2e..f08092d32d 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 02fe877cd0..9a4d488e0f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg; import org.thingsboard.server.common.msg.queue.ServiceType; @@ -153,10 +154,14 @@ public abstract class AbstractConsumerService scriptIdToNameMap = new ConcurrentHashMap<>(); protected Map blackListedFunctions = new ConcurrentHashMap<>(); + protected AbstractJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { + this.apiUsageStatsClient = apiUsageStatsClient; + } + public void init(long maxRequestsTimeout) { if (maxRequestsTimeout > 0) { timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout")); @@ -50,7 +58,7 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { } @Override - public ListenableFuture eval(JsScriptType scriptType, String scriptBody, String... argNames) { + public ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) { UUID scriptId = UUID.randomUUID(); String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); @@ -58,12 +66,13 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { } @Override - public ListenableFuture invokeFunction(UUID scriptId, Object... args) { + public ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args) { String functionName = scriptIdToNameMap.get(scriptId); if (functionName == null) { return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); } if (!isBlackListed(scriptId)) { + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); return doInvokeFunction(scriptId, functionName, args); } else { return Futures.immediateFailedFuture( diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 8e6666bbba..17be0602f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -27,7 +27,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; -import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -38,7 +38,6 @@ import java.util.UUID; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; @@ -57,9 +56,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer private final FutureCallback evalCallback = new JsStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); private final FutureCallback invokeCallback = new JsStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); - @Autowired @Getter - private JsExecutorService jsExecutor; + private final JsExecutorService jsExecutor; @Value("${js.local.max_requests_timeout:0}") private long maxRequestsTimeout; @@ -67,6 +65,11 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer @Value("${js.local.stats.enabled:false}") private boolean statsEnabled; + public AbstractNashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { + super(apiUsageStatsClient); + this.jsExecutor = jsExecutor; + } + @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}") public void printStats() { if (statsEnabled) { diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java index 97e40e6f62..15fd8c7534 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java @@ -27,7 +27,7 @@ public class JsExecutorService extends AbstractListeningExecutor { @Override protected int getThreadPollSize() { - return jsExecutorThreadPoolSize; + return Math.max(jsExecutorThreadPoolSize, 1); } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java index a9f727e4a7..ae990af66c 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java @@ -17,14 +17,15 @@ package org.thingsboard.server.service.script; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import java.util.UUID; public interface JsInvokeService { - ListenableFuture eval(JsScriptType scriptType, String scriptBody, String... argNames); + ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames); - ListenableFuture invokeFunction(UUID scriptId, Object... args); + ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args); ListenableFuture release(UUID scriptId); diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java index 66a14cc827..c20f3e8c08 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import java.util.concurrent.TimeUnit; @@ -42,6 +43,10 @@ public class NashornJsInvokeService extends AbstractNashornJsInvokeService { @Value("${js.local.max_black_list_duration_sec:60}") private int maxBlackListDurationSec; + public NashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { + super(apiUsageStatsClient, jsExecutor); + } + @Override protected boolean useJsSandbox() { return useJsSandbox; diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index cfb0c8a8ee..2ada577287 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -30,6 +30,7 @@ import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -68,6 +69,10 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); + public RemoteJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { + super(apiUsageStatsClient); + } + @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") public void printStats() { if (statsEnabled) { diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 110f410fad..275e645ce2 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -25,6 +25,7 @@ import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -43,13 +44,15 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private final JsInvokeService sandboxService; private final UUID scriptId; + private final TenantId tenantId; private final EntityId entityId; - public RuleNodeJsScriptEngine(JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) { + public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) { + this.tenantId = tenantId; this.sandboxService = sandboxService; this.entityId = entityId; try { - this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get(); + this.scriptId = this.sandboxService.eval(tenantId, JsScriptType.RULE_NODE_SCRIPT, script, argNames).get(); } catch (Exception e) { Throwable t = e; if (e instanceof ExecutionException) { @@ -203,7 +206,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private JsonNode executeScript(TbMsg msg) throws ScriptException { try { String[] inArgs = prepareArgs(msg); - String eval = sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); + String eval = sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString(); return mapper.readTree(eval); } catch (ExecutionException e) { if (e.getCause() instanceof ScriptException) { @@ -220,7 +223,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S private ListenableFuture executeScriptAsync(TbMsg msg) { String[] inArgs = prepareArgs(msg); - return Futures.transformAsync(sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]), + return Futures.transformAsync(sandboxService.invokeFunction(tenantId, this.scriptId, inArgs[0], inArgs[1], inArgs[2]), o -> { try { return Futures.immediateFuture(mapper.readTree(o.toString())); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 6d0b7a0aa8..ab566fb147 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java deleted file mode 100644 index 60495b119d..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java +++ /dev/null @@ -1,250 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.script; - -import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.google.common.collect.Sets; -import org.junit.After; -import org.junit.Before; -import org.junit.Test; -import org.thingsboard.rule.engine.api.ScriptEngine; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleNodeId; -import org.thingsboard.server.common.msg.TbMsg; -import org.thingsboard.server.common.msg.TbMsgDataType; -import org.thingsboard.server.common.msg.TbMsgMetaData; - -import javax.script.ScriptException; -import java.util.Map; -import java.util.Set; -import java.util.UUID; -import java.util.concurrent.*; -import java.util.concurrent.atomic.AtomicInteger; - -import static org.junit.Assert.*; - -public class RuleNodeJsScriptEngineTest { - - private ScriptEngine scriptEngine; - private TestNashornJsInvokeService jsSandboxService; - - private EntityId ruleNodeId = new RuleNodeId(Uuids.timeBased()); - - @Before - public void beforeTest() throws Exception { - jsSandboxService = new TestNashornJsInvokeService(false, 1, 100, 3); - } - - @After - public void afterTest() throws Exception { - jsSandboxService.stop(); - } - - @Test - public void msgCanBeUpdated() throws ScriptException { - String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - - TbMsg actual = scriptEngine.executeUpdate(msg); - assertEquals("70", actual.getMetaData().getValue("temp")); - scriptEngine.destroy(); - } - - @Test - public void newAttributesCanBeAddedInMsg() throws ScriptException { - String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - - TbMsg actual = scriptEngine.executeUpdate(msg); - assertEquals("94", actual.getMetaData().getValue("newAttr")); - scriptEngine.destroy(); - } - - @Test - public void payloadCanBeUpdated() throws ScriptException { - String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\":\"Vit\",\"passed\": 5,\"bigObj\":{\"prop\":42}}"; - - TbMsg msg =TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, rawJson); - - TbMsg actual = scriptEngine.executeUpdate(msg); - - String expectedJson = "{\"name\":\"Vit\",\"passed\":35,\"bigObj\":{\"prop\":42,\"newProp\":\"Ukraine\"}}"; - assertEquals(expectedJson, actual.getData()); - scriptEngine.destroy(); - } - - @Test - public void metadataAccessibleForFilter() throws ScriptException { - String function = "return metadata.humidity < 15;"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, rawJson); - assertFalse(scriptEngine.executeFilter(msg)); - scriptEngine.destroy(); - } - - @Test - public void dataAccessibleForFilter() throws ScriptException { - String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "7"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData,TbMsgDataType.JSON, rawJson); - assertTrue(scriptEngine.executeFilter(msg)); - scriptEngine.destroy(); - } - - @Test - public void dataAccessibleForSwitch() throws ScriptException { - String jsCode = "function nextRelation(metadata, msg) {\n" + - " if(msg.passed == 5 && metadata.temp == 10)\n" + - " return 'one'\n" + - " else\n" + - " return 'two';\n" + - "};\n" + - "\n" + - "return nextRelation(metadata, msg);"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "10"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - Set actual = scriptEngine.executeSwitch(msg); - assertEquals(Sets.newHashSet("one"), actual); - scriptEngine.destroy(); - } - - @Test - public void multipleRelationsReturnedFromSwitch() throws ScriptException { - String jsCode = "function nextRelation(metadata, msg) {\n" + - " if(msg.passed == 5 && metadata.temp == 10)\n" + - " return ['three', 'one']\n" + - " else\n" + - " return 'two';\n" + - "};\n" + - "\n" + - "return nextRelation(metadata, msg);"; - scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode); - TbMsgMetaData metaData = new TbMsgMetaData(); - metaData.putValue("temp", "10"); - metaData.putValue("humidity", "99"); - String rawJson = "{\"name\": \"Vit\", \"passed\": 5, \"bigObj\": {\"prop\":42}}"; - - TbMsg msg = TbMsg.newMsg( "USER", null, metaData, TbMsgDataType.JSON, rawJson); - Set actual = scriptEngine.executeSwitch(msg); - assertEquals(Sets.newHashSet("one", "three"), actual); - scriptEngine.destroy(); - } - - @Test - public void concurrentReleasedCorrectly() throws InterruptedException, ExecutionException { - String code = "metadata.temp = metadata.temp * 10; return {metadata: metadata};"; - - int repeat = 1000; - ExecutorService service = Executors.newFixedThreadPool(repeat); - Map scriptIds = new ConcurrentHashMap<>(); - CountDownLatch startLatch = new CountDownLatch(repeat); - CountDownLatch finishLatch = new CountDownLatch(repeat); - AtomicInteger failedCount = new AtomicInteger(0); - - for (int i = 0; i < repeat; i++) { - service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code)); - } - - finishLatch.await(); - assertTrue(scriptIds.size() == 1); - assertTrue(failedCount.get() == 0); - - CountDownLatch nextStart = new CountDownLatch(repeat); - CountDownLatch nextFinish = new CountDownLatch(repeat); - for (int i = 0; i < repeat; i++) { - service.submit(() -> runScript(nextStart, nextFinish, failedCount, scriptIds, code)); - } - - nextFinish.await(); - assertTrue(scriptIds.size() == 1); - assertTrue(failedCount.get() == 0); - service.shutdownNow(); - } - - @Test - public void concurrentFailedEvaluationShouldThrowException() throws InterruptedException { - String code = "metadata.temp = metadata.temp * 10; urn {metadata: metadata};"; - - int repeat = 10000; - ExecutorService service = Executors.newFixedThreadPool(repeat); - Map scriptIds = new ConcurrentHashMap<>(); - CountDownLatch startLatch = new CountDownLatch(repeat); - CountDownLatch finishLatch = new CountDownLatch(repeat); - AtomicInteger failedCount = new AtomicInteger(0); - for (int i = 0; i < repeat; i++) { - service.submit(() -> { - service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code)); - }); - } - - finishLatch.await(); - assertTrue(scriptIds.isEmpty()); - assertEquals(repeat, failedCount.get()); - service.shutdownNow(); - } - - private void runScript(CountDownLatch startLatch, CountDownLatch finishLatch, AtomicInteger failedCount, - Map scriptIds, String code) { - try { - for (int k = 0; k < 10; k++) { - startLatch.countDown(); - startLatch.await(); - UUID scriptId = jsSandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, code).get(); - scriptIds.put(scriptId, new Object()); - jsSandboxService.invokeFunction(scriptId, "{}", "{}", "TEXT").get(); - jsSandboxService.release(scriptId).get(); - } - } catch (Throwable th) { - failedCount.incrementAndGet(); - } finally { - finishLatch.countDown(); - } - } - -} diff --git a/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java b/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java deleted file mode 100644 index 8a89de5dff..0000000000 --- a/application/src/test/java/org/thingsboard/server/service/script/TestNashornJsInvokeService.java +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.script; - -public class TestNashornJsInvokeService extends AbstractNashornJsInvokeService { - - private boolean useJsSandbox; - private final int monitorThreadPoolSize; - private final long maxCpuTime; - private final int maxErrors; - - public TestNashornJsInvokeService(boolean useJsSandbox, int monitorThreadPoolSize, long maxCpuTime, int maxErrors) { - this.useJsSandbox = useJsSandbox; - this.monitorThreadPoolSize = monitorThreadPoolSize; - this.maxCpuTime = maxCpuTime; - this.maxErrors = maxErrors; - init(); - } - - @Override - protected boolean useJsSandbox() { - return useJsSandbox; - } - - @Override - protected int getMonitorThreadPoolSize() { - return monitorThreadPoolSize; - } - - @Override - protected long getMaxCpuTime() { - return maxCpuTime; - } - - @Override - protected int getMaxErrors() { - return maxErrors; - } - - @Override - protected long getMaxBlacklistDuration() { - return 100000; - } -} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index bc730e7e5a..0048e325f3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -17,9 +17,9 @@ package org.thingsboard.server.common.data; public enum ApiUsageRecordKey { - MSG_COUNT, - DP_TRANSPORT_COUNT, - DP_STORAGE_COUNT, + TRANSPORT_MSG_COUNT, + TRANSPORT_DP_COUNT, + STORAGE_DP_COUNT, RE_EXEC_COUNT, JS_EXEC_COUNT diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java index 6336cb4ace..0996fc9850 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileData.java @@ -28,17 +28,4 @@ public class TenantProfileData { private TenantProfileConfiguration configuration; - @JsonIgnore - private Map properties = new HashMap<>(); - - @JsonAnyGetter - public Map properties() { - return this.properties; - } - - @JsonAnySetter - public void put(String name, Object value) { - this.properties.put(name, value); - } - } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java index ddedb3a606..c28f3637b7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/common/MultipleTbQueueCallbackWrapper.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index e70da7029e..14712a7cb3 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java index 3b775b9f26..980aa33221 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportService.java @@ -19,7 +19,6 @@ import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; @@ -62,10 +61,6 @@ public interface TransportService { void process(ProvisionDeviceRequestMsg msg, TransportServiceCallback callback); - boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback); - - boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits); - void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback); void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java deleted file mode 100644 index 362502bf17..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitFactory.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.transport.limits; - -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.springframework.util.StringUtils; -import org.thingsboard.server.common.msg.tools.TbRateLimits; - -@Slf4j -@Component -public class DefaultTransportRateLimitFactory implements TransportRateLimitFactory { - - private static final DummyTransportRateLimit ALWAYS_TRUE = new DummyTransportRateLimit(); - - @Override - public TransportRateLimit create(TransportRateLimitType type, Object configuration) { - if (!StringUtils.isEmpty(configuration)) { - try { - return new SimpleTransportRateLimit(new TbRateLimits(configuration.toString()), configuration.toString()); - } catch (Exception e) { - log.warn("[{}] Failed to init rate limit with configuration: {}", type, configuration, e); - return ALWAYS_TRUE; - } - } else { - return ALWAYS_TRUE; - } - } - - @Override - public TransportRateLimit createDefault(TransportRateLimitType type) { - return ALWAYS_TRUE; - } -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index bc415cd92a..b6f160ad1e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -17,76 +17,96 @@ package org.thingsboard.server.common.transport.limits; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.springframework.util.StringUtils; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; +import org.thingsboard.server.common.msg.tools.TbRateLimits; import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.queue.util.TbTransportComponent; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.function.BiConsumer; +import java.util.function.Function; @Service @TbTransportComponent @Slf4j public class DefaultTransportRateLimitService implements TransportRateLimitService { + private final static DummyTransportRateLimit ALLOW = new DummyTransportRateLimit(); private final ConcurrentMap tenantAllowed = new ConcurrentHashMap<>(); - private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); - private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap> tenantDevices = new ConcurrentHashMap<>(); + private final ConcurrentMap perTenantLimits = new ConcurrentHashMap<>(); + private final ConcurrentMap perDeviceLimits = new ConcurrentHashMap<>(); - private final TransportRateLimitFactory rateLimitFactory; private final TransportTenantProfileCache tenantProfileCache; - public DefaultTransportRateLimitService(TransportRateLimitFactory rateLimitFactory, TransportTenantProfileCache tenantProfileCache) { - this.rateLimitFactory = rateLimitFactory; + public DefaultTransportRateLimitService(TransportTenantProfileCache tenantProfileCache) { this.tenantProfileCache = tenantProfileCache; } @Override - public TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits) { + public EntityType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints) { if (!tenantAllowed.getOrDefault(tenantId, Boolean.TRUE)) { - return TransportRateLimitType.TENANT_ADDED_TO_DISABLED_LIST; + return EntityType.TENANT; } - TransportRateLimit[] tenantLimits = getTenantRateLimits(tenantId); - TransportRateLimit[] deviceLimits = getDeviceRateLimits(tenantId, deviceId); - for (TransportRateLimitType limitType : limits) { - TransportRateLimit rateLimit; - if (limitType.isTenantLevel()) { - rateLimit = tenantLimits[limitType.ordinal()]; - } else { - rateLimit = deviceLimits[limitType.ordinal()]; - } - if (!rateLimit.tryConsume(limitType.isMessageLevel() ? 1L : dataPoints)) { - return limitType; - } + if (!checkEntityRateLimit(dataPoints, getTenantRateLimits(tenantId))) { + return EntityType.TENANT; + } + if (!checkEntityRateLimit(dataPoints, getDeviceRateLimits(tenantId, deviceId))) { + return EntityType.DEVICE; } return null; } + private boolean checkEntityRateLimit(int dataPoints, EntityTransportRateLimits tenantLimits) { + if (dataPoints > 0) { + return tenantLimits.getTelemetryMsgRateLimit().tryConsume() && tenantLimits.getTelemetryDataPointsRateLimit().tryConsume(dataPoints); + } else { + return tenantLimits.getRegularMsgRateLimit().tryConsume(); + } + } + @Override public void update(TenantProfileUpdateResult update) { - TransportRateLimit[] newLimits = createTransportRateLimits(update.getProfile()); + EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), true); + EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), false); for (TenantId tenantId : update.getAffectedTenants()) { - mergeLimits(tenantId, newLimits); + mergeLimits(tenantId, tenantRateLimitPrototype, perTenantLimits::get, perTenantLimits::put); + tenantDevices.get(tenantId).forEach(deviceId -> { + mergeLimits(deviceId, deviceRateLimitPrototype, perDeviceLimits::get, perDeviceLimits::put); + }); } } @Override public void update(TenantId tenantId) { - mergeLimits(tenantId, fetchProfileAndInit(tenantId)); + EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), true); + EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(tenantProfileCache.get(tenantId), false); + mergeLimits(tenantId, tenantRateLimitPrototype, perTenantLimits::get, perTenantLimits::put); + tenantDevices.get(tenantId).forEach(deviceId -> { + mergeLimits(deviceId, deviceRateLimitPrototype, perDeviceLimits::get, perDeviceLimits::put); + }); } @Override public void remove(TenantId tenantId) { perTenantLimits.remove(tenantId); + tenantDevices.remove(tenantId); } @Override public void remove(DeviceId deviceId) { perDeviceLimits.remove(deviceId); + tenantDevices.values().forEach(set -> set.remove(deviceId)); } @Override @@ -94,48 +114,66 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi tenantAllowed.put(tenantId, allowed); } - private void mergeLimits(TenantId tenantId, TransportRateLimit[] newRateLimits) { - TransportRateLimit[] oldRateLimits = perTenantLimits.get(tenantId); + private void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits, + Function getFunction, + BiConsumer putFunction) { + EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId); if (oldRateLimits == null) { - perTenantLimits.put(tenantId, newRateLimits); + putFunction.accept(deviceId, newRateLimits); } else { - for (int i = 0; i < TransportRateLimitType.values().length; i++) { - TransportRateLimit newLimit = newRateLimits[i]; - TransportRateLimit oldLimit = oldRateLimits[i]; - if (newLimit != null && (oldLimit == null || !oldLimit.getConfiguration().equals(newLimit.getConfiguration()))) { - oldRateLimits[i] = newLimit; - } + EntityTransportRateLimits updated = merge(oldRateLimits, newRateLimits); + if (updated != null) { + putFunction.accept(deviceId, updated); } } } - private TransportRateLimit[] fetchProfileAndInit(TenantId tenantId) { - return perTenantLimits.computeIfAbsent(tenantId, tmp -> createTransportRateLimits(tenantProfileCache.get(tenantId))); + private EntityTransportRateLimits merge(EntityTransportRateLimits oldRateLimits, EntityTransportRateLimits newRateLimits) { + boolean regularUpdate = !oldRateLimits.getRegularMsgRateLimit().getConfiguration().equals(newRateLimits.getRegularMsgRateLimit().getConfiguration()); + boolean telemetryMsgRateUpdate = !oldRateLimits.getTelemetryMsgRateLimit().getConfiguration().equals(newRateLimits.getTelemetryMsgRateLimit().getConfiguration()); + boolean telemetryDataPointUpdate = !oldRateLimits.getTelemetryDataPointsRateLimit().getConfiguration().equals(newRateLimits.getTelemetryDataPointsRateLimit().getConfiguration()); + if (regularUpdate || telemetryMsgRateUpdate || telemetryDataPointUpdate) { + return new EntityTransportRateLimits( + regularUpdate ? newLimit(newRateLimits.getRegularMsgRateLimit().getConfiguration()) : oldRateLimits.getRegularMsgRateLimit(), + telemetryMsgRateUpdate ? newLimit(newRateLimits.getTelemetryMsgRateLimit().getConfiguration()) : oldRateLimits.getTelemetryMsgRateLimit(), + telemetryDataPointUpdate ? newLimit(newRateLimits.getTelemetryDataPointsRateLimit().getConfiguration()) : oldRateLimits.getTelemetryDataPointsRateLimit()); + } else { + return null; + } } - private TransportRateLimit[] createTransportRateLimits(TenantProfile tenantProfile) { + private EntityTransportRateLimits createRateLimits(TenantProfile tenantProfile, boolean tenant) { TenantProfileData profileData = tenantProfile.getProfileData(); - TransportRateLimit[] rateLimits = new TransportRateLimit[TransportRateLimitType.values().length]; - for (TransportRateLimitType type : TransportRateLimitType.values()) { - rateLimits[type.ordinal()] = rateLimitFactory.create(type, profileData.getProperties().get(type.getConfigurationKey())); + DefaultTenantProfileConfiguration profile = (DefaultTenantProfileConfiguration) profileData.getConfiguration(); + if (profile == null) { + return new EntityTransportRateLimits(ALLOW, ALLOW, ALLOW); + } else { + TransportRateLimit regularMsgRateLimit = newLimit(tenant ? profile.getTransportTenantMsgRateLimit() : profile.getTransportDeviceMsgRateLimit()); + TransportRateLimit telemetryMsgRateLimit = newLimit(tenant ? profile.getTransportTenantTelemetryMsgRateLimit() : profile.getTransportDeviceTelemetryMsgRateLimit()); + TransportRateLimit telemetryDpRateLimit = newLimit(tenant ? profile.getTransportTenantTelemetryDataPointsRateLimit() : profile.getTransportTenantTelemetryDataPointsRateLimit()); + return new EntityTransportRateLimits(regularMsgRateLimit, telemetryMsgRateLimit, telemetryDpRateLimit); } - return rateLimits; } - private TransportRateLimit[] getTenantRateLimits(TenantId tenantId) { - TransportRateLimit[] limits = perTenantLimits.get(tenantId); + private static TransportRateLimit newLimit(String config) { + return StringUtils.isEmpty(config) ? ALLOW : new SimpleTransportRateLimit(config); + } + + private EntityTransportRateLimits getTenantRateLimits(TenantId tenantId) { + EntityTransportRateLimits limits = perTenantLimits.get(tenantId); if (limits == null) { - limits = fetchProfileAndInit(tenantId); + limits = createRateLimits(tenantProfileCache.get(tenantId), true); perTenantLimits.put(tenantId, limits); } return limits; } - private TransportRateLimit[] getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { - TransportRateLimit[] limits = perDeviceLimits.get(deviceId); + private EntityTransportRateLimits getDeviceRateLimits(TenantId tenantId, DeviceId deviceId) { + EntityTransportRateLimits limits = perDeviceLimits.get(deviceId); if (limits == null) { - limits = fetchProfileAndInit(tenantId); + limits = createRateLimits(tenantProfileCache.get(tenantId), false); perDeviceLimits.put(deviceId, limits); + tenantDevices.computeIfAbsent(tenantId, id -> ConcurrentHashMap.newKeySet()).add(deviceId); } return limits; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java similarity index 70% rename from common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java rename to common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java index 54d83610c2..50831ccff7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitFactory.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/EntityTransportRateLimits.java @@ -15,10 +15,15 @@ */ package org.thingsboard.server.common.transport.limits; -public interface TransportRateLimitFactory { +import lombok.AllArgsConstructor; +import lombok.Data; - TransportRateLimit create(TransportRateLimitType type, Object config); +@Data +@AllArgsConstructor +public class EntityTransportRateLimits { - TransportRateLimit createDefault(TransportRateLimitType type); + private TransportRateLimit regularMsgRateLimit; + private TransportRateLimit telemetryMsgRateLimit; + private TransportRateLimit telemetryDataPointsRateLimit; } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java index 3253272ded..0ea920a4ba 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/SimpleTransportRateLimit.java @@ -26,6 +26,11 @@ public class SimpleTransportRateLimit implements TransportRateLimit { @Getter private final String configuration; + public SimpleTransportRateLimit(String configuration) { + this.configuration = configuration; + this.rateLimit = new TbRateLimits(configuration); + } + @Override public boolean tryConsume() { return rateLimit.tryConsume(); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java index 2ab1ec38ac..3918927c0b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitService.java @@ -15,13 +15,14 @@ */ package org.thingsboard.server.common.transport.limits; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; public interface TransportRateLimitService { - TransportRateLimitType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints, TransportRateLimitType... limits); + EntityType checkLimits(TenantId tenantId, DeviceId deviceId, int dataPoints); void update(TenantProfileUpdateResult update); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java deleted file mode 100644 index 038de86e84..0000000000 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/TransportRateLimitType.java +++ /dev/null @@ -1,48 +0,0 @@ -/** - * Copyright © 2016-2020 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.common.transport.limits; - -import lombok.Getter; - -public enum TransportRateLimitType { - - TENANT_ADDED_TO_DISABLED_LIST("general.tenant.disabled", true, false), - TENANT_MAX_MSGS("transport.tenant.msg", true, true), - TENANT_TELEMETRY_MSGS("transport.tenant.telemetry", true, true), - TENANT_MAX_DATA_POINTS("transport.tenant.dataPoints", true, false), - DEVICE_MAX_MSGS("transport.device.msg", false, true), - DEVICE_TELEMETRY_MSGS("transport.device.telemetry", false, true), - DEVICE_MAX_DATA_POINTS("transport.device.dataPoints", false, false); - - @Getter - private final String configurationKey; - @Getter - private final boolean tenantLevel; - @Getter - private final boolean deviceLevel; - @Getter - private final boolean messageLevel; - @Getter - private final boolean dataPointLevel; - - TransportRateLimitType(String configurationKey, boolean tenantLevel, boolean messageLevel) { - this.configurationKey = configurationKey; - this.tenantLevel = tenantLevel; - this.deviceLevel = !tenantLevel; - this.messageLevel = messageLevel; - this.dataPointLevel = !messageLevel; - } -} diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 326f996d14..f77a3ce44b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -55,8 +55,6 @@ import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGateway import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; -import org.thingsboard.server.common.transport.limits.TransportRateLimitType; -import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; import org.thingsboard.server.gen.transport.TransportProtos; @@ -364,7 +362,7 @@ public class DefaultTransportService implements TransportService { for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { dataPoints += tsKv.getKvCount(); } - if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { + if (checkLimits(sessionInfo, msg, callback, dataPoints)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -385,7 +383,7 @@ public class DefaultTransportService implements TransportService { @Override public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { - if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { + if (checkLimits(sessionInfo, msg, callback, msg.getKvCount())) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); @@ -575,31 +573,23 @@ public class DefaultTransportService implements TransportService { sessions.remove(toSessionId(sessionInfo)); } - private TransportRateLimitType[] DEFAULT = new TransportRateLimitType[]{TransportRateLimitType.TENANT_MAX_MSGS, TransportRateLimitType.DEVICE_MAX_MSGS}; - private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); - - @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { - return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + return checkLimits(sessionInfo, msg, callback, 0); } - @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { + private boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); - TransportRateLimitType limit = rateLimitService.checkLimits(tenantId, deviceId, 0, limits); - if (limit == null) { + EntityType rateLimitedEntityType = rateLimitService.checkLimits(tenantId, deviceId, dataPoints); + if (rateLimitedEntityType == null) { return true; } else { if (callback != null) { - callback.onError(new TbRateLimitsException(limit.isTenantLevel() ? EntityType.TENANT : EntityType.DEVICE)); - } - if (log.isTraceEnabled()) { - log.trace("[{}][{}] {} rateLimit detected: {}", toSessionId(sessionInfo), tenantId, limit, msg); + callback.onError(new TbRateLimitsException(rateLimitedEntityType)); } return false; } @@ -831,8 +821,8 @@ public class DefaultTransportService implements TransportService { @Override public void onSuccess(T msg) { try { - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.MSG_COUNT, 1); - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.DP_TRANSPORT_COUNT, dataPoints); + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); + apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); } finally { callback.onSuccess(msg); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java index 18c1512a27..6773d09417 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportTenantProfileCache.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 864679ce2a..af327d3505 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; @@ -145,7 +146,9 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T defaultTenantProfile = new TenantProfile(); defaultTenantProfile.setDefault(true); defaultTenantProfile.setName("Default"); - defaultTenantProfile.setProfileData(new TenantProfileData()); + TenantProfileData profileData = new TenantProfileData(); + profileData.setConfiguration(new DefaultTenantProfileConfiguration()); + defaultTenantProfile.setProfileData(profileData); defaultTenantProfile.setDescription("Default tenant profile"); defaultTenantProfile.setIsolatedTbCore(false); defaultTenantProfile.setIsolatedTbRuleEngine(false); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java index 22830bed8c..f93fc2fb1c 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseTenantProfileServiceTest.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.dao.exception.DataValidationException; @@ -262,7 +263,9 @@ public class BaseTenantProfileServiceTest extends AbstractServiceTest { TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName(name); tenantProfile.setDescription(name + " Test"); - tenantProfile.setProfileData(new TenantProfileData()); + TenantProfileData profileData = new TenantProfileData(); + profileData.setConfiguration(new DefaultTenantProfileConfiguration()); + tenantProfile.setProfileData(profileData); tenantProfile.setDefault(false); tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbRuleEngine(false); From 0c1fd9785097cec8948135cb196d65fe8870eabc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 23 Oct 2020 17:50:15 +0300 Subject: [PATCH 026/102] UI: Improvement photo camera input widget --- .../system/widget_bundles/input_widgets.json | 8 +- ...html => photo-camera-input.component.html} | 17 +-- ...scss => photo-camera-input.component.scss} | 8 ++ ...ent.ts => photo-camera-input.component.ts} | 108 ++++++++++++------ .../widget/widget-components.module.ts | 6 +- .../assets/locale/locale.constant-en_US.json | 5 +- 6 files changed, 98 insertions(+), 54 deletions(-) rename ui-ngx/src/app/modules/home/components/widget/lib/{web-camera-input.component.html => photo-camera-input.component.html} (81%) rename ui-ngx/src/app/modules/home/components/widget/lib/{web-camera-input.component.scss => photo-camera-input.component.scss} (92%) rename ui-ngx/src/app/modules/home/components/widget/lib/{web-camera-input.component.ts => photo-camera-input.component.ts} (72%) diff --git a/application/src/main/data/json/system/widget_bundles/input_widgets.json b/application/src/main/data/json/system/widget_bundles/input_widgets.json index ac3486223a..097c31dac4 100644 --- a/application/src/main/data/json/system/widget_bundles/input_widgets.json +++ b/application/src/main/data/json/system/widget_bundles/input_widgets.json @@ -391,16 +391,16 @@ }, { "alias": "web_camera_input", - "name": "Web Camera Input", + "name": "Photo camera input", "descriptor": { "type": "latest", "sizeX": 7.5, "sizeY": 3, "resources": [], - "templateHtml": "\n", + "templateHtml": "\n", "templateCss": "", - "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.webCameraInputWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}\n", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Web Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}", + "controllerScript": "self.onInit = function() {\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.photoCameraInputWidget.onDataUpdated();\n}\n\nself.typeParameters = function() {\n return {\n maxDatasources: 1,\n maxDataKeys: 1,\n singleEntity: true\n }\n}\n\nself.onDestroy = function() {\n}\n", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"Photo Camera\",\n \"properties\": {\n \"widgetTitle\": {\n \"title\": \"Widget title\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"imageFormat\": {\n \"title\": \"Image Format\",\n \"type\": \"string\",\n \"default\": \"image/png\"\n },\n \"imageQuality\":{\n \"title\":\"Image quality that use lossy compression such as jpeg and webp\",\n \"type\":\"number\",\n \"default\": 0.92,\n \"min\": 0,\n \"max\": 1\n },\n \"maxWidth\": {\n \"title\": \"The maximal image width\",\n \"type\": \"number\",\n \"default\": 640\n }, \n \"maxHeight\": {\n \"title\": \"The maximal image heigth\",\n \"type\": \"number\",\n \"default\": 480\n }\n },\n \"required\": []\n },\n \"form\": [\n \"widgetTitle\",\n {\n \"key\": \"imageFormat\",\n \"type\": \"rc-select\",\n \"multiple\": false,\n \"items\": [\n {\n \"value\": \"image/jpeg\",\n \"label\": \"JPEG\"\n },\n {\n \"value\": \"image/png\",\n \"label\": \"PNG\"\n },\n {\n \"value\": \"image/webp\",\n \"label\": \"WEBP\"\n }\n ]\n },\n \"imageQuality\",\n \"maxWidth\",\n \"maxHeight\"\n ]\n}", "dataKeySettingsSchema": "{}\n", "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Random\",\"color\":\"#2196f3\",\"settings\":{},\"_hash\":0.15479322438769105,\"funcBody\":\"var value = prevValue + Math.random() * 100 - 50;\\nvar multiplier = Math.pow(10, 2 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -1000) {\\n\\tvalue = -1000;\\n} else if (value > 1000) {\\n\\tvalue = 1000;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"timewindowMs\":60000}},\"showTitle\":true,\"backgroundColor\":\"#fff\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{},\"title\":\"Web Camera Input\",\"showTitleIcon\":false,\"titleIcon\":\"more_horiz\",\"iconColor\":\"rgba(0, 0, 0, 0.87)\",\"iconSize\":\"24px\",\"titleTooltip\":\"\",\"dropShadow\":true,\"enableFullscreen\":false,\"widgetStyle\":{},\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":true,\"displayTimewindow\":true,\"showLegend\":false,\"actions\":{}}" } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html similarity index 81% rename from ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.html rename to ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html index 88e5475a40..b3b457849b 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.html @@ -16,13 +16,13 @@ -->
-
+
widgets.input-widgets.no-image last photo
-
@@ -59,16 +59,7 @@
-
- {{ 'widgets.input-widgets.no-entity-selected' | translate }} -
-
- {{ 'widgets.input-widgets.no-datakey-selected' | translate }} -
-
- {{ 'widgets.input-widgets.no-support-web-camera' | translate }} -
-
- {{ 'widgets.input-widgets.no-support-web-camera' | translate }} +
+ {{ textMessage | translate }}
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.scss b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.scss similarity index 92% rename from ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.scss rename to ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.scss index 94a51e6870..ea364e17a1 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.scss @@ -18,6 +18,7 @@ &__last-photo { width: 100%; + min-height: 0; margin: 5px 0; text-align: center; border: solid 1px; @@ -71,4 +72,11 @@ color: #a0a0a0; text-align: center; } + + .image-container{ + height: 100%; + min-height: 0; + width: 100%; + min-width: 100%; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts similarity index 72% rename from ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.ts rename to ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts index ee92ee8fe0..aebc500faf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/web-camera-input.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts @@ -40,7 +40,7 @@ import { Observable } from 'rxjs'; import { isString } from '@core/utils'; import { DomSanitizer, SafeUrl } from '@angular/platform-browser'; -interface WebCameraInputWidgetSettings { +interface PhotoCameraInputWidgetSettings { widgetTitle: string; imageQuality: number; imageFormat: string; @@ -50,12 +50,12 @@ interface WebCameraInputWidgetSettings { // @dynamic @Component({ - selector: 'tb-web-camera-widget', - templateUrl: './web-camera-input.component.html', - styleUrls: ['./web-camera-input.component.scss'], + selector: 'tb-photo-camera-widget', + templateUrl: './photo-camera-input.component.html', + styleUrls: ['./photo-camera-input.component.scss'], encapsulation: ViewEncapsulation.None }) -export class WebCameraInputWidgetComponent extends PageComponent implements OnInit, OnDestroy { +export class PhotoCameraInputWidgetComponent extends PageComponent implements OnInit, OnDestroy { constructor(@Inject(WINDOW) private window: Window, protected store: Store, @@ -93,11 +93,11 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn @Input() ctx: WidgetContext; - @ViewChild('videoStream', {static: true}) videoStreamRef: ElementRef; - @ViewChild('canvas', {static: true}) canvasRef: ElementRef; + @ViewChild('videoStream', {static: false}) videoStreamRef: ElementRef; + @ViewChild('canvas', {static: false}) canvasRef: ElementRef; private videoInputsIndex = 0; - private settings: WebCameraInputWidgetSettings; + private settings: PhotoCameraInputWidgetSettings; private datasource: Datasource; private width = 640; private height = 480; @@ -106,10 +106,13 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn isEntityDetected = false; dataKeyDetected = false; + isProtocolHttps = false; isCameraSupport = false; isDeviceDetect = false; isShowCamera = false; isPreviewPhoto = false; + isHavePermissionCamera = true; + isLoading = false; singleDevice = true; updatePhoto = false; previewPhoto: SafeUrl; @@ -132,7 +135,8 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn } ngOnInit(): void { - this.ctx.$scope.webCameraInputWidget = this; + this.ctx.$scope.photoCameraInputWidget = this; + this.isLoading = true; this.settings = this.ctx.settings; this.datasource = this.ctx.datasources[0]; @@ -168,25 +172,33 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn } public onDataUpdated() { - this.ngZone.run(() => { - this.updateWidgetData(this.ctx.defaultSubscription.data); - this.ctx.detectChanges(); - }); + this.updateWidgetData(this.ctx.defaultSubscription.data); + this.ctx.detectChanges(); } private detectAvailableDevices(): void { - if (WebCameraInputWidgetComponent.hasGetUserMedia()) { - this.isCameraSupport = true; - WebCameraInputWidgetComponent.getAvailableVideoInputs().then((devices) => { - this.isDeviceDetect = !!devices.length; - this.singleDevice = devices.length < 2; - this.availableVideoInputs = devices; - this.ctx.detectChanges(); - }, () => { - this.availableVideoInputs = []; - } - ) + if (this.window.location.protocol === 'https:' || this.window.location.hostname === 'localhost') { + this.isProtocolHttps = true; + + if (PhotoCameraInputWidgetComponent.hasGetUserMedia()) { + this.isCameraSupport = true; + PhotoCameraInputWidgetComponent.getAvailableVideoInputs().then((devices) => { + this.isLoading = false; + this.isDeviceDetect = !!devices.length; + this.singleDevice = devices.length < 2; + this.availableVideoInputs = devices; + this.ctx.detectChanges(); + }, () => { + this.isLoading = false; + this.availableVideoInputs = []; + } + ); + } else { + this.isLoading = false; + } + } else { + this.isLoading = false; } } @@ -206,8 +218,7 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn } takePhoto() { - this.isShowCamera = true; - this.initWebCamera(this.availableVideoInputs[this.videoInputsIndex].deviceId); + this.inititedVideoStream(this.availableVideoInputs[this.videoInputsIndex].deviceId, true); } closeCamera() { @@ -243,13 +254,13 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn this.closeCamera(); }, () => { this.updatePhoto = false; - }) + }); } switchWebCamera() { this.videoInputsIndex = (this.videoInputsIndex + 1) % this.availableVideoInputs.length; this.stopMediaTracks(); - this.initWebCamera(this.availableVideoInputs[this.videoInputsIndex].deviceId) + this.inititedVideoStream(this.availableVideoInputs[this.videoInputsIndex].deviceId); } createPhoto() { @@ -257,22 +268,53 @@ export class WebCameraInputWidgetComponent extends PageComponent implements OnIn this.canvasElement.height = this.videoHeight; this.canvasElement.getContext('2d').drawImage(this.videoElement, 0, 0, this.videoWidth, this.videoHeight); - const mimeType: string = this.settings.imageFormat ? this.settings.imageFormat : WebCameraInputWidgetComponent.DEFAULT_IMAGE_TYPE; - const quality: number = this.settings.imageQuality ? this.settings.imageQuality : WebCameraInputWidgetComponent.DEFAULT_IMAGE_QUALITY; - this.previewPhoto = this.sanitizer.bypassSecurityTrustUrl(this.canvasElement.toDataURL(mimeType, quality)); + const mimeType: string = this.settings.imageFormat ? this.settings.imageFormat : PhotoCameraInputWidgetComponent.DEFAULT_IMAGE_TYPE; + const quality: number = this.settings.imageQuality ? this.settings.imageQuality : PhotoCameraInputWidgetComponent.DEFAULT_IMAGE_QUALITY; + this.previewPhoto = this.canvasElement.toDataURL(mimeType, quality); this.isPreviewPhoto = true; } - private initWebCamera(deviceId?: string) { + private inititedVideoStream(deviceId?: string, init = false) { if (window.navigator.mediaDevices && window.navigator.mediaDevices.getUserMedia) { const videoTrackConstraints = { video: {deviceId: deviceId !== '' ? {exact: deviceId} : undefined} }; window.navigator.mediaDevices.getUserMedia(videoTrackConstraints).then((stream: MediaStream) => { + if (init) { + this.isShowCamera = true; + } this.mediaStream = stream; this.videoElement.srcObject = stream; - }) + this.ctx.detectChanges(); + }, () => { + this.isHavePermissionCamera = false; + }); + } + } + + get textMessage() { + if (this.isLoading) { + return ''; + } + if (!this.isProtocolHttps) { + return 'widgets.input-widgets.enable-https-use-widget'; + } + if (!this.isCameraSupport) { + return 'widgets.input-widgets.no-support-web-camera'; + } + if (!this.isEntityDetected) { + return 'widgets.input-widgets.no-entity-selected'; + } + if (!this.dataKeyDetected) { + return 'widgets.input-widgets.no-datakey-selected'; + } + if (!this.isDeviceDetect) { + return 'widgets.input-widgets.no-found-your-camera'; + } + if (!this.isHavePermissionCamera) { + return 'widgets.input-widgets.no-permission-camera'; } + return null; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts index a97096a953..c2a158fcdf 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-components.module.ts @@ -32,7 +32,7 @@ import { } from '@home/components/widget/lib/date-range-navigator/date-range-navigator.component'; import { MultipleInputWidgetComponent } from './lib/multiple-input-widget.component'; import { TripAnimationComponent } from './trip-animation/trip-animation.component'; -import { WebCameraInputWidgetComponent } from './lib/web-camera-input.component'; +import { PhotoCameraInputWidgetComponent } from './lib/photo-camera-input.component'; import { GatewayFormComponent } from './lib/gateway/gateway-form.component'; import { ImportExportService } from '@home/components/import-export/import-export.service'; @@ -49,7 +49,7 @@ import { ImportExportService } from '@home/components/import-export/import-expor DateRangeNavigatorPanelComponent, MultipleInputWidgetComponent, TripAnimationComponent, - WebCameraInputWidgetComponent, + PhotoCameraInputWidgetComponent, GatewayFormComponent ], imports: [ @@ -67,7 +67,7 @@ import { ImportExportService } from '@home/components/import-export/import-expor DateRangeNavigatorWidgetComponent, MultipleInputWidgetComponent, TripAnimationComponent, - WebCameraInputWidgetComponent, + PhotoCameraInputWidgetComponent, GatewayFormComponent ], providers: [ diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..20a01cc99a 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2283,7 +2283,10 @@ "no-entity-selected": "No entity selected", "no-image": "No image", "no-support-geolocation": "Your browser doesn't support geolocation", - "no-support-web-camera": "No supported web camera", + "no-support-web-camera": "Your browser does not support cameras", + "enable-https-use-widget": "Please enable HTTPS to use this widget", + "no-found-your-camera": "Can't find your camera", + "no-permission-camera": "Permission was denied by the user / This site doesn't have permission to use the camera", "no-timeseries-selected": "No timeseries selected", "secret-key": "Secret key", "secret-key-required": "Secret key is required", From c40f6bf47c58cfc26d6d04729093da50e0a3b054 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 23 Oct 2020 18:42:07 +0300 Subject: [PATCH 027/102] UI: Added new options for legend config (sort datakeys in legend) --- ui-ngx/src/app/core/api/widget-subscription.ts | 3 --- .../components/widget/legend-config-panel.component.html | 3 +++ .../components/widget/legend-config-panel.component.ts | 1 + .../app/modules/home/components/widget/legend.component.ts | 7 +++++-- ui-ngx/src/app/shared/models/widget.models.ts | 2 ++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 6 files changed, 12 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index f7017652d8..123fe010c8 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -1226,9 +1226,6 @@ export class WidgetSubscription implements IWidgetSubscription { }); }); } - if (this.displayLegend) { - this.legendData.keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); - } if (this.caulculateLegendData) { this.data.forEach((dataSetHolder, keyIndex) => { this.updateLegend(keyIndex, dataSetHolder.data, false); diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html index 15e5466e80..28cabf7cd8 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.html @@ -38,6 +38,9 @@ + + {{ 'legend.sort-legend' | translate }} + {{ 'legend.show-min' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts index 5f5abff7a3..05e515100e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend-config-panel.component.ts @@ -68,6 +68,7 @@ export class LegendConfigPanelComponent extends PageComponent implements OnInit this.legendConfigForm = this.fb.group({ direction: [this.data.legendConfig.direction, []], position: [this.data.legendConfig.position, []], + sortDataKeys: [this.data.legendConfig.sortDataKeys, []], showMin: [this.data.legendConfig.showMin, []], showMax: [this.data.legendConfig.showMax, []], showAvg: [this.data.legendConfig.showAvg, []], diff --git a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts index 55fffa2b5b..3691742406 100644 --- a/ui-ngx/src/app/modules/home/components/widget/legend.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/legend.component.ts @@ -60,8 +60,11 @@ export class LegendComponent implements OnInit { } legendKeys(): LegendKey[] { - return this.legendData.keys - .filter(legendKey => this.legendData.keys[legendKey.dataIndex].dataKey.inLegend); + let keys = this.legendData.keys; + if (this.legendConfig.sortDataKeys) { + keys = this.legendData.keys.sort((key1, key2) => key1.dataKey.label.localeCompare(key2.dataKey.label)); + } + return keys.filter(legendKey => this.legendData.keys[legendKey.dataIndex].dataKey.inLegend); } } diff --git a/ui-ngx/src/app/shared/models/widget.models.ts b/ui-ngx/src/app/shared/models/widget.models.ts index 8ea338a98f..a97a66a1ef 100644 --- a/ui-ngx/src/app/shared/models/widget.models.ts +++ b/ui-ngx/src/app/shared/models/widget.models.ts @@ -203,6 +203,7 @@ export const legendPositionTranslationMap = new Map( export interface LegendConfig { position: LegendPosition; direction?: LegendDirection; + sortDataKeys: boolean; showMin: boolean; showMax: boolean; showAvg: boolean; @@ -213,6 +214,7 @@ export function defaultLegendConfig(wType: widgetType): LegendConfig { return { direction: LegendDirection.column, position: LegendPosition.bottom, + sortDataKeys: false, showMin: false, showMax: false, showAvg: wType === widgetType.timeseries, diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..11cf7dcbc0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -1664,6 +1664,7 @@ "legend": { "direction": "Legend direction", "position": "Legend position", + "sort-legend": "Sort datakeys in legend", "show-max": "Show max value", "show-min": "Show min value", "show-avg": "Show average value", From fec424f5c536f9e6d3ff015a0685e131d1b278cc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Sat, 24 Oct 2020 20:56:34 +0300 Subject: [PATCH 028/102] UI: Fixed problem for json-form component selector --- ui-ngx/package.json | 6 +-- ui-ngx/yarn.lock | 124 +++++++++++++++++++++++++++----------------- 2 files changed, 80 insertions(+), 50 deletions(-) diff --git a/ui-ngx/package.json b/ui-ngx/package.json index 4612631ff6..8e3ede2934 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -76,8 +76,8 @@ "prettier": "^2.1.2", "prop-types": "^15.7.2", "raphael": "^2.3.0", - "rc-select": "^11.3.3", - "react": "^16.13.1", + "rc-select": "~10.5.1", + "react": "~16.14.0", "react-ace": "^9.1.4", "react-dom": "^16.13.1", "react-dropzone": "^11.2.0", @@ -92,7 +92,7 @@ "tslib": "^2.0.2", "tv4": "^1.3.0", "typeface-roboto": "^1.1.13", - "zone.js": "~0.11.1" + "zone.js": "~0.10.3" }, "devDependencies": { "@angular-builders/custom-webpack": "^10.0.1", diff --git a/ui-ngx/yarn.lock b/ui-ngx/yarn.lock index 58d275db1b..dcdc0909c0 100644 --- a/ui-ngx/yarn.lock +++ b/ui-ngx/yarn.lock @@ -272,6 +272,11 @@ dependencies: tslib "^2.0.0" +"@ant-design/css-animation@^1.7.2": + version "1.7.3" + resolved "https://registry.yarnpkg.com/@ant-design/css-animation/-/css-animation-1.7.3.tgz#60a1c970014e86b28f940510d69e503e428f1136" + integrity sha512-LrX0OGZtW+W6iLnTAqnTaoIsRelYeuLZWsrmBJFUXDALQphPsN8cE5DCsmoSlL0QYb94BQxINiuS70Ar/8BNgA== + "@auth0/angular-jwt@^5.0.1": version "5.0.1" resolved "https://registry.yarnpkg.com/@auth0/angular-jwt/-/angular-jwt-5.0.1.tgz#37851d3ca2a0e88b3e673afd7dd2891f0c61bdf5" @@ -1787,6 +1792,13 @@ acorn@^6.4.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.4.2.tgz#35866fd710528e92de10cf06016498e47e39e1e6" integrity sha512-XtGIhXwF8YM8bJhGxG5kXgjkEuNGLTkoYqVE+KMR+aspr4KGYmKYg7yUe3KghyQ9yheNwLnjmzh/7+gfDBmHCQ== +add-dom-event-listener@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.1.0.tgz#6a92db3a0dd0abc254e095c0f1dc14acbbaae310" + integrity sha512-WCxx1ixHT0GQU9hb0KI/mhgRQhnU+U3GvwY6ZvVjYq8rsihIGoaIOUbY0yMPBxLH5MDtr0kz3fisWGNcbWW7Jw== + dependencies: + object-assign "4.x" + adjust-sourcemap-loader@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/adjust-sourcemap-loader/-/adjust-sourcemap-loader-2.0.0.tgz#6471143af75ec02334b219f54bc7970c52fb29a4" @@ -6752,7 +6764,7 @@ oauth-sign@~0.9.0: resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455" integrity sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@4.x, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM= @@ -7645,7 +7657,7 @@ promise-retry@^1.1.1: err-code "^1.0.0" retry "^0.10.0" -prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.5.10, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -7810,6 +7822,13 @@ querystringify@^2.1.1: resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6" integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ== +raf@^3.4.0, raf@^3.4.1: + version "3.4.1" + resolved "https://registry.yarnpkg.com/raf/-/raf-3.4.1.tgz#0742e99a4a6552f445d73e3ee0328af0ff1ede39" + integrity sha512-Sq4CW4QhwOHE8ucn6J34MqtZCeWFP2aQSmrlroYgqAV1PjStIhJXxYuTgUIfkEk7zTLjmIjLmU5q+fbD1NnOJA== + dependencies: + performance-now "^2.1.0" + randombytes@^2.0.0, randombytes@^2.0.1, randombytes@^2.0.5, randombytes@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/randombytes/-/randombytes-2.1.0.tgz#df6f84372f0270dc65cdf6291349ab7a473d4f2a" @@ -7866,50 +7885,63 @@ rc-align@^4.0.0: rc-util "^5.3.0" resize-observer-polyfill "^1.5.1" -rc-motion@^2.0.0, rc-motion@^2.0.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-2.3.1.tgz#a0c9f402c267bd03543ef80a970297a6ba77c503" - integrity sha512-UAB2gwS9c1DuCFKVCimAkL2JUEGCwzgCbb+VslhIMFg6vY7oyMxYIQ6hkoji1PzgDEw0tHIhP+a17R6Y5DGMrQ== +rc-animate@^3.0.0: + version "3.1.1" + resolved "https://registry.yarnpkg.com/rc-animate/-/rc-animate-3.1.1.tgz#defdd863f56816c222534e4dc68feddecd081386" + integrity sha512-8wg2Zg3EETy0k/9kYuis30NJNQg1D6/WSQwnCiz6SvyxQXNet/rVraRz3bPngwY6rcU2nlRvoShiYOorXyF7Sg== dependencies: - "@babel/runtime" "^7.11.1" - classnames "^2.2.1" - rc-util "^5.2.1" + "@ant-design/css-animation" "^1.7.2" + classnames "^2.2.6" + raf "^3.4.0" + rc-util "^4.15.3" -rc-resize-observer@^0.2.3: - version "0.2.5" - resolved "https://registry.yarnpkg.com/rc-resize-observer/-/rc-resize-observer-0.2.5.tgz#03e3a5c3dfccd6c996a547e4f82721e4f20f6156" - integrity sha512-cc4sOI722MVoCkGf/ZZybDVsjxvnH0giyDdA7wBJLTiMSFJ0eyxBMnr0JLYoClxftjnr75Xzl/VUB3HDrAx04Q== +rc-motion@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/rc-motion/-/rc-motion-1.1.2.tgz#07212f1b96c715b8245ec121339146c4a9b0968c" + integrity sha512-YC/E7SSWKBFakYg4PENhSRWD4ZLDqkI7FKmutJcrMewZ91/ZIWfoZSDvPaBdKO0hsFrrzWepFhXQIq0FNnCMWA== dependencies: - "@babel/runtime" "^7.10.1" + "@babel/runtime" "^7.11.1" classnames "^2.2.1" - rc-util "^5.0.0" - resize-observer-polyfill "^1.5.1" + raf "^3.4.1" + rc-util "^5.0.6" -rc-select@^11.3.3: - version "11.3.3" - resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-11.3.3.tgz#ba445ac4d2d933dd1f80b796c1de28ce6c81bbf8" - integrity sha512-YMsGVEZxXctj15nIZKlFCkiOxMe0PNBeACN6nHqDozDYKR/aqP8J3XZqZ5Gw/fcgS4bI50zPVMieJKlY8/6Wfw== +rc-select@~10.5.1: + version "10.5.1" + resolved "https://registry.yarnpkg.com/rc-select/-/rc-select-10.5.1.tgz#4d4c5d4f8d2fd3b7e3dccf74e4c43142b2979247" + integrity sha512-fZraoNNhjUmDJccfk6VYgrgHBWFmHWh/NJZh2Xttcm/usOolYI1RjO9ikP4QGhzlJBFtnTLH2pE2nchjn3TCXA== dependencies: "@babel/runtime" "^7.10.1" classnames "2.x" - rc-motion "^2.0.1" - rc-trigger "^5.0.4" + rc-animate "^3.0.0" + rc-trigger "^4.3.0" rc-util "^5.0.1" - rc-virtual-list "^3.0.3" + rc-virtual-list "^1.1.2" warning "^4.0.3" -rc-trigger@^5.0.4: - version "5.0.6" - resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-5.0.6.tgz#7e84717525871a7923a671edb5a290e9e616525b" - integrity sha512-L/xIX5OO7a/bdTH0yYT9mwAsV6oM1inAqAbLjoUzpcIW+UUE3jjVOjm5VaKDfHd41rzDzOfN05URmhet5QzXKQ== +rc-trigger@^4.3.0: + version "4.4.3" + resolved "https://registry.yarnpkg.com/rc-trigger/-/rc-trigger-4.4.3.tgz#ed449cd6cce446555bc57f4394229c5c7154f4b0" + integrity sha512-yq/WyuiPwxd2q6jy+VPyy0GUCRFJ2eFqAaCwPE27AOftXeIupOcJ/2t1wakSq63cfk7qtzev5DKHUAjb8LOJCw== dependencies: "@babel/runtime" "^7.11.2" classnames "^2.2.6" + raf "^3.4.1" rc-align "^4.0.0" - rc-motion "^2.0.0" - rc-util "^5.3.4" + rc-motion "^1.0.0" + rc-util "^5.0.1" -rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, rc-util@^5.3.4: +rc-util@^4.15.3: + version "4.21.1" + resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-4.21.1.tgz#88602d0c3185020aa1053d9a1e70eac161becb05" + integrity sha512-Z+vlkSQVc1l8O2UjR3WQ+XdWlhj5q9BMQNLk2iOBch75CqPfrJyGtcWMcnhRlNuDu0Ndtt4kLVO8JI8BrABobg== + dependencies: + add-dom-event-listener "^1.1.0" + prop-types "^15.5.10" + react-is "^16.12.0" + react-lifecycles-compat "^3.0.4" + shallowequal "^1.1.0" + +rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.6, rc-util@^5.3.0: version "5.4.0" resolved "https://registry.yarnpkg.com/rc-util/-/rc-util-5.4.0.tgz#688eaeecfdae9dae2bfdf10bedbe884591dba004" integrity sha512-kXDn1JyLJTAWLBFt+fjkTcUtXhxKkipQCobQmxIEVrX62iXgo24z8YKoWehWfMxPZFPE+RXqrmEu9j5kHz/Lrg== @@ -7917,14 +7949,14 @@ rc-util@^5.0.0, rc-util@^5.0.1, rc-util@^5.0.7, rc-util@^5.2.1, rc-util@^5.3.0, react-is "^16.12.0" shallowequal "^1.1.0" -rc-virtual-list@^3.0.3: - version "3.1.0" - resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-3.1.0.tgz#ca7ddbb291dace89c00cc4198ca7ef6e5e2034f7" - integrity sha512-DYU3wOjVuQo4hzYvmmpnoNtxrd8OIcutazA90x374ZFGGm4xYoSjCdh6UhBLi47IJI2BRry4l583nuoi7+06GA== +rc-virtual-list@^1.1.2: + version "1.1.6" + resolved "https://registry.yarnpkg.com/rc-virtual-list/-/rc-virtual-list-1.1.6.tgz#b255baf9aacde149a8893324e6307214094f4c0a" + integrity sha512-u3+izqWL8p8bQy8nYH48qWpiGyxR/ye8D2k0zJlXmfYeL55/xh83YrzHqiDzO78uj0Ewag3nXDA0JTVrYO7ygQ== dependencies: classnames "^2.2.6" - rc-resize-observer "^0.2.3" - rc-util "^5.0.7" + raf "^3.4.1" + rc-util "^5.0.0" react-ace@^9.1.4: version "9.1.4" @@ -7961,6 +7993,11 @@ react-is@^16.12.0, react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4" integrity sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ== +react-lifecycles-compat@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + react-transition-group@^4.0.0, react-transition-group@^4.4.0: version "4.4.1" resolved "https://registry.yarnpkg.com/react-transition-group/-/react-transition-group-4.4.1.tgz#63868f9325a38ea5ee9535d828327f85773345c9" @@ -7971,10 +8008,10 @@ react-transition-group@^4.0.0, react-transition-group@^4.4.0: loose-envify "^1.4.0" prop-types "^15.6.2" -react@^16.13.1: - version "16.13.1" - resolved "https://registry.yarnpkg.com/react/-/react-16.13.1.tgz#2e818822f1a9743122c063d6410d85c1e3afe48e" - integrity sha512-YMZQQq32xHLX0bz5Mnibv1/LHb3Sqzngu7xstSM+vrkE5Kzr9xE0yMByK5kMoTK30YVJE61WfbxIFFvfeDKT1w== +react@~16.14.0: + version "16.14.0" + resolved "https://registry.yarnpkg.com/react/-/react-16.14.0.tgz#94d776ddd0aaa37da3eda8fc5b6b18a4c9a3114d" + integrity sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -10206,10 +10243,3 @@ zone.js@~0.10.3: version "0.10.3" resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.10.3.tgz#3e5e4da03c607c9dcd92e37dd35687a14a140c16" integrity sha512-LXVLVEq0NNOqK/fLJo3d0kfzd4sxwn2/h67/02pjCjfKDxgx1i9QqpvtHD8CrBnSSwMw5+dy11O7FRX5mkO7Cg== - -zone.js@~0.11.1: - version "0.11.1" - resolved "https://registry.yarnpkg.com/zone.js/-/zone.js-0.11.1.tgz#0301d00d26febb2722f074c46aac4a948698ce39" - integrity sha512-KcZawpmVgS+3U2rzKTM6fLKaCX1QDv3//NxiSOOsqpQY/r5hl+xpYikPwY93Sp7CAB+J5mZJpb/YubxEYLGK5g== - dependencies: - tslib "^2.0.0" From d6c2eefe05d6cc8427e2fda8b2c9d0b50e374442 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Oct 2020 15:28:59 +0300 Subject: [PATCH 029/102] base url improvements --- .../server/controller/AuthController.java | 7 +++--- .../server/controller/UserController.java | 10 +++++--- .../Oauth2AuthenticationFailureHandler.java | 8 ++++++- .../Oauth2AuthenticationSuccessHandler.java | 11 ++++++--- .../system/DefaultSystemSecurityService.java | 24 ++++++++++++++++--- .../system/SystemSecurityService.java | 5 ++++ .../controller/AbstractControllerTest.java | 8 +++++++ ui/src/app/admin/general-settings.tpl.html | 5 ++++ ui/src/app/locale/locale.constant-en_US.json | 1 + 9 files changed, 66 insertions(+), 13 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 9f6c6f3b44..d57346cf48 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -171,7 +171,8 @@ public class AuthController extends BaseController { try { String email = resetPasswordByEmailRequest.get("email").asText(); UserCredentials userCredentials = userService.requestPasswordReset(TenantId.SYS_TENANT_ID, email); - String baseUrl = MiscUtils.constructBaseUrl(request); + User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String resetUrl = String.format("%s/api/noauth/resetPassword?resetToken=%s", baseUrl, userCredentials.getResetToken()); @@ -219,7 +220,7 @@ public class AuthController extends BaseController { User user = userService.findUserById(TenantId.SYS_TENANT_ID, credentials.getUserId()); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String loginUrl = String.format("%s/login", baseUrl); String email = user.getEmail(); @@ -266,7 +267,7 @@ public class AuthController extends BaseController { User user = userService.findUserById(TenantId.SYS_TENANT_ID, userCredentials.getUserId()); UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal); - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(user.getTenantId(), user.getCustomerId(), request); String loginUrl = String.format("%s/login", baseUrl); String email = user.getEmail(); mailService.sendPasswordWasResetEmail(loginUrl, email); diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index b3d66adc53..d1868b2916 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -52,6 +52,7 @@ import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; +import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; @@ -78,6 +79,9 @@ public class UserController extends BaseController { @Autowired private RefreshTokenRepository refreshTokenRepository; + @Autowired + private SystemSecurityService systemSecurityService; + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") @RequestMapping(value = "/user/{userId}", method = RequestMethod.GET) @@ -145,7 +149,7 @@ public class UserController extends BaseController { if (sendEmail) { SecurityUser authUser = getCurrentUser(); UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), savedUser.getId()); - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken()); String email = savedUser.getEmail(); @@ -185,7 +189,7 @@ public class UserController extends BaseController { UserCredentials userCredentials = userService.findUserCredentialsByUserId(getCurrentUser().getTenantId(), user.getId()); if (!userCredentials.isEnabled()) { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken()); mailService.sendActivationEmail(activateUrl, email); @@ -210,7 +214,7 @@ public class UserController extends BaseController { SecurityUser authUser = getCurrentUser(); UserCredentials userCredentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), user.getId()); if (!userCredentials.isEnabled()) { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(getTenantId(), getCurrentUser().getCustomerId(), request); String activateUrl = String.format(ACTIVATE_URL_PATTERN, baseUrl, userCredentials.getActivateToken()); return activateUrl; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 653be85f72..10fe5fe3fd 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -19,6 +19,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.ServletException; @@ -32,11 +36,13 @@ import java.nio.charset.StandardCharsets; @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { + private SystemSecurityService systemSecurityService; + @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index 3375d046e2..ece8708fd3 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -21,13 +21,16 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import org.thingsboard.server.utils.MiscUtils; +import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -43,16 +46,18 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final RefreshTokenRepository refreshTokenRepository; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; private final OAuth2Configuration oauth2Configuration; + private final SystemSecurityService systemSecurityService; @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, - final OAuth2Configuration oauth2Configuration) { + final OAuth2Configuration oauth2Configuration, SystemSecurityService systemSecurityService) { this.tokenFactory = tokenFactory; this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; this.oauth2Configuration = oauth2Configuration; + this.systemSecurityService = systemSecurityService; } @Override @@ -60,7 +65,7 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS HttpServletResponse response, Authentication authentication) throws IOException { - String baseUrl = MiscUtils.constructBaseUrl(request); + String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 11857f25b6..4219dbc609 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -40,17 +40,20 @@ import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; +import org.thingsboard.server.common.data.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.user.UserServiceImpl; import org.thingsboard.server.service.security.exception.UserPasswordExpiredException; -import org.thingsboard.server.common.data.security.model.SecuritySettings; -import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; +import org.thingsboard.server.utils.MiscUtils; import javax.annotation.Resource; +import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -146,7 +149,7 @@ public class DefaultSystemSecurityService implements SystemSecurityService { if (isPositiveInteger(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) { if ((userCredentials.getCreatedTime() + TimeUnit.DAYS.toMillis(securitySettings.getPasswordPolicy().getPasswordExpirationPeriodDays())) - < System.currentTimeMillis()) { + < System.currentTimeMillis()) { userCredentials = userService.requestExpiredPasswordReset(tenantId, userCredentials.getId()); throw new UserPasswordExpiredException("User password expired!", userCredentials.getResetToken()); } @@ -197,6 +200,21 @@ public class DefaultSystemSecurityService implements SystemSecurityService { } } + @Override + public String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest) { + String baseUrl; + AdminSettings generalSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "general"); + + JsonNode prohibitDifferentUrl = generalSettings.getJsonValue().get("prohibitDifferentUrl"); + + if (prohibitDifferentUrl != null && prohibitDifferentUrl.asBoolean()) { + baseUrl = generalSettings.getJsonValue().get("baseUrl").asText(); + } else { + baseUrl = MiscUtils.constructBaseUrl(httpServletRequest); + } + return baseUrl; + } + private static boolean isPositiveInteger(Integer val) { return val != null && val.intValue() > 0; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index afb7834cef..01367d53f1 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -16,11 +16,14 @@ package org.thingsboard.server.service.security.system; import org.springframework.security.core.AuthenticationException; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.common.data.security.model.SecuritySettings; +import javax.servlet.http.HttpServletRequest; + public interface SystemSecurityService { SecuritySettings getSecuritySettings(TenantId tenantId); @@ -31,4 +34,6 @@ public interface SystemSecurityService { void validatePassword(TenantId tenantId, String password, UserCredentials userCredentials) throws DataValidationException; + String getBaseUrl(TenantId tenantId, CustomerId customerId, HttpServletRequest httpServletRequest); + } diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index ca1991ed12..c0d79204d0 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -18,6 +18,7 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; @@ -58,6 +59,7 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; +import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; @@ -68,6 +70,7 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; +import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.mail.TestMailService; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; @@ -174,6 +177,11 @@ public abstract class AbstractControllerTest { } loginSysAdmin(); + ObjectNode generalSettings = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + AdminSettings adminSettings = new AdminSettings(); + adminSettings.setKey("general"); + adminSettings.setJsonValue(generalSettings); + Tenant tenant = new Tenant(); tenant.setTitle(TEST_TENANT_NAME); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); diff --git a/ui/src/app/admin/general-settings.tpl.html b/ui/src/app/admin/general-settings.tpl.html index b6eea094bf..cc50e990d4 100644 --- a/ui/src/app/admin/general-settings.tpl.html +++ b/ui/src/app/admin/general-settings.tpl.html @@ -34,6 +34,11 @@
admin.base-url-required
+ + {{ 'admin.prohibit-different-url' | translate }} +
{{'action.save' | translate}}
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index 2b21ec44be..f3ec840d7d 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -74,6 +74,7 @@ "test-mail-sent": "Test mail was successfully sent!", "base-url": "Base URL", "base-url-required": "Base URL is required.", + "prohibit-different-url": "Prohibit different URL", "mail-from": "Mail From", "mail-from-required": "Mail From is required.", "smtp-protocol": "SMTP protocol", From b9cc400a15779cf8c9621ea1f52125c2534e99f6 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 26 Oct 2020 12:52:13 +0200 Subject: [PATCH 030/102] base url improvements --- .../server/service/install/DefaultSystemDataLoaderService.java | 1 + ui/src/app/admin/general-settings.tpl.html | 3 +++ ui/src/app/locale/locale.constant-en_US.json | 3 ++- 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 95eb966a1a..d0ec2367df 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -116,6 +116,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { generalSettings.setKey("general"); ObjectNode node = objectMapper.createObjectNode(); node.put("baseUrl", "http://localhost:8080"); + node.put("prohibitDifferentUrl", true); generalSettings.setJsonValue(node); adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, generalSettings); diff --git a/ui/src/app/admin/general-settings.tpl.html b/ui/src/app/admin/general-settings.tpl.html index cc50e990d4..8a97f2300a 100644 --- a/ui/src/app/admin/general-settings.tpl.html +++ b/ui/src/app/admin/general-settings.tpl.html @@ -39,6 +39,9 @@ ng-model="vm.settings.jsonValue.prohibitDifferentUrl"> {{ 'admin.prohibit-different-url' | translate }} +
+ admin.prohibit-different-url-hint +
{{'action.save' | translate}}
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json index f3ec840d7d..046fa4df66 100644 --- a/ui/src/app/locale/locale.constant-en_US.json +++ b/ui/src/app/locale/locale.constant-en_US.json @@ -74,7 +74,8 @@ "test-mail-sent": "Test mail was successfully sent!", "base-url": "Base URL", "base-url-required": "Base URL is required.", - "prohibit-different-url": "Prohibit different URL", + "prohibit-different-url": "Prohibit to use hostname from the client request headers", + "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", "mail-from": "Mail From", "mail-from-required": "Mail From is required.", "smtp-protocol": "SMTP protocol", From 174570610bd2e26339eaaacfb48e49467dba1484 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 23 Oct 2020 17:16:08 +0300 Subject: [PATCH 031/102] added circles validation in BaseRuleChainService --- .../server/dao/rule/BaseRuleChainService.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index fdd99f97f7..a57ce62957 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.rule; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -38,6 +39,7 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; +import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; @@ -123,6 +125,10 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return null; } + if (CollectionUtils.isNotEmpty(ruleChainMetaData.getConnections())) { + validateCircles(ruleChainMetaData.getConnections()); + } + List nodes = ruleChainMetaData.getNodes(); List toAddOrUpdate = new ArrayList<>(); List toDelete = new ArrayList<>(); @@ -205,6 +211,28 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC return loadRuleChainMetaData(tenantId, ruleChainMetaData.getRuleChainId()); } + private void validateCircles(List connectionInfos) { + Map> connectionsMap = new HashMap<>(); + for (NodeConnectionInfo nodeConnection : connectionInfos) { + connectionsMap + .computeIfAbsent(nodeConnection.getFromIndex(), from -> new ArrayList<>()) + .add(nodeConnection.getToIndex()); + } + connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap)); + } + + private void validateCircles(int from, List toList, Map> connectionsMap) { + if (toList == null) { + return; + } + for (Integer to : toList) { + if (from == to) { + throw new IncorrectParameterException("Can't create circling relations in rule chain."); + } + validateCircles(from, connectionsMap.get(to), connectionsMap); + } + } + @Override public RuleChainMetaData loadRuleChainMetaData(TenantId tenantId, RuleChainId ruleChainId) { Validator.validateId(ruleChainId, "Incorrect rule chain id."); From 44a700e89a6cf783df6e4e6bf681ea33db072497 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 26 Oct 2020 12:24:48 +0200 Subject: [PATCH 032/102] rule chain circles validation improvements --- .../server/dao/rule/BaseRuleChainService.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index a57ce62957..ab05febc3a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -47,8 +47,10 @@ import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; @@ -212,16 +214,19 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } private void validateCircles(List connectionInfos) { - Map> connectionsMap = new HashMap<>(); + Map> connectionsMap = new HashMap<>(); for (NodeConnectionInfo nodeConnection : connectionInfos) { + if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) { + throw new IncorrectParameterException("Can't create the relation to yourself."); + } connectionsMap - .computeIfAbsent(nodeConnection.getFromIndex(), from -> new ArrayList<>()) + .computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>()) .add(nodeConnection.getToIndex()); } connectionsMap.keySet().forEach(key -> validateCircles(key, connectionsMap.get(key), connectionsMap)); } - private void validateCircles(int from, List toList, Map> connectionsMap) { + private void validateCircles(int from, Set toList, Map> connectionsMap) { if (toList == null) { return; } From 8bd617e3b583ae4db532055fef01da83f047b2d1 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 27 Oct 2020 17:43:12 +0200 Subject: [PATCH 033/102] created tests for rule chain metadata validator --- .../server/dao/rule/BaseRuleChainService.java | 6 +- .../dao/service/BaseRuleChainServiceTest.java | 90 +++++++++++++++++++ 2 files changed, 92 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index ab05febc3a..a99ad20ea6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -39,7 +39,6 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; import org.thingsboard.server.dao.service.Validator; @@ -52,7 +51,6 @@ import java.util.List; import java.util.Map; import java.util.Set; import java.util.concurrent.ExecutionException; -import java.util.stream.Collectors; /** * Created by igor on 3/12/18. @@ -217,7 +215,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC Map> connectionsMap = new HashMap<>(); for (NodeConnectionInfo nodeConnection : connectionInfos) { if (nodeConnection.getFromIndex() == nodeConnection.getToIndex()) { - throw new IncorrectParameterException("Can't create the relation to yourself."); + throw new DataValidationException("Can't create the relation to yourself."); } connectionsMap .computeIfAbsent(nodeConnection.getFromIndex(), from -> new HashSet<>()) @@ -232,7 +230,7 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC } for (Integer to : toList) { if (from == to) { - throw new IncorrectParameterException("Can't create circling relations in rule chain."); + throw new DataValidationException("Can't create circling relations in rule chain."); } validateCircles(from, connectionsMap.get(to), connectionsMap); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java index e6c6eaa699..8c9e9c37e3 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRuleChainServiceTest.java @@ -317,6 +317,16 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { ruleChainService.deleteRuleChainById(tenantId, savedRuleChainMetaData.getRuleChainId()); } + @Test(expected = DataValidationException.class) + public void testUpdateRuleChainMetaDataWithCirclingRelation() throws Exception { + ruleChainService.saveRuleChainMetaData(tenantId, createRuleChainMetadataWithCirclingRelation()); + } + + @Test(expected = DataValidationException.class) + public void testUpdateRuleChainMetaDataWithCirclingRelation2() throws Exception { + ruleChainService.saveRuleChainMetaData(tenantId, createRuleChainMetadataWithCirclingRelation2()); + } + private RuleChainMetaData createRuleChainMetadata() throws Exception { RuleChain ruleChain = new RuleChain(); ruleChain.setName("My RuleChain"); @@ -357,5 +367,85 @@ public abstract class BaseRuleChainServiceTest extends AbstractServiceTest { return ruleChainService.saveRuleChainMetaData(tenantId, ruleChainMetaData); } + private RuleChainMetaData createRuleChainMetadataWithCirclingRelation() throws Exception { + RuleChain ruleChain = new RuleChain(); + ruleChain.setName("My RuleChain"); + ruleChain.setTenantId(tenantId); + RuleChain savedRuleChain = ruleChainService.saveRuleChain(ruleChain); + + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(savedRuleChain.getId()); + + ObjectMapper mapper = new ObjectMapper(); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("name1"); + ruleNode1.setType("type1"); + ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\"")); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("name2"); + ruleNode2.setType("type2"); + ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\"")); + + RuleNode ruleNode3 = new RuleNode(); + ruleNode3.setName("name3"); + ruleNode3.setType("type3"); + ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\"")); + + List ruleNodes = new ArrayList<>(); + ruleNodes.add(ruleNode1); + ruleNodes.add(ruleNode2); + ruleNodes.add(ruleNode3); + ruleChainMetaData.setFirstNodeIndex(0); + ruleChainMetaData.setNodes(ruleNodes); + + ruleChainMetaData.addConnectionInfo(0,1,"success"); + ruleChainMetaData.addConnectionInfo(0,2,"fail"); + ruleChainMetaData.addConnectionInfo(1,2,"success"); + ruleChainMetaData.addConnectionInfo(2,2,"success"); + + return ruleChainMetaData; + } + + private RuleChainMetaData createRuleChainMetadataWithCirclingRelation2() throws Exception { + RuleChain ruleChain = new RuleChain(); + ruleChain.setName("My RuleChain"); + ruleChain.setTenantId(tenantId); + RuleChain savedRuleChain = ruleChainService.saveRuleChain(ruleChain); + + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(savedRuleChain.getId()); + + ObjectMapper mapper = new ObjectMapper(); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("name1"); + ruleNode1.setType("type1"); + ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\"")); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("name2"); + ruleNode2.setType("type2"); + ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\"")); + + RuleNode ruleNode3 = new RuleNode(); + ruleNode3.setName("name3"); + ruleNode3.setType("type3"); + ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\"")); + + List ruleNodes = new ArrayList<>(); + ruleNodes.add(ruleNode1); + ruleNodes.add(ruleNode2); + ruleNodes.add(ruleNode3); + ruleChainMetaData.setFirstNodeIndex(0); + ruleChainMetaData.setNodes(ruleNodes); + + ruleChainMetaData.addConnectionInfo(0,1,"success"); + ruleChainMetaData.addConnectionInfo(0,2,"fail"); + ruleChainMetaData.addConnectionInfo(1,2,"success"); + ruleChainMetaData.addConnectionInfo(2,0,"success"); + return ruleChainMetaData; + } } From 702a09132901836b9edea9e238c5b2ed9af5a7a1 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 12:18:03 +0200 Subject: [PATCH 034/102] fix/AlarmActionEvents --- .../actors/ruleChain/DefaultTbContext.java | 16 ++++++++-------- .../server/controller/AlarmController.java | 2 ++ .../thingsboard/rule/engine/api/TbContext.java | 2 +- .../rule/engine/action/TbAbstractAlarmNode.java | 15 ++++++++++----- 4 files changed, 21 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 30747e8f1b..8c53a6c02c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -252,26 +252,26 @@ class DefaultTbContext implements TbContext { } public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityCreatedMsg(customer, customer.getId(), ruleNodeId); + return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { - return entityCreatedMsg(device, device.getId(), ruleNodeId); + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { - return entityCreatedMsg(asset, asset.getId(), ruleNodeId); + return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } - public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) { - return entityCreatedMsg(alarm, alarm.getId(), ruleNodeId); + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action); } - public TbMsg entityCreatedMsg(E entity, I id, RuleNodeId ruleNodeId) { + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { try { - return TbMsg.newMsg(DataConstants.ENTITY_CREATED, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); + return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); } catch (JsonProcessingException | IllegalArgumentException e) { - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " created msg: " + e); + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); } } diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 7553448c09..6f3350143b 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -124,6 +124,7 @@ public class AlarmController extends BaseController { long ackTs = System.currentTimeMillis(); alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get(); alarm.setAckTs(ackTs); + alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null); } catch (Exception e) { throw handleException(e); @@ -141,6 +142,7 @@ public class AlarmController extends BaseController { long clearTs = System.currentTimeMillis(); alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get(); alarm.setClearTs(clearTs); + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null); } catch (Exception e) { throw handleException(e); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 032b285693..006fb1351b 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -141,7 +141,7 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); // TODO: Does this changes the message? - TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); /* * diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index b3344105bb..6ed5a05589 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -25,6 +25,7 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -61,13 +62,11 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"), - throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_CREATED, "Created"); } else if (alarmResult.isUpdated) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated"); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Updated"); } else if (alarmResult.isCleared) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); + tellNext(ctx, msg, alarmResult, DataConstants.ALARM_CLEAR, "Cleared"); } else { ctx.tellSuccess(msg); } @@ -126,4 +125,10 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmResultMsgType), + throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + } } From b78d9ca18959f4b9719813545bdf594f6b50cd57 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 14:21:33 +0200 Subject: [PATCH 035/102] fix AlarmNodeTests --- .../thingsboard/rule/engine/action/TbAlarmNodeTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 114967eb51..b54bd1d633 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -249,6 +250,9 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -297,6 +301,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -345,6 +351,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From 606ae2e5347c9646be2d29290b0455fbe348f8d7 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 16:38:57 +0200 Subject: [PATCH 036/102] fix typo --- .../java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index b54bd1d633..ba6a98afb1 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -252,7 +252,6 @@ public class TbAlarmNodeTest { verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); successCaptor.getValue().run(); - verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From 67e8bf8c2fe2600f3a7d730f0ef52e268b7b65d2 Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 12:18:03 +0200 Subject: [PATCH 037/102] push ENTITY_UPDATED events on ALARM updates,clears --- .../actors/ruleChain/DefaultTbContext.java | 16 ++++++++-------- .../thingsboard/rule/engine/api/TbContext.java | 2 +- .../rule/engine/action/TbAbstractAlarmNode.java | 14 +++++++++----- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index c8bb083577..3ab4ac8b9c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -257,26 +257,26 @@ class DefaultTbContext implements TbContext { } public TbMsg customerCreatedMsg(Customer customer, RuleNodeId ruleNodeId) { - return entityCreatedMsg(customer, customer.getId(), ruleNodeId); + return entityActionMsg(customer, customer.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg deviceCreatedMsg(Device device, RuleNodeId ruleNodeId) { - return entityCreatedMsg(device, device.getId(), ruleNodeId); + return entityActionMsg(device, device.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } public TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId) { - return entityCreatedMsg(asset, asset.getId(), ruleNodeId); + return entityActionMsg(asset, asset.getId(), ruleNodeId, DataConstants.ENTITY_CREATED); } - public TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId) { - return entityCreatedMsg(alarm, alarm.getId(), ruleNodeId); + public TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action) { + return entityActionMsg(alarm, alarm.getId(), ruleNodeId, action); } - public TbMsg entityCreatedMsg(E entity, I id, RuleNodeId ruleNodeId) { + public TbMsg entityActionMsg(E entity, I id, RuleNodeId ruleNodeId, String action) { try { - return TbMsg.newMsg(DataConstants.ENTITY_CREATED, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); + return TbMsg.newMsg(action, id, getActionMetaData(ruleNodeId), mapper.writeValueAsString(mapper.valueToTree(entity))); } catch (JsonProcessingException | IllegalArgumentException e) { - throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " created msg: " + e); + throw new RuntimeException("Failed to process " + id.getEntityType().name().toLowerCase() + " " + action + " msg: " + e); } } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 3d45fcf445..f48151f498 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -144,7 +144,7 @@ public interface TbContext { TbMsg assetCreatedMsg(Asset asset, RuleNodeId ruleNodeId); // TODO: Does this changes the message? - TbMsg alarmCreatedMsg(Alarm alarm, RuleNodeId ruleNodeId); + TbMsg alarmActionMsg(Alarm alarm, RuleNodeId ruleNodeId, String action); /* * diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 103da4706a..bf0ac8f859 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -58,13 +58,11 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Created"), - throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_CREATED, "Created"); } else if (alarmResult.isUpdated) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Updated"); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Updated"); } else if (alarmResult.isCleared) { - ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), "Cleared"); + tellNext(ctx, msg, alarmResult, DataConstants.ENTITY_UPDATED, "Cleared"); } else { ctx.tellSuccess(msg); } @@ -109,4 +107,10 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmAction), + throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); + } + } From 41796a4d891093bbcc90955d573c15229d1c20ea Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 14:21:33 +0200 Subject: [PATCH 038/102] fix AlarmNodeTests --- .../thingsboard/rule/engine/action/TbAlarmNodeTest.java | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 721d053b9c..908e263400 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -35,6 +35,7 @@ import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.DeviceId; @@ -249,6 +250,9 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -298,6 +302,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); @@ -346,6 +352,8 @@ public class TbAlarmNodeTest { node.onMsg(ctx, msg); + verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); verify(ctx).tellNext(any(), eq("Cleared")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From d042707441b1a568c8ccebc4a6d055af934420af Mon Sep 17 00:00:00 2001 From: ShvaykaD Date: Mon, 26 Oct 2020 14:34:19 +0200 Subject: [PATCH 039/102] cherry-pick from 2.5.5 --- .../java/org/thingsboard/server/controller/AlarmController.java | 2 ++ .../org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 4d063d07f1..47aa4aa550 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java @@ -126,6 +126,7 @@ public class AlarmController extends BaseController { long ackTs = System.currentTimeMillis(); alarmService.ackAlarm(getCurrentUser().getTenantId(), alarmId, ackTs).get(); alarm.setAckTs(ackTs); + alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null); } catch (Exception e) { throw handleException(e); @@ -143,6 +144,7 @@ public class AlarmController extends BaseController { long clearTs = System.currentTimeMillis(); alarmService.clearAlarm(getCurrentUser().getTenantId(), alarmId, null, clearTs).get(); alarm.setClearTs(clearTs); + alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); logEntityAction(alarm.getOriginator(), alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null); } catch (Exception e) { throw handleException(e); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index bf0ac8f859..e23b0d4af0 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -62,7 +62,7 @@ public abstract class TbAbstractAlarmNode Date: Mon, 26 Oct 2020 16:36:59 +0200 Subject: [PATCH 040/102] fix typo --- .../org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java | 1 - .../java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java | 1 - 2 files changed, 2 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index e23b0d4af0..def7afecd2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -112,5 +112,4 @@ public abstract class TbAbstractAlarmNode ctx.tellNext(toAlarmMsg(ctx, alarmResult, msg), alarmAction), throwable -> ctx.tellFailure(toAlarmMsg(ctx, alarmResult, msg), throwable)); } - } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index 908e263400..fa2eeb7ba3 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -252,7 +252,6 @@ public class TbAlarmNodeTest { verify(ctx).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); successCaptor.getValue().run(); - verify(ctx).tellNext(any(), eq("Updated")); ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); From 03581c294161fdbebe38b775c080e8f9dad5a4d1 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 27 Oct 2020 18:30:23 +0200 Subject: [PATCH 041/102] Usage records --- .../server/actors/ActorSystemContext.java | 11 +- .../RuleChainActorMessageProcessor.java | 8 +- .../ruleChain/RuleChainManagerActor.java | 6 + .../RuleNodeActorMessageProcessor.java | 5 + .../server/actors/tenant/TenantActor.java | 63 ++++++---- .../server/service/apiusage/ApiFeature.java | 11 +- .../DefaultTbApiUsageStateService.java | 110 ++++++++++++------ .../service/apiusage/TenantApiUsageState.java | 24 +--- .../DefaultSystemDataLoaderService.java | 3 +- .../script/AbstractJsInvokeService.java | 61 ++++++---- .../AbstractNashornJsInvokeService.java | 8 +- .../script/NashornJsInvokeService.java | 7 +- .../service/script/RemoteJsInvokeService.java | 7 +- .../server/common/data/ApiUsageRecordKey.java | 22 +++- .../DefaultTenantProfileConfiguration.java | 19 +++ .../profile/TenantProfileConfiguration.java | 4 + ...ient.java => DefaultTbApiUsageClient.java} | 8 +- ...StatsClient.java => TbApiUsageClient.java} | 3 +- .../service/DefaultTransportService.java | 13 ++- .../sql/tenant/TenantProfileRepository.java | 1 + .../server/dao/tenant/TenantProfileDao.java | 1 + .../usagerecord/ApiUsageStateServiceImpl.java | 35 +++++- 22 files changed, 295 insertions(+), 135 deletions(-) rename common/queue/src/main/java/org/thingsboard/server/queue/usagestats/{DefaultTbUsageStatsClient.java => DefaultTbApiUsageClient.java} (93%) rename common/queue/src/main/java/org/thingsboard/server/queue/usagestats/{TbUsageStatsClient.java => TbApiUsageClient.java} (90%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 7f028932f2..5a0e291ac2 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -32,7 +32,6 @@ import org.springframework.data.redis.core.RedisTemplate; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; -import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.DataConstants; @@ -65,6 +64,8 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; @@ -106,6 +107,14 @@ public class ActorSystemContext { return debugPerTenantLimits; } + @Autowired + @Getter + private TbApiUsageStateService apiUsageStateService; + + @Autowired + @Getter + private TbApiUsageClient apiUsageClient; + @Autowired @Getter @Setter diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index 5ca394be35..f5df62cfd3 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -23,6 +23,7 @@ import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.TbEntityActorId; import org.thingsboard.server.actors.service.DefaultActorService; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; @@ -46,6 +47,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper; import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.queue.TbClusterService; import java.util.ArrayList; @@ -68,15 +70,16 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor> nodeRoutes; private final RuleChainService service; private final TbClusterService clusterService; + private final TbApiUsageClient apiUsageClient; private String ruleChainName; private RuleNodeId firstId; private RuleNodeCtx firstNode; private boolean started; - RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext - , TbActorRef parent, TbActorRef self) { + RuleChainActorMessageProcessor(TenantId tenantId, RuleChain ruleChain, ActorSystemContext systemContext, TbActorRef parent, TbActorRef self) { super(systemContext, tenantId, ruleChain.getId()); + this.apiUsageClient = systemContext.getApiUsageClient(); this.ruleChainName = ruleChain.getName(); this.parent = parent; this.self = self; @@ -331,6 +334,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor(link -> ruleChainService.findTenantRuleChains(tenantId, link), ContextAwareActor.ENTITY_PACK_LIMIT)) { + ctx.stop(new TbEntityActorId(ruleChain.getId())); + } + } + protected void visit(RuleChain entity, TbActorRef actorRef) { if (entity != null && entity.isRoot()) { rootChain = entity; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java index db55ff8edf..52bd9f8c28 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java @@ -21,6 +21,7 @@ import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorCtx; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.actors.shared.ComponentMsgProcessor; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; @@ -28,6 +29,7 @@ import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.RuleNodeException; import org.thingsboard.server.common.msg.queue.RuleNodeInfo; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; /** * @author Andrew Shvayka @@ -36,6 +38,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor isolatedTenantId = systemContext.getServiceInfoProvider().getIsolatedTenant(); TenantProfile tenantProfile = systemContext.getTenantProfileCache().get(tenant.getTenantProfileId()); - isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); isCore = systemContext.getServiceInfoProvider().isService(ServiceType.TB_CORE); - + isRuleEngineForCurrentTenant = systemContext.getServiceInfoProvider().isService(ServiceType.TB_RULE_ENGINE); if (isRuleEngineForCurrentTenant) { try { if (isolatedTenantId.map(id -> id.equals(tenantId)).orElseGet(() -> !tenantProfile.isIsolatedTbRuleEngine())) { - log.info("[{}] Going to init rule chains", tenantId); - initRuleChains(); + if (apiUsageState.isReExecEnabled()) { + log.info("[{}] Going to init rule chains", tenantId); + initRuleChains(); + } else { + log.info("[{}] Skip init of the rule chains due to API limits", tenantId); + } } else { isRuleEngineForCurrentTenant = false; } @@ -98,8 +105,6 @@ public class TenantActor extends RuleChainManagerActor { } } catch (Exception e) { log.warn("[{}] Unknown failure", tenantId, e); -// TODO: throw this in 3.1? -// throw new TbActorException("Failed to init actor", e); } } @@ -115,7 +120,7 @@ public class TenantActor extends RuleChainManagerActor { if (msg.getMsgType().equals(MsgType.QUEUE_TO_RULE_ENGINE_MSG)) { QueueToRuleEngineMsg queueMsg = (QueueToRuleEngineMsg) msg; queueMsg.getTbMsg().getCallback().onSuccess(); - } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)){ + } else if (msg.getMsgType().equals(MsgType.TRANSPORT_TO_DEVICE_ACTOR_MSG)) { TransportToDeviceActorMsgWrapper transportMsg = (TransportToDeviceActorMsgWrapper) msg; transportMsg.getCallback().onSuccess(); } @@ -173,26 +178,33 @@ public class TenantActor extends RuleChainManagerActor { return; } TbMsg tbMsg = msg.getTbMsg(); - if (tbMsg.getRuleChainId() == null) { - if (getRootChainActor() != null) { - getRootChainActor().tell(msg); + if (apiUsageState.isReExecEnabled()) { + if (tbMsg.getRuleChainId() == null) { + if (getRootChainActor() != null) { + getRootChainActor().tell(msg); + } else { + tbMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!")); + log.info("[{}] No Root Chain: {}", tenantId, msg); + } } else { - tbMsg.getCallback().onFailure(new RuleEngineException("No Root Rule Chain available!")); - log.info("[{}] No Root Chain: {}", tenantId, msg); + try { + ctx.tell(new TbEntityActorId(tbMsg.getRuleChainId()), msg); + } catch (TbActorNotRegisteredException ex) { + log.trace("Received message for non-existing rule chain: [{}]", tbMsg.getRuleChainId()); + //TODO: 3.1 Log it to dead letters queue; + tbMsg.getCallback().onSuccess(); + } } } else { - try { - ctx.tell(new TbEntityActorId(tbMsg.getRuleChainId()), msg); - } catch (TbActorNotRegisteredException ex) { - log.trace("Received message for non-existing rule chain: [{}]", tbMsg.getRuleChainId()); - //TODO: 3.1 Log it to dead letters queue; - tbMsg.getCallback().onSuccess(); - } + log.trace("[{}] Ack message because Rule Engine is disabled", tenantId); + tbMsg.getCallback().onSuccess(); } } private void onRuleChainMsg(RuleChainAwareMsg msg) { - getOrCreateActor(msg.getRuleChainId()).tell(msg); + if (apiUsageState.isReExecEnabled()) { + getOrCreateActor(msg.getRuleChainId()).tell(msg); + } } private void onToDeviceActorMsg(DeviceAwareMsg msg, boolean priority) { @@ -208,6 +220,17 @@ public class TenantActor extends RuleChainManagerActor { } private void onComponentLifecycleMsg(ComponentLifecycleMsg msg) { + if (msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { + ApiUsageState old = apiUsageState; + apiUsageState = new ApiUsageState(systemContext.getApiUsageStateService().getApiUsageState(tenantId)); + if (old.isReExecEnabled() && !apiUsageState.isReExecEnabled()) { + log.info("[{}] Received API state update. Going to DISABLE Rule Engine execution.", tenantId); + destroyRuleChains(); + } else if (!old.isReExecEnabled() && apiUsageState.isReExecEnabled()) { + log.info("[{}] Received API state update. Going to ENABLE Rule Engine execution.", tenantId); + initRuleChains(); + } + } if (isRuleEngineForCurrentTenant) { TbActorRef target = getEntityActorRef(msg.getEntityId()); if (target != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java index 8267d4addd..f2dfab54d4 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java @@ -15,6 +15,15 @@ */ package org.thingsboard.server.service.apiusage; +import lombok.Getter; + public enum ApiFeature { - TRANSPORT, DB, RE, JS + TRANSPORT("transportApiState"), DB("dbApiState"), RE("ruleEngineApiState"), JS("jsExecutionApiState"); + + @Getter + private final String apiStateKey; + + ApiFeature(String apiStateKey) { + this.apiStateKey = apiStateKey; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 61d0baca0c..1b1a2853c7 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -15,18 +15,24 @@ */ package org.thingsboard.server.service.apiusage; +import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; @@ -34,7 +40,6 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; -import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; @@ -42,6 +47,7 @@ import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; +import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.PostConstruct; import java.util.ArrayList; @@ -60,11 +66,23 @@ import java.util.concurrent.locks.ReentrantLock; @Service public class DefaultTbApiUsageStateService implements TbApiUsageStateService { - public static final String HOURLY = "HOURLY_"; + public static final String HOURLY = "Hourly"; + public static final FutureCallback VOID_CALLBACK = new FutureCallback() { + @Override + public void onSuccess(@Nullable Void result) { + + } + + @Override + public void onFailure(Throwable t) { + + } + }; private final TbClusterService clusterService; private final PartitionService partitionService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; + private final TelemetrySubscriptionService tsWsService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; @@ -84,13 +102,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, ApiUsageStateService apiUsageStateService, - TimeseriesService tsService, + TimeseriesService tsService, TelemetrySubscriptionService tsWsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.clusterService = clusterService; this.partitionService = partitionService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; + this.tsWsService = tsWsService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; } @@ -122,9 +141,9 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); long newValue = tenantState.add(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.name(), newValue))); + updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); - updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(HOURLY + recordKey.name(), newHourlyValue))); + updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); Pair update = tenantState.checkStateUpdatedDueToThreshold(recordKey); if (update != null) { result.put(update.getFirst(), update.getSecond()); @@ -133,10 +152,11 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsService.save(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L); + tsWsService.saveAndNotify(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); if (!result.isEmpty()) { persistAndNotify(tenantState, result); } + callback.onSuccess(); } @Override @@ -149,27 +169,38 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public ApiUsageState getApiUsageState(TenantId tenantId) { - if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { - TenantApiUsageState state = getOrFetchState(tenantId); - return state.getApiUsageState(); + TenantApiUsageState tenantState = myTenantStates.get(tenantId); + if (tenantState != null) { + return tenantState.getApiUsageState(); } else { ApiUsageState state = otherTenantStates.get(tenantId); - if (state == null) { - updateLock.lock(); - try { - state = otherTenantStates.get(tenantId); - if (state == null) { - state = apiUsageStateService.findTenantApiUsageState(tenantId); - otherTenantStates.put(tenantId, state); + if (state != null) { + return state; + } else { + if (partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).isMyPartition()) { + return getOrFetchState(tenantId).getApiUsageState(); + } else { + updateLock.lock(); + try { + state = otherTenantStates.get(tenantId); + if (state == null) { + state = apiUsageStateService.findTenantApiUsageState(tenantId); + otherTenantStates.put(tenantId, state); + } + } finally { + updateLock.unlock(); } - } finally { - updateLock.unlock(); + return state; } } - return state; } } + @Override + public void onApiUsageStateUpdate(TenantId tenantId) { + otherTenantStates.remove(tenantId); + } + @Override public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); @@ -199,29 +230,38 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } - @Override - public void onApiUsageStateUpdate(TenantId tenantId) { - - } - private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { + TenantProfileData oldProfileData = state.getTenantProfileData(); state.setTenantProfileData(tenantProfile.getProfileData()); Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { persistAndNotify(state, result); } + updateProfileThresholds(state.getTenantId(), state.getApiUsageState().getId(), + oldProfileData.getConfiguration(), tenantProfile.getProfileData().getConfiguration()); + } + + private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, + TenantProfileConfiguration oldData, TenantProfileConfiguration newData) { + long ts = System.currentTimeMillis(); + List profileThresholds = new ArrayList<>(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + if (oldData == null || oldData.getProfileThreshold(key) != newData.getProfileThreshold(key)) { + profileThresholds.add(new BasicTsKvEntry(ts, new LongDataEntry(key.getApiLimitKey(), newData.getProfileThreshold(key)))); + } + } + if (!profileThresholds.isEmpty()) { + tsWsService.saveAndNotify(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); + } } private void persistAndNotify(TenantApiUsageState state, Map result) { - // TODO: - // 1. Broadcast to everyone notifications about enabled/disabled features. - // 2. Report rule engine and js executor metrics - // 4. UI for configuration of the thresholds - // 5. Max rule node executions per message. apiUsageStateService.update(state.getApiUsageState()); - if (result.containsKey(ApiFeature.TRANSPORT)) { - clusterService.onApiStateChange(state.getApiUsageState(), null); - } + clusterService.onApiStateChange(state.getApiUsageState(), null); + long ts = System.currentTimeMillis(); + List stateTelemetry = new ArrayList<>(); + result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); + tsWsService.saveAndNotify(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); } private void checkStartOfNextCycle() { @@ -252,15 +292,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfile tenantProfile = tenantProfileCache.get(tenantId); tenantState = new TenantApiUsageState(tenantProfile, dbStateEntity); try { - List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getEntityId()).get(); + List dbValues = tsService.findAllLatest(tenantId, dbStateEntity.getId()).get(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { boolean cycleEntryFound = false; boolean hourlyEntryFound = false; for (TsKvEntry tsKvEntry : dbValues) { - if (tsKvEntry.getKey().equals(key.name())) { + if (tsKvEntry.getKey().equals(key.getApiCountKey())) { cycleEntryFound = true; tenantState.put(key, tsKvEntry.getLongValue().get()); - } else if (tsKvEntry.getKey().equals(HOURLY + key.name())) { + } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { hourlyEntryFound = true; if (tsKvEntry.getTs() == tenantState.getCurrentHourTs()) { tenantState.putHourly(key, tsKvEntry.getLongValue().get()); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 5dcfb34ec3..8fcc1d46cb 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -21,11 +21,10 @@ import org.springframework.data.util.Pair; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.tools.SchedulerUtils; import java.util.HashMap; @@ -101,30 +100,13 @@ public class TenantApiUsageState { } public long getProfileThreshold(ApiUsageRecordKey key) { - DefaultTenantProfileConfiguration config = (DefaultTenantProfileConfiguration) tenantProfileData.getConfiguration(); - switch (key) { - case TRANSPORT_MSG_COUNT: - return config.getMaxTransportMessages(); - case TRANSPORT_DP_COUNT: - return config.getMaxTransportDataPoints(); - case JS_EXEC_COUNT: - return config.getMaxJSExecutions(); - case RE_EXEC_COUNT: - return config.getMaxREExecutions(); - case STORAGE_DP_COUNT: - return config.getMaxDPStorageDays(); - } - return 0L; + return tenantProfileData.getConfiguration().getProfileThreshold(key); } public TenantId getTenantId() { return apiUsageState.getTenantId(); } - public EntityId getEntityId() { - return apiUsageState.getEntityId(); - } - public boolean isTransportEnabled() { return apiUsageState.isTransportEnabled(); } diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index eeef99fff6..e6d44f9de9 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -129,8 +129,7 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { tenantProfileService.findOrCreateDefaultTenantProfile(TenantId.SYS_TENANT_ID); TenantProfileData tenantProfileData = new TenantProfileData(); - DefaultTenantProfileConfiguration configuration = new DefaultTenantProfileConfiguration(); - tenantProfileData.setConfiguration(configuration); + tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); TenantProfile isolatedTbCoreProfile = new TenantProfile(); isolatedTbCoreProfile.setDefault(false); diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java index eb5d3e3b49..8d9fa898dd 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java @@ -21,7 +21,8 @@ import lombok.extern.slf4j.Slf4j; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import java.util.Map; import java.util.UUID; @@ -36,13 +37,15 @@ import java.util.concurrent.atomic.AtomicInteger; @Slf4j public abstract class AbstractJsInvokeService implements JsInvokeService { - private final TbUsageStatsClient apiUsageStatsClient; + private final TbApiUsageStateService apiUsageStateService; + private final TbApiUsageClient apiUsageClient; protected ScheduledExecutorService timeoutExecutorService; protected Map scriptIdToNameMap = new ConcurrentHashMap<>(); - protected Map blackListedFunctions = new ConcurrentHashMap<>(); + protected Map disabledFunctions = new ConcurrentHashMap<>(); - protected AbstractJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { - this.apiUsageStatsClient = apiUsageStatsClient; + protected AbstractJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + this.apiUsageStateService = apiUsageStateService; + this.apiUsageClient = apiUsageClient; } public void init(long maxRequestsTimeout) { @@ -59,24 +62,32 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { @Override public ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) { - UUID scriptId = UUID.randomUUID(); - String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); - String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); - return doEval(scriptId, functionName, jsScript); + if (apiUsageStateService.getApiUsageState(tenantId).isJsExecEnabled()) { + UUID scriptId = UUID.randomUUID(); + String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_'); + String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); + return doEval(scriptId, functionName, jsScript); + } else { + return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); + } } @Override public ListenableFuture invokeFunction(TenantId tenantId, UUID scriptId, Object... args) { - String functionName = scriptIdToNameMap.get(scriptId); - if (functionName == null) { - return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); - } - if (!isBlackListed(scriptId)) { - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); - return doInvokeFunction(scriptId, functionName, args); + if (apiUsageStateService.getApiUsageState(tenantId).isJsExecEnabled()) { + String functionName = scriptIdToNameMap.get(scriptId); + if (functionName == null) { + return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!")); + } + if (!isDisabled(scriptId)) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); + return doInvokeFunction(scriptId, functionName, args); + } else { + return Futures.immediateFailedFuture( + new RuntimeException("Script invocation is blocked due to maximum error count " + getMaxErrors() + "!")); + } } else { - return Futures.immediateFailedFuture( - new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!")); + return Futures.immediateFailedFuture(new RuntimeException("JS Execution is disabled due to API limits!")); } } @@ -86,7 +97,7 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { if (functionName != null) { try { scriptIdToNameMap.remove(scriptId); - blackListedFunctions.remove(scriptId); + disabledFunctions.remove(scriptId); doRelease(scriptId, functionName); } catch (Exception e) { return Futures.immediateFailedFuture(e); @@ -106,7 +117,7 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { protected abstract long getMaxBlacklistDuration(); protected void onScriptExecutionError(UUID scriptId) { - blackListedFunctions.computeIfAbsent(scriptId, key -> new BlackListInfo()).incrementAndGet(); + disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()).incrementAndGet(); } private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { @@ -116,11 +127,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); } - private boolean isBlackListed(UUID scriptId) { - BlackListInfo errorCount = blackListedFunctions.get(scriptId); + private boolean isDisabled(UUID scriptId) { + DisableListInfo errorCount = disabledFunctions.get(scriptId); if (errorCount != null) { if (errorCount.getExpirationTime() <= System.currentTimeMillis()) { - blackListedFunctions.remove(scriptId); + disabledFunctions.remove(scriptId); return false; } else { return errorCount.get() >= getMaxErrors(); @@ -130,11 +141,11 @@ public abstract class AbstractJsInvokeService implements JsInvokeService { } } - private class BlackListInfo { + private class DisableListInfo { private final AtomicInteger counter; private long expirationTime; - private BlackListInfo() { + private DisableListInfo() { this.counter = new AtomicInteger(0); } diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java index 17be0602f8..db8bb68b4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java @@ -24,10 +24,10 @@ import delight.nashornsandbox.NashornSandboxes; import jdk.nashorn.api.scripting.NashornScriptEngineFactory; import lombok.Getter; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.scheduling.annotation.Scheduled; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -65,8 +65,8 @@ public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeSer @Value("${js.local.stats.enabled:false}") private boolean statsEnabled; - public AbstractNashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { - super(apiUsageStatsClient); + public AbstractNashornJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient, JsExecutorService jsExecutor) { + super(apiUsageStateService, apiUsageClient); this.jsExecutor = jsExecutor; } diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java index c20f3e8c08..0dd45d340c 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java @@ -19,7 +19,8 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import java.util.concurrent.TimeUnit; @@ -43,8 +44,8 @@ public class NashornJsInvokeService extends AbstractNashornJsInvokeService { @Value("${js.local.max_black_list_duration_sec:60}") private int maxBlackListDurationSec; - public NashornJsInvokeService(TbUsageStatsClient apiUsageStatsClient, JsExecutorService jsExecutor) { - super(apiUsageStatsClient, jsExecutor); + public NashornJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient, JsExecutorService jsExecutor) { + super(apiUsageStateService, apiUsageClient, jsExecutor); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 2ada577287..7b4114dce6 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -30,7 +30,8 @@ import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.Nullable; import javax.annotation.PostConstruct; @@ -69,8 +70,8 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); - public RemoteJsInvokeService(TbUsageStatsClient apiUsageStatsClient) { - super(apiUsageStatsClient); + public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + super(apiUsageStateService, apiUsageClient); } @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index 0048e325f3..b1ff230e71 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -15,12 +15,24 @@ */ package org.thingsboard.server.common.data; +import lombok.Getter; + public enum ApiUsageRecordKey { - TRANSPORT_MSG_COUNT, - TRANSPORT_DP_COUNT, - STORAGE_DP_COUNT, - RE_EXEC_COUNT, - JS_EXEC_COUNT + TRANSPORT_MSG_COUNT("transportMsgCount", "transportMsgLimit"), + TRANSPORT_DP_COUNT("transportDataPointsCount", "transportDataPointsLimit"), + STORAGE_DP_COUNT("storageDataPointsCount", "storageDataPointsLimit"), + RE_EXEC_COUNT("ruleEngineExecutionCount", "ruleEngineExecutionLimit"), + JS_EXEC_COUNT("jsExecutionCount", "jsExecutionLimit"); + + @Getter + private final String apiCountKey; + @Getter + private final String apiLimitKey; + + ApiUsageRecordKey(String apiCountKey, String apiLimitKey) { + this.apiCountKey = apiCountKey; + this.apiLimitKey = apiLimitKey; + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index f361ec901c..65940916b3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.tenant.profile; import lombok.Data; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.TenantProfileType; @Data @@ -38,6 +39,24 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxDPStorageDays; private int maxRuleNodeExecutionsPerMessage; + @Override + public long getProfileThreshold(ApiUsageRecordKey key) { + switch (key) { + case TRANSPORT_MSG_COUNT: + return maxTransportMessages; + case TRANSPORT_DP_COUNT: + return maxTransportDataPoints; + case JS_EXEC_COUNT: + return maxJSExecutions; + case RE_EXEC_COUNT: + return maxREExecutions; + case STORAGE_DP_COUNT: + return maxDPStorageDays; + } + return 0L; + } + + @Override public TenantProfileType getType() { return TenantProfileType.DEFAULT; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java index 07b184b389..d058d11cfc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java @@ -19,6 +19,7 @@ import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.TenantProfileType; @JsonIgnoreProperties(ignoreUnknown = true) @@ -33,4 +34,7 @@ public interface TenantProfileConfiguration { @JsonIgnore TenantProfileType getType(); + @JsonIgnore + long getProfileThreshold(ApiUsageRecordKey key); + } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java similarity index 93% rename from common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java rename to common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 5a94cd505e..0702322838 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -40,7 +40,7 @@ import java.util.concurrent.atomic.AtomicLong; @Component @Slf4j -public class DefaultTbUsageStatsClient implements TbUsageStatsClient { +public class DefaultTbApiUsageClient implements TbApiUsageClient { @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -53,7 +53,7 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { private final TbQueueProducerProvider producerProvider; private TbQueueProducer> msgProducer; - public DefaultTbUsageStatsClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { + public DefaultTbApiUsageClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { this.partitionService = partitionService; this.scheduler = scheduler; this.producerProvider = producerProvider; @@ -109,4 +109,8 @@ public class DefaultTbUsageStatsClient implements TbUsageStatsClient { } } + @Override + public void report(TenantId tenantId, ApiUsageRecordKey key) { + report(tenantId, key, 1L); + } } diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java similarity index 90% rename from common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java rename to common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java index 4ed850a018..31d85b27c7 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbUsageStatsClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java @@ -18,9 +18,10 @@ package org.thingsboard.server.queue.usagestats; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.TenantId; -public interface TbUsageStatsClient { +public interface TbApiUsageClient { void report(TenantId tenantId, ApiUsageRecordKey key, long value); + void report(TenantId tenantId, ApiUsageRecordKey key); } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index f77a3ce44b..29bc1b04a7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -78,7 +78,7 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.usagestats.TbUsageStatsClient; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.util.TbTransportComponent; import javax.annotation.PostConstruct; @@ -123,7 +123,7 @@ public class DefaultTransportService implements TransportService { private final StatsFactory statsFactory; private final TransportDeviceProfileCache deviceProfileCache; private final TransportTenantProfileCache tenantProfileCache; - private final TbUsageStatsClient apiUsageStatsClient; + private final TbApiUsageClient apiUsageClient; private final TransportRateLimitService rateLimitService; private final DataDecodingEncodingService dataDecodingEncodingService; private final SchedulerComponent scheduler; @@ -152,7 +152,7 @@ public class DefaultTransportService implements TransportService { StatsFactory statsFactory, TransportDeviceProfileCache deviceProfileCache, TransportTenantProfileCache tenantProfileCache, - TbUsageStatsClient apiUsageStatsClient, TransportRateLimitService rateLimitService, + TbApiUsageClient apiUsageClient, TransportRateLimitService rateLimitService, DataDecodingEncodingService dataDecodingEncodingService, SchedulerComponent scheduler) { this.serviceInfoProvider = serviceInfoProvider; this.queueProvider = queueProvider; @@ -161,7 +161,7 @@ public class DefaultTransportService implements TransportService { this.statsFactory = statsFactory; this.deviceProfileCache = deviceProfileCache; this.tenantProfileCache = tenantProfileCache; - this.apiUsageStatsClient = apiUsageStatsClient; + this.apiUsageClient = apiUsageClient; this.rateLimitService = rateLimitService; this.dataDecodingEncodingService = dataDecodingEncodingService; this.scheduler = scheduler; @@ -647,6 +647,7 @@ public class DefaultTransportService implements TransportService { if (stateOpt.isPresent()) { ApiUsageState apiUsageState = stateOpt.get(); rateLimitService.update(apiUsageState.getTenantId(), apiUsageState.isTransportEnabled()); + //TODO: if transport is disabled, we should close all sessions and not to check credentials. } } } else if (toSessionMsg.hasEntityDeleteMsg()) { @@ -821,8 +822,8 @@ public class DefaultTransportService implements TransportService { @Override public void onSuccess(T msg) { try { - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); - apiUsageStatsClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); + apiUsageClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_MSG_COUNT, 1); + apiUsageClient.report(tenantId, ApiUsageRecordKey.TRANSPORT_DP_COUNT, dataPoints); } finally { callback.onSuccess(msg); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java index 9c0687ab64..0deae8f729 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantProfileRepository.java @@ -51,4 +51,5 @@ public interface TenantProfileRepository extends PagingAndSortingRepository { TenantProfile findDefaultTenantProfile(TenantId tenantId); EntityInfo findDefaultTenantProfileInfo(TenantId tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 15be73a0b8..e1b494e2e8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -17,15 +17,26 @@ package org.thingsboard.server.dao.usagerecord; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.tenant.TenantDao; +import org.thingsboard.server.dao.tenant.TenantProfileDao; +import org.thingsboard.server.dao.timeseries.TimeseriesService; + +import java.util.ArrayList; +import java.util.List; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -35,11 +46,15 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; private final ApiUsageStateDao apiUsageStateDao; + private final TenantProfileDao tenantProfileDao; private final TenantDao tenantDao; + private final TimeseriesService tsService; - public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao) { + public ApiUsageStateServiceImpl(TenantDao tenantDao, ApiUsageStateDao apiUsageStateDao, TenantProfileDao tenantProfileDao, TimeseriesService tsService) { this.tenantDao = tenantDao; this.apiUsageStateDao = apiUsageStateDao; + this.tenantProfileDao = tenantProfileDao; + this.tsService = tsService; } @Override @@ -57,7 +72,18 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(tenantId); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); - return apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + + ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); + + Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); + TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); + TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); + List profileThresholds = new ArrayList<>(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { + profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); + } + tsService.save(tenantId, saved.getId(), profileThresholds, 0L); + return saved; } @Override @@ -80,7 +106,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A log.trace("Executing findApiUsageStateById, tenantId [{}], apiUsageStateId [{}]", tenantId, id); validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(id, "Incorrect apiUsageStateId " + id); - return apiUsageStateDao.findById(tenantId, id.getId()); } + return apiUsageStateDao.findById(tenantId, id.getId()); + } private DataValidator apiUsageStateValidator = new DataValidator() { From e9592174c2293b842ef6f1946dbd11d2e27056e4 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 27 Oct 2020 19:39:14 +0200 Subject: [PATCH 042/102] Api Tenant States --- .../DefaultTbApiUsageStateService.java | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 1b1a2853c7..17563bcbbd 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -23,6 +23,7 @@ import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,11 +32,13 @@ import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; @@ -69,17 +72,14 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Void result) { - - } + public void onSuccess(@Nullable Void result) {} @Override - public void onFailure(Throwable t) { - - } + public void onFailure(Throwable t) {} }; private final TbClusterService clusterService; private final PartitionService partitionService; + private final TenantService tenantService; private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; private final TelemetrySubscriptionService tsWsService; @@ -101,12 +101,13 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, - ApiUsageStateService apiUsageStateService, + TenantService tenantService, ApiUsageStateService apiUsageStateService, TimeseriesService tsService, TelemetrySubscriptionService tsWsService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.clusterService = clusterService; this.partitionService = partitionService; + this.tenantService = tenantService; this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; this.tsWsService = tsWsService; @@ -117,6 +118,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @PostConstruct public void init() { if (enabled) { + log.info("Starting api usage service."); + initStatesFromDataBase(); scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); } } @@ -164,6 +167,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { if (partitionChangeEvent.getServiceType().equals(ServiceType.TB_CORE)) { myTenantStates.entrySet().removeIf(entry -> !partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); otherTenantStates.entrySet().removeIf(entry -> partitionService.resolve(ServiceType.TB_CORE, entry.getKey(), entry.getKey()).isMyPartition()); + initStatesFromDataBase(); } } @@ -299,12 +303,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { for (TsKvEntry tsKvEntry : dbValues) { if (tsKvEntry.getKey().equals(key.getApiCountKey())) { cycleEntryFound = true; - tenantState.put(key, tsKvEntry.getLongValue().get()); + tenantState.put(key, tsKvEntry.getTs() == tenantState.getCurrentCycleTs() ? tsKvEntry.getLongValue().get() : 0L); } else if (tsKvEntry.getKey().equals(key.getApiCountKey() + HOURLY)) { hourlyEntryFound = true; - if (tsKvEntry.getTs() == tenantState.getCurrentHourTs()) { - tenantState.putHourly(key, tsKvEntry.getLongValue().get()); - } + tenantState.putHourly(key, tsKvEntry.getTs() == tenantState.getCurrentHourTs() ? tsKvEntry.getLongValue().get() : 0L); } if (cycleEntryFound && hourlyEntryFound) { break; @@ -319,4 +321,25 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { return tenantState; } + private void initStatesFromDataBase() { + try { + PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); + for (Tenant tenant : tenantIterator) { + if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { + updateLock.lock(); + try { + updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); + } catch (Exception e) { + log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); + } finally { + updateLock.unlock(); + } + } + } + log.info("Api usage service started."); + } catch (Exception e) { + log.warn("Unknown failure", e); + } + } + } From d4ae02f9a6dd94c1cad7adb5773fd94ea7ad374f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 27 Oct 2020 19:39:45 +0200 Subject: [PATCH 043/102] Update State of the API on startup --- .../server/service/apiusage/DefaultTbApiUsageStateService.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 17563bcbbd..3f9fab11ec 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, From c2a2b8cc4fdf1b5fa2e914cce2f5ec2ccb161035 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 10:53:50 +0200 Subject: [PATCH 044/102] base url improvements --- .../oauth2/Oauth2AuthenticationFailureHandler.java | 10 ++-------- .../oauth2/Oauth2AuthenticationSuccessHandler.java | 12 +++--------- .../server/controller/AbstractControllerTest.java | 9 --------- 3 files changed, 5 insertions(+), 26 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java index 10fe5fe3fd..984936874f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationFailureHandler.java @@ -19,10 +19,6 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.service.security.system.SystemSecurityService; import org.thingsboard.server.utils.MiscUtils; import javax.servlet.ServletException; @@ -34,15 +30,13 @@ import java.nio.charset.StandardCharsets; @Component(value = "oauth2AuthenticationFailureHandler") @ConditionalOnProperty(prefix = "security.oauth2", value = "enabled", havingValue = "true") -public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { - - private SystemSecurityService systemSecurityService; +public class Oauth2AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + String baseUrl = MiscUtils.constructBaseUrl(request); getRedirectStrategy().sendRedirect(request, response, baseUrl + "/login?loginError=" + URLEncoder.encode(exception.getMessage(), StandardCharsets.UTF_8.toString())); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index ece8708fd3..6641ab0ac4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -21,16 +21,13 @@ import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken; import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.oauth2.OAuth2Client; import org.thingsboard.server.dao.oauth2.OAuth2Configuration; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtToken; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import org.thingsboard.server.service.security.system.SystemSecurityService; +import org.thingsboard.server.utils.MiscUtils; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -46,26 +43,23 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS private final RefreshTokenRepository refreshTokenRepository; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; private final OAuth2Configuration oauth2Configuration; - private final SystemSecurityService systemSecurityService; @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, - final OAuth2Configuration oauth2Configuration, SystemSecurityService systemSecurityService) { + final OAuth2Configuration oauth2Configuration) { this.tokenFactory = tokenFactory; this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; this.oauth2Configuration = oauth2Configuration; - this.systemSecurityService = systemSecurityService; } @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException { - - String baseUrl = systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + String baseUrl = MiscUtils.constructBaseUrl(request); try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java index c0d79204d0..b819d70a7a 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -18,7 +18,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import io.jsonwebtoken.Claims; import io.jsonwebtoken.Header; import io.jsonwebtoken.Jwt; @@ -59,7 +58,6 @@ import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilde import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; import org.springframework.web.context.WebApplicationContext; -import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Tenant; @@ -70,7 +68,6 @@ import org.thingsboard.server.common.data.page.TextPageLink; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.config.ThingsboardSecurityConfiguration; -import org.thingsboard.server.dao.util.mapping.JacksonUtil; import org.thingsboard.server.service.mail.TestMailService; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; @@ -176,12 +173,6 @@ public abstract class AbstractControllerTest { .apply(springSecurity()).build(); } loginSysAdmin(); - - ObjectNode generalSettings = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - AdminSettings adminSettings = new AdminSettings(); - adminSettings.setKey("general"); - adminSettings.setJsonValue(generalSettings); - Tenant tenant = new Tenant(); tenant.setTitle(TEST_TENANT_NAME); Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class); From 7724495cb12530a45e64761c5cb66b908686237f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 14:48:00 +0200 Subject: [PATCH 045/102] added default queue for device profile --- .../src/main/data/upgrade/3.1.1/schema_update_before.sql | 1 + .../server/service/queue/DefaultTbClusterService.java | 4 ++++ .../org/thingsboard/server/common/data/DeviceProfile.java | 2 ++ .../main/java/org/thingsboard/server/common/msg/TbMsg.java | 5 +++++ .../org/thingsboard/server/dao/model/ModelConstants.java | 1 + .../server/dao/model/sql/DeviceProfileEntity.java | 5 +++++ dao/src/main/resources/sql/schema-entities-hsql.sql | 1 + dao/src/main/resources/sql/schema-entities.sql | 1 + .../profile/add-device-profile-dialog.component.html | 5 +++++ .../profile/add-device-profile-dialog.component.ts | 4 ++++ .../home/components/profile/device-profile.component.html | 5 +++++ .../home/components/profile/device-profile.component.ts | 5 +++++ .../components/wizard/device-wizard-dialog.component.html | 7 +++++++ .../components/wizard/device-wizard-dialog.component.ts | 7 +++++++ ui-ngx/src/app/shared/models/device.models.ts | 1 + ui-ngx/src/assets/locale/locale.constant-en_US.json | 1 + 16 files changed, 55 insertions(+) diff --git a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql index 216940b8f0..5f30f135e1 100644 --- a/application/src/main/data/upgrade/3.1.1/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.1.1/schema_update_before.sql @@ -96,6 +96,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + default_queue_name varchar(255), provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 9451b58e9f..5d59955e2b 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -159,6 +159,10 @@ public class DefaultTbClusterService implements TbClusterService { if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) { tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); } + String targetQueueName = deviceProfile.getDefaultQueueName(); + if (targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName())) { + tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName); + } } return tbMsg; } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java index 10990bc436..b9ecb862eb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DeviceProfile.java @@ -43,6 +43,7 @@ public class DeviceProfile extends SearchTextBased implements H private DeviceTransportType transportType; private DeviceProfileProvisionType provisionType; private RuleChainId defaultRuleChainId; + private String defaultQueueName; private transient DeviceProfileData profileData; @JsonIgnore private byte[] profileDataBytes; @@ -63,6 +64,7 @@ public class DeviceProfile extends SearchTextBased implements H this.description = deviceProfile.getDescription(); this.isDefault = deviceProfile.isDefault(); this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); + this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.setProfileData(deviceProfile.getProfileData()); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index f0af529be9..5ff8527327 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -90,6 +90,11 @@ public final class TbMsg implements Serializable { origMsg.data, ruleChainId, null, origMsg.getCallback()); } + public static TbMsg transformMsg(TbMsg origMsg, String queueName) { + return new TbMsg(queueName, origMsg.id, origMsg.ts, origMsg.type, origMsg.originator, origMsg.metaData, origMsg.dataType, + origMsg.data, origMsg.getRuleChainId(), null, origMsg.getCallback()); + } + public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 14ab9ed9e0..f7e8711dcb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -174,6 +174,7 @@ public class ModelConstants { public static final String DEVICE_PROFILE_DESCRIPTION_PROPERTY = "description"; public static final String DEVICE_PROFILE_IS_DEFAULT_PROPERTY = "is_default"; public static final String DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY = "default_rule_chain_id"; + public static final String DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY = "default_queue_name"; public static final String DEVICE_PROFILE_PROVISION_DEVICE_KEY = "provision_device_key"; /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java index 264d0725ae..d70268c349 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/DeviceProfileEntity.java @@ -79,6 +79,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_RULE_CHAIN_ID_PROPERTY, columnDefinition = "uuid") private UUID defaultRuleChainId; + @Column(name = ModelConstants.DEVICE_PROFILE_DEFAULT_QUEUE_NAME_PROPERTY) + private String defaultQueueName; + @Type(type = "jsonb") @Column(name = ModelConstants.DEVICE_PROFILE_PROFILE_DATA_PROPERTY, columnDefinition = "jsonb") private JsonNode profileData; @@ -108,6 +111,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (deviceProfile.getDefaultRuleChainId() != null) { this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId().getId(); } + this.defaultQueueName = deviceProfile.getDefaultQueueName(); this.provisionDeviceKey = deviceProfile.getProvisionDeviceKey(); } @@ -142,6 +146,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity impl if (defaultRuleChainId != null) { deviceProfile.setDefaultRuleChainId(new RuleChainId(defaultRuleChainId)); } + deviceProfile.setDefaultQueueName(defaultQueueName); deviceProfile.setProvisionDeviceKey(provisionDeviceKey); return deviceProfile; } diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index 41d9e3ac81..28abe32786 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -170,6 +170,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + default_queue_name varchar(255), provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index e1ffba98eb..233cd509da 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -188,6 +188,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( is_default boolean, tenant_id uuid, default_rule_chain_id uuid, + default_queue_name varchar(255), provision_device_key varchar, CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name), CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key), diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index fd5b5b17c2..b53ca8f0b0 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -45,6 +45,11 @@ labelText="device-profile.default-rule-chain" formControlName="defaultRuleChainId"> + + +
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts index fee4f91c66..07ce80aca6 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.ts @@ -48,6 +48,7 @@ import { MatHorizontalStepper } from '@angular/material/stepper'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { deepTrim } from '@core/utils'; +import {ServiceType} from "@shared/models/queue.models"; export interface AddDeviceProfileDialogData { deviceProfileName: string; @@ -89,6 +90,8 @@ export class AddDeviceProfileDialogComponent extends provisionConfigFormGroup: FormGroup; + serviceType = ServiceType.TB_RULE_ENGINE; + constructor(protected store: Store, protected router: Router, @Inject(MAT_DIALOG_DATA) public data: AddDeviceProfileDialogData, @@ -104,6 +107,7 @@ export class AddDeviceProfileDialogComponent extends name: [data.deviceProfileName, [Validators.required]], type: [DeviceProfileType.DEFAULT, [Validators.required]], defaultRuleChainId: [null, []], + defaultQueueName: ['', []], description: ['', []] } ); diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index f74b19891e..0eb4c07367 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -53,6 +53,11 @@ labelText="device-profile.default-rule-chain" formControlName="defaultRuleChainId"> + + +
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts index e837caf660..d30c8b47b9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts @@ -38,6 +38,7 @@ import { } from '@shared/models/device.models'; import { EntityType } from '@shared/models/entity-type.models'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import {ServiceType} from "@shared/models/queue.models"; @Component({ selector: 'tb-device-profile', @@ -63,6 +64,8 @@ export class DeviceProfileComponent extends EntityComponent { displayTransportConfiguration: boolean; + serviceType = ServiceType.TB_RULE_ENGINE; + constructor(protected store: Store, protected translate: TranslateService, @Optional() @Inject('entity') protected entityValue: DeviceProfile, @@ -101,6 +104,7 @@ export class DeviceProfileComponent extends EntityComponent { provisionConfiguration: [deviceProvisionConfiguration, Validators.required] }), defaultRuleChainId: [entity && entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null, []], + defaultQueueName: [entity ? entity.defaultQueueName : '', []], description: [entity ? entity.description : '', []], } ); @@ -174,6 +178,7 @@ export class DeviceProfileComponent extends EntityComponent { provisionConfiguration: deviceProvisionConfiguration }}); this.entityForm.patchValue({defaultRuleChainId: entity.defaultRuleChainId ? entity.defaultRuleChainId.id : null}); + this.entityForm.patchValue({defaultQueueName: entity.defaultQueueName}); this.entityForm.patchValue({description: entity.description}); } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 0d224122d6..fe7849d3e1 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -97,6 +97,13 @@ formControlName="defaultRuleChainId"> +
+ + +
device-profile.select-queue-hint
+
{{ 'device.is-gateway' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index c4f100b30f..a539b48917 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -43,6 +43,7 @@ import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; +import {ServiceType} from "@shared/models/queue.models"; @Component({ selector: 'tb-device-wizard', @@ -84,6 +85,8 @@ export class DeviceWizardDialogComponent extends labelPosition = 'end'; + serviceType = ServiceType.TB_RULE_ENGINE; + private subscriptions: Subscription[] = []; constructor(protected store: Store, @@ -105,6 +108,7 @@ export class DeviceWizardDialogComponent extends deviceProfileId: [null, Validators.required], newDeviceProfileTitle: [{value: null, disabled: true}], defaultRuleChainId: [{value: null, disabled: true}], + defaultQueueName: [''], description: [''] } ); @@ -117,6 +121,7 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators(null); this.deviceWizardFormGroup.get('newDeviceProfileTitle').disable(); this.deviceWizardFormGroup.get('defaultRuleChainId').disable(); + this.deviceWizardFormGroup.get('defaultQueueName').disable(); this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = false; this.createTransportConfiguration = false; @@ -126,6 +131,8 @@ export class DeviceWizardDialogComponent extends this.deviceWizardFormGroup.get('newDeviceProfileTitle').setValidators([Validators.required]); this.deviceWizardFormGroup.get('newDeviceProfileTitle').enable(); this.deviceWizardFormGroup.get('defaultRuleChainId').enable(); + this.deviceWizardFormGroup.get('defaultQueueName').enable(); + this.deviceWizardFormGroup.updateValueAndValidity(); this.createProfile = true; this.createTransportConfiguration = this.deviceWizardFormGroup.get('transportType').value && diff --git a/ui-ngx/src/app/shared/models/device.models.ts b/ui-ngx/src/app/shared/models/device.models.ts index f6850dbc57..9fbe289543 100644 --- a/ui-ngx/src/app/shared/models/device.models.ts +++ b/ui-ngx/src/app/shared/models/device.models.ts @@ -369,6 +369,7 @@ export interface DeviceProfile extends BaseData { provisionType: DeviceProvisionType; provisionDeviceKey?: string; defaultRuleChainId?: RuleChainId; + defaultQueueName?: string; profileData: DeviceProfileData; } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 453d5d2abd..22f4b9527d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -872,6 +872,7 @@ "profile-configuration": "Profile configuration", "transport-configuration": "Transport configuration", "default-rule-chain": "Default rule chain", + "select-queue-hint": "The queue name can be selected from a drop-down list or add a custom name.", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", From df133e48fffee8d66c8df9338471bc5d22a9693f Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 16:28:04 +0200 Subject: [PATCH 046/102] DefaultTransportService refactored --- .../queue/DefaultTbClusterService.java | 12 +- .../thingsboard/server/common/msg/TbMsg.java | 5 + .../service/DefaultTransportService.java | 161 ++++++++++-------- 3 files changed, 106 insertions(+), 72 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 5d59955e2b..9a331bb208 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -156,11 +156,15 @@ public class DefaultTbClusterService implements TbClusterService { private TbMsg transformMsg(TbMsg tbMsg, DeviceProfile deviceProfile) { if (deviceProfile != null) { RuleChainId targetRuleChainId = deviceProfile.getDefaultRuleChainId(); - if (targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId())) { - tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); - } String targetQueueName = deviceProfile.getDefaultQueueName(); - if (targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName())) { + boolean isRuleChainTransform = targetRuleChainId != null && !targetRuleChainId.equals(tbMsg.getRuleChainId()); + boolean isQueueTransform = targetQueueName != null && !targetQueueName.equals(tbMsg.getQueueName()); + + if (isRuleChainTransform && isQueueTransform) { + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId, targetQueueName); + } else if (isRuleChainTransform) { + tbMsg = TbMsg.transformMsg(tbMsg, targetRuleChainId); + } else if (isQueueTransform) { tbMsg = TbMsg.transformMsg(tbMsg, targetQueueName); } } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java index 5ff8527327..844a4d0216 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java @@ -95,6 +95,11 @@ public final class TbMsg implements Serializable { origMsg.data, origMsg.getRuleChainId(), null, origMsg.getCallback()); } + public static TbMsg transformMsg(TbMsg origMsg, RuleChainId ruleChainId, String queueName) { + return new TbMsg(queueName, origMsg.id, origMsg.ts, origMsg.type, origMsg.originator, origMsg.metaData, origMsg.dataType, + origMsg.data, ruleChainId, null, origMsg.getCallback()); + } + public static TbMsg newMsg(TbMsg tbMsg, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { return new TbMsg(tbMsg.getQueueName(), UUID.randomUUID(), tbMsg.getTs(), tbMsg.getType(), tbMsg.getOriginator(), tbMsg.getMetaData().copy(), tbMsg.getDataType(), tbMsg.getData(), ruleChainId, ruleNodeId, TbMsgCallback.EMPTY); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 3f37685383..aabb8c927e 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -23,7 +23,6 @@ import com.google.gson.JsonObject; import com.google.protobuf.ByteString; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.DeviceProfile; @@ -53,21 +52,46 @@ import org.thingsboard.server.common.transport.TransportTenantProfileCache; import org.thingsboard.server.common.transport.auth.GetOrCreateDeviceFromGatewayResponse; import org.thingsboard.server.common.transport.auth.TransportDeviceInfo; import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse; -import org.thingsboard.server.common.transport.limits.TransportRateLimit; import org.thingsboard.server.common.transport.limits.TransportRateLimitService; import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; -import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; +import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.EntityDeleteMsg; +import org.thingsboard.server.gen.transport.TransportProtos.EntityUpdateMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; +import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; +import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; +import org.thingsboard.server.gen.transport.TransportProtos.SessionType; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; +import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; +import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; +import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsgMetadata; @@ -237,14 +261,14 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { - sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); + public void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener) { + sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, SessionType.ASYNC, listener)); } @Override - public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { - TbProtoQueueMsg protoMsg = - new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); + public GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg) { + TbProtoQueueMsg protoMsg = + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); return response.getValue().getEntityProfileResponseMsg(); @@ -254,7 +278,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, + public void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -263,7 +287,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateBasicMqttCredRequestMsg msg, + public void process(DeviceTransportType transportType, ValidateBasicMqttCredRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -272,7 +296,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + public void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); @@ -281,7 +305,7 @@ public class DefaultTransportService implements TransportService { private void doProcess(DeviceTransportType transportType, TbProtoQueueMsg protoMsg, TransportServiceCallback callback) { ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); + ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); if (msg.hasDeviceInfo()) { result.credentials(msg.getCredentialsBody()); @@ -304,11 +328,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { + public void process(GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(requestMsg).build()); log.trace("Processing msg: {}", requestMsg); ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); + GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder(); if (msg.hasDeviceInfo()) { TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); @@ -323,7 +347,7 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } - private TransportDeviceInfo getTransportDeviceInfo(TransportProtos.DeviceInfoProto di) { + private TransportDeviceInfo getTransportDeviceInfo(DeviceInfoProto di) { TransportDeviceInfo tdi = new TransportDeviceInfo(); tdi.setTenantId(new TenantId(new UUID(di.getTenantIdMSB(), di.getTenantIdLSB()))); tdi.setDeviceId(new DeviceId(new UUID(di.getDeviceIdMSB(), di.getDeviceIdLSB()))); @@ -345,7 +369,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback callback) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -354,7 +378,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -363,9 +387,9 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) { int dataPoints = 0; - for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { + for (TsKvListProto tsKv : msg.getTsKvListList()) { dataPoints += tsKv.getKvCount(); } if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { @@ -373,22 +397,19 @@ public class DefaultTransportService implements TransportService { TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), callback); - for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { + for (TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("ts", tsKv.getTs() + ""); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); - RuleChainId ruleChainId = resolveRuleChainId(sessionInfo); - TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.POST_TELEMETRY_REQUEST.name(), - deviceId, metaData, gson.toJson(json), ruleChainId, null); - sendToRuleEngine(tenantId, tbMsg, packCallback); + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); } } } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); @@ -398,15 +419,12 @@ public class DefaultTransportService implements TransportService { metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("notifyDevice", "false"); - RuleChainId ruleChainId = resolveRuleChainId(sessionInfo); - TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.POST_ATTRIBUTES_REQUEST.name(), - deviceId, metaData, gson.toJson(json), ruleChainId, null); - sendToRuleEngine(tenantId, tbMsg, new TransportTbQueueCallback(callback)); + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(callback)); } } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -415,7 +433,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); @@ -425,7 +443,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); @@ -435,7 +453,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -450,13 +468,13 @@ public class DefaultTransportService implements TransportService { if (md != null) { SessionMsgListener listener = md.getListener(); transportCallbackExecutor.submit(() -> { - TransportProtos.ToServerRpcResponseMsg responseMsg = - TransportProtos.ToServerRpcResponseMsg.newBuilder() + ToServerRpcResponseMsg responseMsg = + ToServerRpcResponseMsg.newBuilder() .setRequestId(data.getRequestId()) .setError("timeout").build(); listener.onToServerRpcResponse(responseMsg); }); - if (md.getSessionType() == TransportProtos.SessionType.SYNC) { + if (md.getSessionType() == SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { @@ -466,7 +484,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); UUID sessionId = toSessionId(sessionInfo); @@ -482,10 +500,9 @@ public class DefaultTransportService implements TransportService { metaData.putValue("requestId", Integer.toString(msg.getRequestId())); metaData.putValue("serviceId", serviceInfoProvider.getServiceId()); metaData.putValue("sessionId", sessionId.toString()); - RuleChainId ruleChainId = resolveRuleChainId(sessionInfo); - TbMsg tbMsg = TbMsg.newMsg(ServiceQueue.MAIN, SessionMsgType.TO_SERVER_RPC_REQUEST.name(), - deviceId, metaData, gson.toJson(json), ruleChainId, null); - sendToRuleEngine(tenantId, tbMsg, new TransportTbQueueCallback(callback)); + + sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, + SessionMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); toServerRpcPendingMap.put(requestId, new RpcRequestMetadata(sessionId, msg.getRequestId())); schedulerExecutor.schedule(() -> processTimeout(requestId), clientSideRpcTimeout, TimeUnit.MILLISECONDS); @@ -493,7 +510,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg, TransportServiceCallback callback) { + public void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -502,11 +519,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { + public void reportActivity(SessionInfoProto sessionInfo) { reportActivityInternal(sessionInfo); } - private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { + private SessionMetaData reportActivityInternal(SessionInfoProto sessionInfo) { UUID sessionId = toSessionId(sessionInfo); SessionMetaData sessionMetaData = sessions.get(sessionId); if (sessionMetaData != null) { @@ -519,7 +536,7 @@ public class DefaultTransportService implements TransportService { long expTime = System.currentTimeMillis() - sessionInactivityTimeout; sessions.forEach((uuid, sessionMD) -> { long lastActivityTime = sessionMD.getLastActivityTime(); - TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); + SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); if (sessionInfo.getGwSessionIdMSB() > 0 && sessionInfo.getGwSessionIdLSB() > 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); @@ -531,13 +548,13 @@ public class DefaultTransportService implements TransportService { if (log.isDebugEnabled()) { log.debug("[{}] Session has expired due to last activity time: {}", toSessionId(sessionInfo), lastActivityTime); } - process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); + process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null); sessions.remove(uuid); - sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); + sessionMD.getListener().onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); } else { if (lastActivityTime > sessionMD.getLastReportedActivityTime()) { final long lastActivityTimeFinal = lastActivityTime; - process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() + process(sessionInfo, SubscriptionInfoProto.newBuilder() .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) .setRpcSubscription(sessionMD.isSubscribedToRPC()) .setLastActivityTime(lastActivityTime).build(), new TransportServiceCallback() { @@ -557,12 +574,12 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { - SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); + public void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { + SessionMetaData currentSession = new SessionMetaData(sessionInfo, SessionType.SYNC, listener); sessions.putIfAbsent(toSessionId(sessionInfo), currentSession); ScheduledFuture executorFuture = schedulerExecutor.schedule(() -> { - listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); + listener.onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); deregisterSession(sessionInfo); }, timeout, TimeUnit.MILLISECONDS); @@ -570,7 +587,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void deregisterSession(TransportProtos.SessionInfoProto sessionInfo) { + public void deregisterSession(SessionInfoProto sessionInfo) { SessionMetaData currentSession = sessions.get(toSessionId(sessionInfo)); if (currentSession != null && currentSession.hasScheduledFuture()) { log.debug("Stopping scheduler to avoid resending response if request has been ack."); @@ -583,12 +600,12 @@ public class DefaultTransportService implements TransportService { private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); } @Override - public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { + public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -609,7 +626,7 @@ public class DefaultTransportService implements TransportService { } } - protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { + protected void processToTransportMsg(ToTransportMsg toSessionMsg) { UUID sessionId = new UUID(toSessionMsg.getSessionIdMSB(), toSessionMsg.getSessionIdLSB()); SessionMetaData md = sessions.get(sessionId); if (md != null) { @@ -633,12 +650,12 @@ public class DefaultTransportService implements TransportService { listener.onToServerRpcResponse(toSessionMsg.getToServerResponse()); } }); - if (md.getSessionType() == TransportProtos.SessionType.SYNC) { + if (md.getSessionType() == SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { if (toSessionMsg.hasEntityUpdateMsg()) { - TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); + EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); @@ -659,7 +676,7 @@ public class DefaultTransportService implements TransportService { } } } else if (toSessionMsg.hasEntityDeleteMsg()) { - TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); + EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); UUID entityUuid = new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { @@ -690,29 +707,29 @@ public class DefaultTransportService implements TransportService { }); } - protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { + protected UUID toSessionId(SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } - protected UUID getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { + protected UUID getRoutingKey(SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()); } - protected TenantId getTenantId(TransportProtos.SessionInfoProto sessionInfo) { + protected TenantId getTenantId(SessionInfoProto sessionInfo) { return new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); } - protected DeviceId getDeviceId(TransportProtos.SessionInfoProto sessionInfo) { + protected DeviceId getDeviceId(SessionInfoProto sessionInfo) { return new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); } - public static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { - return TransportProtos.SessionEventMsg.newBuilder() - .setSessionType(TransportProtos.SessionType.ASYNC) + public static SessionEventMsg getSessionEventMsg(SessionEvent event) { + return SessionEventMsg.newBuilder() + .setSessionType(SessionType.ASYNC) .setEvent(event).build(); } - protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { + protected void sendToDeviceActor(SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); if (log.isTraceEnabled()) { log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); @@ -740,17 +757,25 @@ public class DefaultTransportService implements TransportService { ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); } - private RuleChainId resolveRuleChainId(TransportProtos.SessionInfoProto sessionInfo) { + protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, SessionInfoProto sessionInfo, JsonObject json, + TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; + String queueName; + if (deviceProfile == null) { log.warn("[{}] Device profile is null!", deviceProfileId); ruleChainId = null; + queueName = ServiceQueue.MAIN; } else { ruleChainId = deviceProfile.getDefaultRuleChainId(); + String defaultQueueName = deviceProfile.getDefaultQueueName(); + queueName = defaultQueueName != null ? defaultQueueName : ServiceQueue.MAIN; } - return ruleChainId; + + TbMsg tbMsg = TbMsg.newMsg(queueName, sessionMsgType.name(), deviceId, metaData, gson.toJson(json), ruleChainId, null); + sendToRuleEngine(tenantId, tbMsg, callback); } private class TransportTbQueueCallback implements TbQueueCallback { From a0d39a3e46c968642b7e99f8fcf3ee19683b9564 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 16:56:13 +0200 Subject: [PATCH 047/102] revert import changes from DefaultTransportService --- .../common/transport/service/DefaultTransportService.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index aabb8c927e..b1ae211ed7 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -757,8 +757,8 @@ public class DefaultTransportService implements TransportService { ruleEngineMsgProducer.send(tpi, new TbProtoQueueMsg<>(tbMsg.getId(), msg), wrappedCallback); } - protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, SessionInfoProto sessionInfo, JsonObject json, - TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { + protected void sendToRuleEngine(TenantId tenantId, DeviceId deviceId, TransportProtos.SessionInfoProto sessionInfo, JsonObject json, + TbMsgMetaData metaData, SessionMsgType sessionMsgType, TbQueueCallback callback) { DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(sessionInfo.getDeviceProfileIdMSB(), sessionInfo.getDeviceProfileIdLSB())); DeviceProfile deviceProfile = deviceProfileCache.get(deviceProfileId); RuleChainId ruleChainId; From 2211cff1e9e35e3a98a5d4fa4cf52e1a0c6193c4 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 17:00:31 +0200 Subject: [PATCH 048/102] revert DefaultTransportService --- .../service/DefaultTransportService.java | 132 +++++++----------- 1 file changed, 53 insertions(+), 79 deletions(-) diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index b1ae211ed7..089b8aaf53 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -57,41 +57,15 @@ import org.thingsboard.server.common.transport.limits.TransportRateLimitType; import org.thingsboard.server.common.transport.profile.TenantProfileUpdateResult; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.common.transport.util.JsonUtils; -import org.thingsboard.server.gen.transport.TransportProtos.ClaimDeviceMsg; -import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.EntityDeleteMsg; -import org.thingsboard.server.gen.transport.TransportProtos.EntityUpdateMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetEntityProfileResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.PostAttributeMsg; -import org.thingsboard.server.gen.transport.TransportProtos.PostTelemetryMsg; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.ProvisionDeviceResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SessionCloseNotificationProto; -import org.thingsboard.server.gen.transport.TransportProtos.SessionEvent; -import org.thingsboard.server.gen.transport.TransportProtos.SessionEventMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SessionInfoProto; -import org.thingsboard.server.gen.transport.TransportProtos.SessionType; -import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToAttributeUpdatesMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SubscribeToRPCMsg; -import org.thingsboard.server.gen.transport.TransportProtos.SubscriptionInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.ToCoreMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToDeviceRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ToServerRpcResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToTransportMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportApiResponseMsg; import org.thingsboard.server.gen.transport.TransportProtos.TransportToDeviceActorMsg; -import org.thingsboard.server.gen.transport.TransportProtos.TsKvListProto; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateBasicMqttCredRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceCredentialsResponseMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceTokenRequestMsg; -import org.thingsboard.server.gen.transport.TransportProtos.ValidateDeviceX509CertRequestMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueMsgMetadata; @@ -261,14 +235,14 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerAsyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener) { - sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, SessionType.ASYNC, listener)); + public void registerAsyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener) { + sessions.putIfAbsent(toSessionId(sessionInfo), new SessionMetaData(sessionInfo, TransportProtos.SessionType.ASYNC, listener)); } @Override - public GetEntityProfileResponseMsg getRoutingInfo(GetEntityProfileRequestMsg msg) { - TbProtoQueueMsg protoMsg = - new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); + public TransportProtos.GetEntityProfileResponseMsg getRoutingInfo(TransportProtos.GetEntityProfileRequestMsg msg) { + TbProtoQueueMsg protoMsg = + new TbProtoQueueMsg<>(UUID.randomUUID(), TransportProtos.TransportApiRequestMsg.newBuilder().setEntityProfileRequestMsg(msg).build()); try { TbProtoQueueMsg response = transportApiRequestTemplate.send(protoMsg).get(); return response.getValue().getEntityProfileResponseMsg(); @@ -278,7 +252,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, ValidateDeviceTokenRequestMsg msg, + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceTokenRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -287,7 +261,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, ValidateBasicMqttCredRequestMsg msg, + public void process(DeviceTransportType transportType, TransportProtos.ValidateBasicMqttCredRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), @@ -296,7 +270,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(DeviceTransportType transportType, ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { + public void process(DeviceTransportType transportType, TransportProtos.ValidateDeviceX509CertRequestMsg msg, TransportServiceCallback callback) { log.trace("Processing msg: {}", msg); TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setValidateX509CertRequestMsg(msg).build()); doProcess(transportType, protoMsg, callback); @@ -305,7 +279,7 @@ public class DefaultTransportService implements TransportService { private void doProcess(DeviceTransportType transportType, TbProtoQueueMsg protoMsg, TransportServiceCallback callback) { ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); + TransportProtos.ValidateDeviceCredentialsResponseMsg msg = tmp.getValue().getValidateCredResponseMsg(); ValidateDeviceCredentialsResponse.ValidateDeviceCredentialsResponseBuilder result = ValidateDeviceCredentialsResponse.builder(); if (msg.hasDeviceInfo()) { result.credentials(msg.getCredentialsBody()); @@ -328,11 +302,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { + public void process(TransportProtos.GetOrCreateDeviceFromGatewayRequestMsg requestMsg, TransportServiceCallback callback) { TbProtoQueueMsg protoMsg = new TbProtoQueueMsg<>(UUID.randomUUID(), TransportApiRequestMsg.newBuilder().setGetOrCreateDeviceRequestMsg(requestMsg).build()); log.trace("Processing msg: {}", requestMsg); ListenableFuture response = Futures.transform(transportApiRequestTemplate.send(protoMsg), tmp -> { - GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); + TransportProtos.GetOrCreateDeviceFromGatewayResponseMsg msg = tmp.getValue().getGetOrCreateDeviceResponseMsg(); GetOrCreateDeviceFromGatewayResponse.GetOrCreateDeviceFromGatewayResponseBuilder result = GetOrCreateDeviceFromGatewayResponse.builder(); if (msg.hasDeviceInfo()) { TransportDeviceInfo tdi = getTransportDeviceInfo(msg.getDeviceInfo()); @@ -347,7 +321,7 @@ public class DefaultTransportService implements TransportService { AsyncCallbackTemplate.withCallback(response, callback::onSuccess, callback::onError, transportCallbackExecutor); } - private TransportDeviceInfo getTransportDeviceInfo(DeviceInfoProto di) { + private TransportDeviceInfo getTransportDeviceInfo(TransportProtos.DeviceInfoProto di) { TransportDeviceInfo tdi = new TransportDeviceInfo(); tdi.setTenantId(new TenantId(new UUID(di.getTenantIdMSB(), di.getTenantIdLSB()))); tdi.setDeviceId(new DeviceId(new UUID(di.getDeviceIdMSB(), di.getDeviceIdLSB()))); @@ -369,7 +343,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SubscriptionInfoProto msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscriptionInfoProto msg, TransportServiceCallback callback) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -378,7 +352,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SessionEventMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SessionEventMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -387,9 +361,9 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, PostTelemetryMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostTelemetryMsg msg, TransportServiceCallback callback) { int dataPoints = 0; - for (TsKvListProto tsKv : msg.getTsKvListList()) { + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { dataPoints += tsKv.getKvCount(); } if (checkLimits(sessionInfo, msg, callback, dataPoints, TELEMETRY)) { @@ -397,19 +371,20 @@ public class DefaultTransportService implements TransportService { TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); DeviceId deviceId = new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); MsgPackCallback packCallback = new MsgPackCallback(msg.getTsKvListCount(), callback); - for (TsKvListProto tsKv : msg.getTsKvListList()) { + for (TransportProtos.TsKvListProto tsKv : msg.getTsKvListList()) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); metaData.putValue("ts", tsKv.getTs() + ""); JsonObject json = JsonUtils.getJsonObject(tsKv.getKvList()); sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.POST_TELEMETRY_REQUEST, packCallback); + } } } @Override - public void process(SessionInfoProto sessionInfo, PostAttributeMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.PostAttributeMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback, msg.getKvCount(), TELEMETRY)) { reportActivityInternal(sessionInfo); TenantId tenantId = new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); @@ -424,7 +399,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, GetAttributeRequestMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.GetAttributeRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -433,7 +408,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToAttributeUpdatesMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToAttributes(!msg.getUnsubscribe()); @@ -443,7 +418,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, SubscribeToRPCMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.SubscribeToRPCMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { SessionMetaData sessionMetaData = reportActivityInternal(sessionInfo); sessionMetaData.setSubscribedToRPC(!msg.getUnsubscribe()); @@ -453,7 +428,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToDeviceRpcResponseMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -468,13 +443,13 @@ public class DefaultTransportService implements TransportService { if (md != null) { SessionMsgListener listener = md.getListener(); transportCallbackExecutor.submit(() -> { - ToServerRpcResponseMsg responseMsg = - ToServerRpcResponseMsg.newBuilder() + TransportProtos.ToServerRpcResponseMsg responseMsg = + TransportProtos.ToServerRpcResponseMsg.newBuilder() .setRequestId(data.getRequestId()) .setError("timeout").build(); listener.onToServerRpcResponse(responseMsg); }); - if (md.getSessionType() == SessionType.SYNC) { + if (md.getSessionType() == TransportProtos.SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { @@ -484,7 +459,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, ToServerRpcRequestMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ToServerRpcRequestMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); UUID sessionId = toSessionId(sessionInfo); @@ -500,7 +475,6 @@ public class DefaultTransportService implements TransportService { metaData.putValue("requestId", Integer.toString(msg.getRequestId())); metaData.putValue("serviceId", serviceInfoProvider.getServiceId()); metaData.putValue("sessionId", sessionId.toString()); - sendToRuleEngine(tenantId, deviceId, sessionInfo, json, metaData, SessionMsgType.TO_SERVER_RPC_REQUEST, new TransportTbQueueCallback(callback)); String requestId = sessionId + "-" + msg.getRequestId(); @@ -510,7 +484,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void process(SessionInfoProto sessionInfo, ClaimDeviceMsg msg, TransportServiceCallback callback) { + public void process(TransportProtos.SessionInfoProto sessionInfo, TransportProtos.ClaimDeviceMsg msg, TransportServiceCallback callback) { if (checkLimits(sessionInfo, msg, callback)) { reportActivityInternal(sessionInfo); sendToDeviceActor(sessionInfo, TransportToDeviceActorMsg.newBuilder().setSessionInfo(sessionInfo) @@ -519,11 +493,11 @@ public class DefaultTransportService implements TransportService { } @Override - public void reportActivity(SessionInfoProto sessionInfo) { + public void reportActivity(TransportProtos.SessionInfoProto sessionInfo) { reportActivityInternal(sessionInfo); } - private SessionMetaData reportActivityInternal(SessionInfoProto sessionInfo) { + private SessionMetaData reportActivityInternal(TransportProtos.SessionInfoProto sessionInfo) { UUID sessionId = toSessionId(sessionInfo); SessionMetaData sessionMetaData = sessions.get(sessionId); if (sessionMetaData != null) { @@ -536,7 +510,7 @@ public class DefaultTransportService implements TransportService { long expTime = System.currentTimeMillis() - sessionInactivityTimeout; sessions.forEach((uuid, sessionMD) -> { long lastActivityTime = sessionMD.getLastActivityTime(); - SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); + TransportProtos.SessionInfoProto sessionInfo = sessionMD.getSessionInfo(); if (sessionInfo.getGwSessionIdMSB() > 0 && sessionInfo.getGwSessionIdLSB() > 0) { SessionMetaData gwMetaData = sessions.get(new UUID(sessionInfo.getGwSessionIdMSB(), sessionInfo.getGwSessionIdLSB())); @@ -548,13 +522,13 @@ public class DefaultTransportService implements TransportService { if (log.isDebugEnabled()) { log.debug("[{}] Session has expired due to last activity time: {}", toSessionId(sessionInfo), lastActivityTime); } - process(sessionInfo, getSessionEventMsg(SessionEvent.CLOSED), null); + process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); sessions.remove(uuid); - sessionMD.getListener().onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); + sessionMD.getListener().onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); } else { if (lastActivityTime > sessionMD.getLastReportedActivityTime()) { final long lastActivityTimeFinal = lastActivityTime; - process(sessionInfo, SubscriptionInfoProto.newBuilder() + process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder() .setAttributeSubscription(sessionMD.isSubscribedToAttributes()) .setRpcSubscription(sessionMD.isSubscribedToRPC()) .setLastActivityTime(lastActivityTime).build(), new TransportServiceCallback() { @@ -574,12 +548,12 @@ public class DefaultTransportService implements TransportService { } @Override - public void registerSyncSession(SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { - SessionMetaData currentSession = new SessionMetaData(sessionInfo, SessionType.SYNC, listener); + public void registerSyncSession(TransportProtos.SessionInfoProto sessionInfo, SessionMsgListener listener, long timeout) { + SessionMetaData currentSession = new SessionMetaData(sessionInfo, TransportProtos.SessionType.SYNC, listener); sessions.putIfAbsent(toSessionId(sessionInfo), currentSession); ScheduledFuture executorFuture = schedulerExecutor.schedule(() -> { - listener.onRemoteSessionCloseCommand(SessionCloseNotificationProto.getDefaultInstance()); + listener.onRemoteSessionCloseCommand(TransportProtos.SessionCloseNotificationProto.getDefaultInstance()); deregisterSession(sessionInfo); }, timeout, TimeUnit.MILLISECONDS); @@ -587,7 +561,7 @@ public class DefaultTransportService implements TransportService { } @Override - public void deregisterSession(SessionInfoProto sessionInfo) { + public void deregisterSession(TransportProtos.SessionInfoProto sessionInfo) { SessionMetaData currentSession = sessions.get(toSessionId(sessionInfo)); if (currentSession != null && currentSession.hasScheduledFuture()) { log.debug("Stopping scheduler to avoid resending response if request has been ack."); @@ -600,12 +574,12 @@ public class DefaultTransportService implements TransportService { private TransportRateLimitType[] TELEMETRY = TransportRateLimitType.values(); @Override - public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback) { return checkLimits(sessionInfo, msg, callback, 0, DEFAULT); } @Override - public boolean checkLimits(SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { + public boolean checkLimits(TransportProtos.SessionInfoProto sessionInfo, Object msg, TransportServiceCallback callback, int dataPoints, TransportRateLimitType... limits) { if (log.isTraceEnabled()) { log.trace("[{}] Processing msg: {}", toSessionId(sessionInfo), msg); } @@ -626,7 +600,7 @@ public class DefaultTransportService implements TransportService { } } - protected void processToTransportMsg(ToTransportMsg toSessionMsg) { + protected void processToTransportMsg(TransportProtos.ToTransportMsg toSessionMsg) { UUID sessionId = new UUID(toSessionMsg.getSessionIdMSB(), toSessionMsg.getSessionIdLSB()); SessionMetaData md = sessions.get(sessionId); if (md != null) { @@ -650,12 +624,12 @@ public class DefaultTransportService implements TransportService { listener.onToServerRpcResponse(toSessionMsg.getToServerResponse()); } }); - if (md.getSessionType() == SessionType.SYNC) { + if (md.getSessionType() == TransportProtos.SessionType.SYNC) { deregisterSession(md.getSessionInfo()); } } else { if (toSessionMsg.hasEntityUpdateMsg()) { - EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); + TransportProtos.EntityUpdateMsg msg = toSessionMsg.getEntityUpdateMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { DeviceProfile deviceProfile = deviceProfileCache.put(msg.getData()); @@ -676,7 +650,7 @@ public class DefaultTransportService implements TransportService { } } } else if (toSessionMsg.hasEntityDeleteMsg()) { - EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); + TransportProtos.EntityDeleteMsg msg = toSessionMsg.getEntityDeleteMsg(); EntityType entityType = EntityType.valueOf(msg.getEntityType()); UUID entityUuid = new UUID(msg.getEntityIdMSB(), msg.getEntityIdLSB()); if (EntityType.DEVICE_PROFILE.equals(entityType)) { @@ -707,29 +681,29 @@ public class DefaultTransportService implements TransportService { }); } - protected UUID toSessionId(SessionInfoProto sessionInfo) { + protected UUID toSessionId(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getSessionIdMSB(), sessionInfo.getSessionIdLSB()); } - protected UUID getRoutingKey(SessionInfoProto sessionInfo) { + protected UUID getRoutingKey(TransportProtos.SessionInfoProto sessionInfo) { return new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB()); } - protected TenantId getTenantId(SessionInfoProto sessionInfo) { + protected TenantId getTenantId(TransportProtos.SessionInfoProto sessionInfo) { return new TenantId(new UUID(sessionInfo.getTenantIdMSB(), sessionInfo.getTenantIdLSB())); } - protected DeviceId getDeviceId(SessionInfoProto sessionInfo) { + protected DeviceId getDeviceId(TransportProtos.SessionInfoProto sessionInfo) { return new DeviceId(new UUID(sessionInfo.getDeviceIdMSB(), sessionInfo.getDeviceIdLSB())); } - public static SessionEventMsg getSessionEventMsg(SessionEvent event) { - return SessionEventMsg.newBuilder() - .setSessionType(SessionType.ASYNC) + public static TransportProtos.SessionEventMsg getSessionEventMsg(TransportProtos.SessionEvent event) { + return TransportProtos.SessionEventMsg.newBuilder() + .setSessionType(TransportProtos.SessionType.ASYNC) .setEvent(event).build(); } - protected void sendToDeviceActor(SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { + protected void sendToDeviceActor(TransportProtos.SessionInfoProto sessionInfo, TransportToDeviceActorMsg toDeviceActorMsg, TransportServiceCallback callback) { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, getTenantId(sessionInfo), getDeviceId(sessionInfo)); if (log.isTraceEnabled()) { log.trace("[{}][{}] Pushing to topic {} message {}", getTenantId(sessionInfo), getDeviceId(sessionInfo), tpi.getFullTopicName(), toDeviceActorMsg); From 6cc8eada320b9fb716da67f51939d3e94c024852 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 28 Oct 2020 11:20:33 +0200 Subject: [PATCH 049/102] base url improvements --- .../modules/home/pages/admin/general-settings.component.html | 4 ++++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 ++ 2 files changed, 6 insertions(+) diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html index 1bd8c60cc2..1a5c4da887 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html @@ -35,6 +35,10 @@ {{ 'admin.base-url-required' | translate }}
+ + {{ 'admin.prohibit-different-url' | translate }} + +
admin.prohibit-different-url-hint
\n
\n\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
tb.rulenode.select-queue-hint
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce],imports:[r.CommonModule,a.SharedModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce]})],e)}(),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Fe,xe,Te,qe,Se,Ie,ke,Ne],imports:[r.CommonModule,a.SharedModule,le],exports:[Fe,xe,Te,qe,Se,Ie,ke,Ne]})],e)}(),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(){function e(){}return e=b([t.NgModule({declarations:[Ee,Ae,Le,Me,Pe,Re,we,Oe],imports:[r.CommonModule,a.SharedModule,le],exports:[Ee,Ae,Le,Me,Pe,Re,we,Oe]})],e)}(),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),je=function(){function e(){}return e=b([t.NgModule({declarations:[Ke,Be,Ue],imports:[r.CommonModule,a.SharedModule,le],exports:[Ke,Be,Ue]})],e)}(),He=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ve,Ve,De,je,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=He,e.ɵa=F,e.ɵb=ve,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=le,e.ɵbe=ne,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=Ve,e.ɵbj=Fe,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=De,e.ɵbs=Ee,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=je,e.ɵcb=Ke,e.ɵcc=Be,e.ɵcd=Ue,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); + ***************************************************************************** */var g=function(e,t){return(g=Object.setPrototypeOf||{__proto__:[]}instanceof Array&&function(e,t){e.__proto__=t}||function(e,t){for(var r in t)Object.prototype.hasOwnProperty.call(t,r)&&(e[r]=t[r])})(e,t)};function y(e,t){function r(){this.constructor=e}g(e,t),e.prototype=null===t?Object.create(t):(r.prototype=t.prototype,new r)}function b(e,t,r,n){var a,o=arguments.length,i=o<3?t:null===n?n=Object.getOwnPropertyDescriptor(t,r):n;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)i=Reflect.decorate(e,t,r,n);else for(var l=e.length-1;l>=0;l--)(a=e[l])&&(i=(o<3?a(i):o>3?a(t,r,i):a(t,r))||i);return o>3&&i&&Object.defineProperty(t,r,i),i}function h(e,t){if("object"==typeof Reflect&&"function"==typeof Reflect.metadata)return Reflect.metadata(e,t)}Object.create;function C(e){var t="function"==typeof Symbol&&Symbol.iterator,r=t&&e[t],n=0;if(r)return r.call(e);if(e&&"number"==typeof e.length)return{next:function(){return e&&n>=e.length&&(e=void 0),{value:e&&e[n++],done:!e}}};throw new TypeError(t?"Object is not iterable.":"Symbol.iterator is not defined.")}Object.create;var v,F=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.emptyConfigForm},r.prototype.onConfigurationSet=function(e){this.emptyConfigForm=this.fb.group({})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-node-empty-config",template:"
"}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),x=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.attributeScopes=Object.keys(a.AttributeScope),n.telemetryTypeTranslationsMap=a.telemetryTypeTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.attributesConfigForm},r.prototype.onConfigurationSet=function(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[i.Validators.required]],notifyDevice:[!e||e.scope,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-attributes-config",template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),T=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.timeseriesConfigForm},r.prototype.onConfigurationSet=function(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-timeseries-config",template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),q=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcRequestConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-request-config",template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),S=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.logConfigForm},r.prototype.onConfigurationSet=function(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.logConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-log-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),I=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.assignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),k=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.clearAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],alarmType:[e?e.alarmType:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-clear-alarm-config",template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),N=function(e){function r(t,r,n,o){var i=e.call(this,t)||this;return i.store=t,i.fb=r,i.nodeScriptTestService=n,i.translate=o,i.alarmSeverities=Object.keys(a.AlarmSeverity),i.alarmSeverityTranslationMap=a.alarmSeverityTranslations,i.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],i}return y(r,e),r.prototype.configForm=function(){return this.createAlarmConfigForm},r.prototype.onConfigurationSet=function(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[i.Validators.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]]})},r.prototype.validatorTriggers=function(){return["useMessageAlarmData"]},r.prototype.updateValidators=function(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([i.Validators.required]),this.createAlarmConfigForm.get("severity").setValidators([i.Validators.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})},r.prototype.testScript=function(){var e=this,t=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(t,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(t)}))},r.prototype.removeKey=function(e,t){var r=this.createAlarmConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.createAlarmConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.createAlarmConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.createAlarmConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-create-alarm-config",template:'
\n \n \n \n
\n \n
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),V=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.createRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[i.Validators.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["entityType"]},r.prototype.updateValidators=function(e){var t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==a.EntityType.DEVICE&&t!==a.EntityType.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([i.Validators.required]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-create-relation-config",template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),E=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgDelayConfigForm},r.prototype.onConfigurationSet=function(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(1e5)]]})},r.prototype.validatorTriggers=function(){return["useMetadataPeriodInSecondsPatterns"]},r.prototype.updateValidators=function(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([i.Validators.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([i.Validators.required,i.Validators.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-delay-config",template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),A=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n}return y(r,e),r.prototype.configForm=function(){return this.deleteRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[i.Validators.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[i.Validators.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.prototype.validatorTriggers=function(){return["deleteForSingleEntity","entityType"]},r.prototype.updateValidators=function(e){var t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,r=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&r?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([i.Validators.required]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-delete-relation-config",template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),L=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.generatorConfigForm},r.prototype.onConfigurationSet=function(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[i.Validators.required,i.Validators.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[i.Validators.required,i.Validators.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.prepareInputConfig=function(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e},r.prototype.prepareOutputConfig=function(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e},r.prototype.testScript=function(){var e=this,t=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId).subscribe((function(t){t&&e.generatorConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-action-node-generator-config",template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent);!function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR"}(v||(v={}));var M,P=new Map([[v.CUSTOMER,"tb.rulenode.originator-customer"],[v.TENANT,"tb.rulenode.originator-tenant"],[v.RELATED,"tb.rulenode.originator-related"],[v.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"]]);!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(M||(M={}));var R,w=new Map([[M.CIRCLE,"tb.rulenode.perimeter-circle"],[M.POLYGON,"tb.rulenode.perimeter-polygon"]]);!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(R||(R={}));var O,D=new Map([[R.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[R.SECONDS,"tb.rulenode.time-unit-seconds"],[R.MINUTES,"tb.rulenode.time-unit-minutes"],[R.HOURS,"tb.rulenode.time-unit-hours"],[R.DAYS,"tb.rulenode.time-unit-days"]]);!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(O||(O={}));var K,B=new Map([[O.METER,"tb.rulenode.range-unit-meter"],[O.KILOMETER,"tb.rulenode.range-unit-kilometer"],[O.FOOT,"tb.rulenode.range-unit-foot"],[O.MILE,"tb.rulenode.range-unit-mile"],[O.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(K||(K={}));var U,j,H,G=new Map([[K.TITLE,"tb.rulenode.entity-details-title"],[K.COUNTRY,"tb.rulenode.entity-details-country"],[K.STATE,"tb.rulenode.entity-details-state"],[K.ZIP,"tb.rulenode.entity-details-zip"],[K.ADDRESS,"tb.rulenode.entity-details-address"],[K.ADDRESS2,"tb.rulenode.entity-details-address2"],[K.PHONE,"tb.rulenode.entity-details-phone"],[K.EMAIL,"tb.rulenode.entity-details-email"],[K.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(U||(U={})),function(e){e.ASC="ASC",e.DESC="DESC"}(j||(j={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(H||(H={}));var z,$=new Map([[H.STANDARD,"tb.rulenode.sqs-queue-standard"],[H.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Q=["anonymous","basic","cert.PEM"],_=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),W=["sas","cert.PEM"],J=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(z||(z={}));var Y=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Z=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]),X=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.geoActionConfigForm},r.prototype.onConfigurationSet=function(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[i.Validators.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterType").setValidators([]):this.geoActionConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoActionConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoActionConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.msgCountConfigForm},r.prototype.onConfigurationSet=function(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[i.Validators.required,i.Validators.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-msg-count-config",template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.rpcReplyConfigForm},r.prototype.onConfigurationSet=function(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rpc-reply-config",template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.saveToCustomTableConfigForm},r.prototype.onConfigurationSet=function(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[i.Validators.required]],fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-custom-table-config",template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ne=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.translate=r,o.injector=n,o.fb=a,o.propagateChange=null,o.valueChangeSubscription=null,o}var a;return y(r,e),a=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){this.ngControl=this.injector.get(i.NgControl),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))},r.prototype.keyValsFormArray=function(){return this.kvListFormGroup.get("keyVals")},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t,r,n=this;this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();var a=[];if(e)try{for(var o=C(Object.keys(e)),l=o.next();!l.done;l=o.next()){var s=l.value;Object.prototype.hasOwnProperty.call(e,s)&&a.push(this.fb.group({key:[s,[i.Validators.required]],value:[e[s],[i.Validators.required]]}))}}catch(e){t={error:e}}finally{try{l&&!l.done&&(r=o.return)&&r.call(o)}finally{if(t)throw t.error}}this.kvListFormGroup.setControl("keyVals",this.fb.array(a)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((function(){n.updateModel()}))},r.prototype.removeKeyVal=function(e){this.kvListFormGroup.get("keyVals").removeAt(e)},r.prototype.addKeyVal=function(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[i.Validators.required]],value:["",[i.Validators.required]]}))},r.prototype.validate=function(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}},r.prototype.updateModel=function(){var e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{var t={};e.forEach((function(e){t[e.key]=e.value})),this.propagateChange(t)}},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:t.Injector},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",String)],r.prototype,"requiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"keyRequiredText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valText",void 0),b([t.Input(),h("design:type",String)],r.prototype,"valRequiredText",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=a=b([t.Component({selector:"tb-kv-map-config",template:'
\n
\n {{ keyText }}\n {{ valText }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n \n
\n \n
\n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return a})),multi:!0},{provide:i.NG_VALIDATORS,useExisting:t.forwardRef((function(){return a})),multi:!0}],styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:rgba(0,0,0,.54);font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .row{padding-top:5px;max-height:40px}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0;max-height:40px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0}"]}),h("design:paramtypes",[o.Store,n.TranslateService,t.Injector,i.FormBuilder])],r)}(a.PageComponent),ae=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.entityType=a.EntityType,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[i.Validators.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((function(t){e.deviceRelationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-device-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.directionTypes=Object.keys(a.EntitySearchDirection),n.directionTypeTranslations=a.entitySearchDirectionTranslations,n.propagateChange=null,n}var n;return y(r,e),n=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.ngOnInit=function(){var e=this;this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[i.Validators.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((function(t){e.relationsQueryFormGroup.valid?e.propagateChange(t):e.propagateChange(null)}))},r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})},r.prototype.writeValue=function(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),r=n=b([t.Component({selector:"tb-relations-query-config",template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return n})),multi:!0}]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.PageComponent),ie=function(e){function r(t,r,n,o){var i,l,m=e.call(this,t)||this;m.store=t,m.translate=r,m.truncate=n,m.fb=o,m.placeholder="tb.rulenode.message-type",m.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],m.messageTypes=[],m.messageTypesList=[],m.searchText="",m.propagateChange=function(e){},m.messageTypeConfigForm=m.fb.group({messageType:[null]});try{for(var u=C(Object.keys(a.MessageType)),d=u.next();!d.done;d=u.next()){var p=d.value;m.messageTypesList.push({name:a.messageTypeNames.get(a.MessageType[p]),value:p})}}catch(e){i={error:e}}finally{try{d&&!d.done&&(l=u.return)&&l.call(u)}finally{if(i)throw i.error}}return m}var l;return y(r,e),l=r,Object.defineProperty(r.prototype,"required",{get:function(){return this.requiredValue},set:function(e){this.requiredValue=u.coerceBooleanProperty(e)},enumerable:!0,configurable:!0}),r.prototype.registerOnChange=function(e){this.propagateChange=e},r.prototype.registerOnTouched=function(e){},r.prototype.ngOnInit=function(){var e=this;this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(t){return e.fetchMessageTypes(t)})),f.share())},r.prototype.ngAfterViewInit=function(){},r.prototype.setDisabledState=function(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})},r.prototype.writeValue=function(e){var t=this;this.searchText="",this.messageTypes.length=0,e&&e.forEach((function(e){var r=t.messageTypesList.find((function(t){return t.value===e}));r?t.messageTypes.push({name:r.name,value:r.value}):t.messageTypes.push({name:e,value:e})}))},r.prototype.displayMessageTypeFn=function(e){return e?e.name:void 0},r.prototype.textIsNotEmpty=function(e){return!!(e&&null!=e&&e.length>0)},r.prototype.createMessageType=function(e,t){e.preventDefault(),this.transformMessageType(t)},r.prototype.add=function(e){this.transformMessageType(e.value)},r.prototype.fetchMessageTypes=function(e){if(this.searchText=e,this.searchText&&this.searchText.length){var t=this.searchText.toUpperCase();return c.of(this.messageTypesList.filter((function(e){return e.name.toUpperCase().includes(t)})))}return c.of(this.messageTypesList)},r.prototype.transformMessageType=function(e){if((e||"").trim()){var t=null,r=e.trim(),n=this.messageTypesList.find((function(e){return e.name===r}));(t=n?{name:n.name,value:n.value}:{name:r,value:r})&&this.addMessageType(t)}this.clear("")},r.prototype.remove=function(e){var t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())},r.prototype.selected=function(e){this.addMessageType(e.option.value),this.clear("")},r.prototype.addMessageType=function(e){-1===this.messageTypes.findIndex((function(t){return t.value===e.value}))&&(this.messageTypes.push(e),this.updateModel())},r.prototype.onFocus=function(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((function(){t.messageTypeInput.nativeElement.blur(),t.messageTypeInput.nativeElement.focus()}),0)},r.prototype.updateModel=function(){var e=this.messageTypes.map((function(e){return e.value}));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:a.TruncatePipe},{type:i.FormBuilder}]},b([t.Input(),h("design:type",Boolean),h("design:paramtypes",[Boolean])],r.prototype,"required",null),b([t.Input(),h("design:type",String)],r.prototype,"label",void 0),b([t.Input(),h("design:type",Object)],r.prototype,"placeholder",void 0),b([t.Input(),h("design:type",Boolean)],r.prototype,"disabled",void 0),b([t.ViewChild("chipList",{static:!1}),h("design:type",d.MatChipList)],r.prototype,"chipList",void 0),b([t.ViewChild("messageTypeAutocomplete",{static:!1}),h("design:type",p.MatAutocomplete)],r.prototype,"matAutocomplete",void 0),b([t.ViewChild("messageTypeInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"messageTypeInput",void 0),r=l=b([t.Component({selector:"tb-message-types-config",template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',providers:[{provide:i.NG_VALUE_ACCESSOR,useExisting:t.forwardRef((function(){return l})),multi:!0}]}),h("design:paramtypes",[o.Store,n.TranslateService,a.TruncatePipe,i.FormBuilder])],r)}(a.PageComponent),le=function(){function e(){}return e=b([t.NgModule({declarations:[ne,ae,oe,ie],imports:[r.CommonModule,a.SharedModule,m.HomeComponentsModule],exports:[ne,ae,oe,ie]})],e)}(),se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.unassignCustomerConfigForm},r.prototype.onConfigurationSet=function(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[i.Validators.required]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[i.Validators.required,i.Validators.min(0)]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-un-assign-to-customer-config",template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.snsConfigForm},r.prototype.onConfigurationSet=function(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[i.Validators.required]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sns-config",template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.sqsQueueType=H,n.sqsQueueTypes=Object.keys(H),n.sqsQueueTypeTranslationsMap=$,n}return y(r,e),r.prototype.configForm=function(){return this.sqsConfigForm},r.prototype.onConfigurationSet=function(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[i.Validators.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[i.Validators.required]],delaySeconds:[e?e.delaySeconds:null,[i.Validators.min(0),i.Validators.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[i.Validators.required]],secretAccessKey:[e?e.secretAccessKey:null,[i.Validators.required]],region:[e?e.region:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-sqs-config",template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),de=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.pubSubConfigForm},r.prototype.onConfigurationSet=function(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[i.Validators.required]],topicName:[e?e.topicName:null,[i.Validators.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[i.Validators.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[i.Validators.required]],messageAttributes:[e?e.messageAttributes:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-pub-sub-config",template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.ackValues=["all","-1","0","1"],n.ToByteStandartCharsetTypesValues=Y,n.ToByteStandartCharsetTypeTranslationMap=Z,n}return y(r,e),r.prototype.configForm=function(){return this.kafkaConfigForm},r.prototype.onConfigurationSet=function(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],bootstrapServers:[e?e.bootstrapServers:null,[i.Validators.required]],retries:[e?e.retries:null,[i.Validators.min(0)]],batchSize:[e?e.batchSize:null,[i.Validators.min(0)]],linger:[e?e.linger:null,[i.Validators.min(0)]],bufferMemory:[e?e.bufferMemory:null,[i.Validators.min(0)]],acks:[e?e.acks:null,[i.Validators.required]],keySerializer:[e?e.keySerializer:null,[i.Validators.required]],valueSerializer:[e?e.valueSerializer:null,[i.Validators.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})},r.prototype.validatorTriggers=function(){return["addMetadataKeyValuesAsKafkaHeaders"]},r.prototype.updateValidators=function(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([i.Validators.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-kafka-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allMqttCredentialsTypes=Q,n.mqttCredentialsTypeTranslationsMap=_,n}return y(r,e),r.prototype.configForm=function(){return this.mqttConfigForm},r.prototype.onConfigurationSet=function(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],username:[e&&e.credentials?e.credentials.username:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;switch(t){case"anonymous":e.credentials={type:t};break;case"basic":e.credentials={type:t,username:e.credentials.username,password:e.credentials.password};break;case"cert.PEM":delete e.credentials.username}return e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.mqttConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("username").setValidators([]),t.get("password").setValidators([]),t.get("caCert").setValidators([]),t.get("caCertFileName").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"anonymous":break;case"basic":t.get("username").setValidators([i.Validators.required]),t.get("password").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("caCert").setValidators([i.Validators.required]),t.get("caCertFileName").setValidators([i.Validators.required]),t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("username").updateValueAndValidity({emitEvent:e}),t.get("password").updateValueAndValidity({emitEvent:e}),t.get("caCert").updateValueAndValidity({emitEvent:e}),t.get("caCertFileName").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-mqtt-config",template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n \n tb.rulenode.credentials\n \n {{ mqttCredentialsTypeTranslationsMap.get(mqttConfigForm.get(\'credentials\').get(\'type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ mqttCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],n}return y(r,e),r.prototype.configForm=function(){return this.rabbitMqConfigForm},r.prototype.onConfigurationSet=function(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[i.Validators.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[i.Validators.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rabbit-mq-config",template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ge=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.proxySchemes=["http","https"],n.httpRequestTypes=Object.keys(z),n}return y(r,e),r.prototype.configForm=function(){return this.restApiCallConfigForm},r.prototype.onConfigurationSet=function(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[i.Validators.required]],requestMethod:[e?e.requestMethod:null,[i.Validators.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[i.Validators.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]]})},r.prototype.validatorTriggers=function(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]},r.prototype.updateValidators=function(e){var t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,r=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,n=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;n&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(n?[i.Validators.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(n?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([i.Validators.min(0)])),r?this.restApiCallConfigForm.get("maxQueueSize").setValidators([i.Validators.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-rest-api-call-config",template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n \n \n \n tb.rulenode.max-parallel-requests-count\n \n \n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ye=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.smtpProtocols=["smtp","smtps"],n.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"],n}return y(r,e),r.prototype.configForm=function(){return this.sendEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})},r.prototype.validatorTriggers=function(){return["useSystemSmtpSettings","enableProxy"]},r.prototype.updateValidators=function(e){var t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,r=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([i.Validators.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([i.Validators.required,i.Validators.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(r?[i.Validators.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(r?[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-send-email-config",template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),be=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.serviceType=a.ServiceType.TB_RULE_ENGINE,n}return y(r,e),r.prototype.configForm=function(){return this.checkPointConfigForm},r.prototype.onConfigurationSet=function(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-check-point-config",template:'
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),he=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allAzureIotHubCredentialsTypes=W,n.azureIotHubCredentialsTypeTranslationsMap=J,n}return y(r,e),r.prototype.configForm=function(){return this.azureIotHubConfigForm},r.prototype.onConfigurationSet=function(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[i.Validators.required]],host:[e?e.host:null,[i.Validators.required]],port:[e?e.port:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[i.Validators.required,i.Validators.min(1),i.Validators.max(200)]],clientId:[e?e.clientId:null,[i.Validators.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[i.Validators.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})},r.prototype.prepareOutputConfig=function(e){var t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e},r.prototype.validatorTriggers=function(){return["credentials.type"]},r.prototype.updateValidators=function(e){var t=this.azureIotHubConfigForm.get("credentials"),r=t.get("type").value;switch(e&&t.reset({type:r},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),r){case"sas":t.get("sasKey").setValidators([i.Validators.required]);break;case"cert.PEM":t.get("privateKey").setValidators([i.Validators.required]),t.get("privateKeyFileName").setValidators([i.Validators.required]),t.get("cert").setValidators([i.Validators.required]),t.get("certFileName").setValidators([i.Validators.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-action-node-azure-iot-hub-config",template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ce=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.deviceProfile},r.prototype.onConfigurationSet=function(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,i.Validators.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,i.Validators.required]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-device-profile-config",template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),ve=function(){function e(){}return e=b([t.NgModule({declarations:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce],imports:[r.CommonModule,a.SharedModule,le],exports:[x,T,q,S,I,k,N,V,E,A,L,X,ee,te,re,se,me,ue,de,pe,ce,fe,ge,ye,be,he,Ce]})],e)}(),Fe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.checkMessageConfigForm},r.prototype.onConfigurationSet=function(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})},r.prototype.validateConfig=function(){var e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0},r.prototype.removeMessageName=function(e){var t=this.checkMessageConfigForm.get("messageNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))},r.prototype.removeMetadataName=function(e){var t=this.checkMessageConfigForm.get("metadataNames").value,r=t.indexOf(e);r>=0&&(t.splice(r,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))},r.prototype.addMessageName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("messageNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("messageNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.prototype.addMetadataName=function(e){var t=e.input,r=e.value;if((r||"").trim()){r=r.trim();var n=this.checkMessageConfigForm.get("metadataNames").value;n&&-1!==n.indexOf(r)||(n||(n=[]),n.push(r),this.checkMessageConfigForm.get("metadataNames").setValue(n,{emitEvent:!0}))}t&&(t.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-message-config",template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),xe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.entitySearchDirection=Object.keys(a.EntitySearchDirection),n.entitySearchDirectionTranslationsMap=a.entitySearchDirectionTranslations,n}return y(r,e),r.prototype.configForm=function(){return this.checkRelationConfigForm},r.prototype.onConfigurationSet=function(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[i.Validators.required]:[]],relationType:[e?e.relationType:null,[i.Validators.required]]})},r.prototype.validatorTriggers=function(){return["checkForSingleEntity"]},r.prototype.updateValidators=function(e){var t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[i.Validators.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-check-relation-config",template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Te=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.perimeterType=M,n.perimeterTypes=Object.keys(M),n.perimeterTypeTranslationMap=w,n.rangeUnits=Object.keys(O),n.rangeUnitTranslationMap=B,n}return y(r,e),r.prototype.configForm=function(){return this.geoFilterConfigForm},r.prototype.onConfigurationSet=function(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[i.Validators.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[i.Validators.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterType:[e?e.perimeterType:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]},r.prototype.updateValidators=function(e){var t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,r=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterType").setValidators([]):this.geoFilterConfigForm.get("perimeterType").setValidators([i.Validators.required]),t||r!==M.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([i.Validators.required,i.Validators.min(-90),i.Validators.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([i.Validators.required,i.Validators.min(-180),i.Validators.max(180)]),this.geoFilterConfigForm.get("range").setValidators([i.Validators.required,i.Validators.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([i.Validators.required])),t||r!==M.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([i.Validators.required]),this.geoFilterConfigForm.get("perimeterType").updateValueAndValidity({emitEvent:!1}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-gps-geofencing-config",template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n
\n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),qe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.messageTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-message-type-config",template:'
\n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Se=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.allowedEntityTypes=[a.EntityType.DEVICE,a.EntityType.ASSET,a.EntityType.ENTITY_VIEW,a.EntityType.TENANT,a.EntityType.CUSTOMER,a.EntityType.USER,a.EntityType.DASHBOARD,a.EntityType.RULE_CHAIN,a.EntityType.RULE_NODE],n}return y(r,e),r.prototype.configForm=function(){return this.originatorTypeConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-filter-node-originator-type-config",template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ie=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),ke=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.switchConfigForm},r.prototype.onConfigurationSet=function(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.switchConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-filter-node-switch-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ne=function(e){function r(t,r,n){var o,l,s=e.call(this,t)||this;s.store=t,s.translate=r,s.fb=n,s.alarmStatusTranslationsMap=a.alarmStatusTranslations,s.alarmStatusList=[],s.searchText="",s.displayStatusFn=s.displayStatus.bind(s);try{for(var m=C(Object.keys(a.AlarmStatus)),u=m.next();!u.done;u=m.next()){var d=u.value;s.alarmStatusList.push(a.AlarmStatus[d])}}catch(e){o={error:e}}finally{try{u&&!u.done&&(l=m.return)&&l.call(m)}finally{if(o)throw o.error}}return s.statusFormControl=new i.FormControl(""),s.filteredAlarmStatus=s.statusFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return s.fetchAlarmStatus(e)})),f.share()),s}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.alarmStatusConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[i.Validators.required]]})},r.prototype.displayStatus=function(e){return e?this.translate.instant(a.alarmStatusTranslations.get(e)):void 0},r.prototype.fetchAlarmStatus=function(e){var t=this,r=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){var n=this.searchText.toUpperCase();return c.of(r.filter((function(e){return t.translate.instant(a.alarmStatusTranslations.get(a.AlarmStatus[e])).toUpperCase().includes(n)})))}return c.of(r)},r.prototype.alarmStatusSelected=function(e){this.addAlarmStatus(e.option.value),this.clear("")},r.prototype.removeAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}},r.prototype.addAlarmStatus=function(e){var t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))},r.prototype.getAlarmStatusList=function(){var e=this;return this.alarmStatusList.filter((function(t){return-1===e.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)}))},r.prototype.onAlarmStatusInputFocus=function(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.alarmStatusInput.nativeElement.blur(),t.alarmStatusInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("alarmStatusInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"alarmStatusInput",void 0),r=b([t.Component({selector:"tb-filter-node-check-alarm-status-config",template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n'}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ve=function(){function e(){}return e=b([t.NgModule({declarations:[Fe,xe,Te,qe,Se,Ie,ke,Ne],imports:[r.CommonModule,a.SharedModule,le],exports:[Fe,xe,Te,qe,Se,Ie,ke,Ne]})],e)}(),Ee=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.customerAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-customer-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Ae=function(e){function r(t,r,n){var a,o,l=e.call(this,t)||this;l.store=t,l.translate=r,l.fb=n,l.entityDetailsTranslationsMap=G,l.entityDetailsList=[],l.searchText="",l.displayDetailsFn=l.displayDetails.bind(l);try{for(var s=C(Object.keys(K)),m=s.next();!m.done;m=s.next()){var u=m.value;l.entityDetailsList.push(K[u])}}catch(e){a={error:e}}finally{try{m&&!m.done&&(o=s.return)&&o.call(s)}finally{if(a)throw a.error}}return l.detailsFormControl=new i.FormControl(""),l.filteredEntityDetails=l.detailsFormControl.valueChanges.pipe(f.startWith(""),f.map((function(e){return e||""})),f.mergeMap((function(e){return l.fetchEntityDetails(e)})),f.share()),l}return y(r,e),r.prototype.ngOnInit=function(){e.prototype.ngOnInit.call(this)},r.prototype.configForm=function(){return this.entityDetailsConfigForm},r.prototype.prepareInputConfig=function(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e},r.prototype.onConfigurationSet=function(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[i.Validators.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})},r.prototype.displayDetails=function(e){return e?this.translate.instant(G.get(e)):void 0},r.prototype.fetchEntityDetails=function(e){var t=this;if(this.searchText=e,this.searchText&&this.searchText.length){var r=this.searchText.toUpperCase();return c.of(this.entityDetailsList.filter((function(e){return t.translate.instant(G.get(K[e])).toUpperCase().includes(r)})))}return c.of(this.entityDetailsList)},r.prototype.detailsFieldSelected=function(e){this.addDetailsField(e.option.value),this.clear("")},r.prototype.removeDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;if(t){var r=t.indexOf(e);r>=0&&(t.splice(r,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}},r.prototype.addDetailsField=function(e){var t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]),-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))},r.prototype.onEntityDetailsInputFocus=function(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})},r.prototype.clear=function(e){var t=this;void 0===e&&(e=""),this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((function(){t.detailsInput.nativeElement.blur(),t.detailsInput.nativeElement.focus()}),0)},r.ctorParameters=function(){return[{type:o.Store},{type:n.TranslateService},{type:i.FormBuilder}]},b([t.ViewChild("detailsInput",{static:!1}),h("design:type",t.ElementRef)],r.prototype,"detailsInput",void 0),r=b([t.Component({selector:"tb-enrichment-node-entity-details-config",template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}"]}),h("design:paramtypes",[o.Store,n.TranslateService,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Le=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.deviceAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[i.Validators.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.deviceAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.deviceAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.deviceAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.deviceAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-device-attributes-config",template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Me=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n}return y(r,e),r.prototype.configForm=function(){return this.originatorAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})},r.prototype.removeKey=function(e,t){var r=this.originatorAttributesConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.originatorAttributesConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.originatorAttributesConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.originatorAttributesConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-attributes-config",template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Pe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.originatorFieldsConfigForm},r.prototype.onConfigurationSet=function(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-originator-fields-config",template:'
\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Re=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.separatorKeysCodes=[s.ENTER,s.COMMA,s.SEMICOLON],n.fetchMode=U,n.fetchModes=Object.keys(U),n.samplingOrders=Object.keys(j),n.timeUnits=Object.keys(R),n.timeUnitsTranslationMap=D,n}return y(r,e),r.prototype.configForm=function(){return this.getTelemetryFromDatabaseConfigForm},r.prototype.onConfigurationSet=function(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],fetchMode:[e?e.fetchMode:null,[i.Validators.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})},r.prototype.validatorTriggers=function(){return["fetchMode","useMetadataIntervalPatterns"]},r.prototype.updateValidators=function(e){var t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,r=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===U.ALL?(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([i.Validators.required,i.Validators.min(2),i.Validators.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),r?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([i.Validators.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([i.Validators.required,i.Validators.min(1),i.Validators.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([i.Validators.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})},r.prototype.removeKey=function(e,t){var r=this.getTelemetryFromDatabaseConfigForm.get(t).value,n=r.indexOf(e);n>=0&&(r.splice(n,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(r,{emitEvent:!0}))},r.prototype.addKey=function(e,t){var r=e.input,n=e.value;if((n||"").trim()){n=n.trim();var a=this.getTelemetryFromDatabaseConfigForm.get(t).value;a&&-1!==a.indexOf(n)||(a||(a=[]),a.push(n),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(a,{emitEvent:!0}))}r&&(r.value="")},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-get-telemetry-from-database",template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}"]}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),we=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.relatedAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[i.Validators.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-related-attributes-config",template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Oe=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.tenantAttributesConfigForm},r.prototype.onConfigurationSet=function(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-enrichment-node-tenant-attributes-config",template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),De=function(){function e(){}return e=b([t.NgModule({declarations:[Ee,Ae,Le,Me,Pe,Re,we,Oe],imports:[r.CommonModule,a.SharedModule,le],exports:[Ee,Ae,Le,Me,Pe,Re,we,Oe]})],e)}(),Ke=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n.originatorSource=v,n.originatorSources=Object.keys(v),n.originatorSourceTranslationMap=P,n}return y(r,e),r.prototype.configForm=function(){return this.changeOriginatorConfigForm},r.prototype.onConfigurationSet=function(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[i.Validators.required]],relationsQuery:[e?e.relationsQuery:null,[]]})},r.prototype.validatorTriggers=function(){return["originatorSource"]},r.prototype.updateValidators=function(e){var t=this.changeOriginatorConfigForm.get("originatorSource").value;t&&t===v.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([i.Validators.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-change-originator-config",template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),Be=function(e){function r(t,r,n,a){var o=e.call(this,t)||this;return o.store=t,o.fb=r,o.nodeScriptTestService=n,o.translate=a,o}return y(r,e),r.prototype.configForm=function(){return this.scriptConfigForm},r.prototype.onConfigurationSet=function(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[i.Validators.required]]})},r.prototype.testScript=function(){var e=this,t=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(t,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId).subscribe((function(t){t&&e.scriptConfigForm.get("jsScript").setValue(t)}))},r.prototype.onValidate=function(){this.jsFuncComponent.validateOnSubmit()},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder},{type:l.NodeScriptTestService},{type:n.TranslateService}]},b([t.ViewChild("jsFuncComponent",{static:!0}),h("design:type",a.JsFuncComponent)],r.prototype,"jsFuncComponent",void 0),r=b([t.Component({selector:"tb-transformation-node-script-config",template:'
\n \n \n \n
\n \n
\n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder,l.NodeScriptTestService,n.TranslateService])],r)}(a.RuleNodeConfigurationComponent),Ue=function(e){function r(t,r){var n=e.call(this,t)||this;return n.store=t,n.fb=r,n}return y(r,e),r.prototype.configForm=function(){return this.toEmailConfigForm},r.prototype.onConfigurationSet=function(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[i.Validators.required]],toTemplate:[e?e.toTemplate:null,[i.Validators.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[i.Validators.required]],bodyTemplate:[e?e.bodyTemplate:null,[i.Validators.required]]})},r.ctorParameters=function(){return[{type:o.Store},{type:i.FormBuilder}]},r=b([t.Component({selector:"tb-transformation-node-to-email-config",template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n'}),h("design:paramtypes",[o.Store,i.FormBuilder])],r)}(a.RuleNodeConfigurationComponent),je=function(){function e(){}return e=b([t.NgModule({declarations:[Ke,Be,Ue],imports:[r.CommonModule,a.SharedModule,le],exports:[Ke,Be,Ue]})],e)}(),He=function(){function e(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-name-pattern-hint":"Name pattern, use ${metaKeyName} to substitute variables from metadata","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-type-pattern-hint":"Type pattern, use ${metaKeyName} to substitute variables from metadata","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-name-pattern-hint":"Customer name pattern, use ${metaKeyName} to substitute variables from metadata","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","client-attributes-hint":"Client attributes, use ${metaKeyName} to substitute variables from metadata","shared-attributes":"Shared attributes","shared-attributes-hint":"Shared attributes, use ${metaKeyName} to substitute variables from metadata","server-attributes":"Server attributes","server-attributes-hint":"Server attributes, use ${metaKeyName} to substitute variables from metadata","notify-device":"Notify Device","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","latest-timeseries-hint":"Latest timeseries, use ${metaKeyName} to substitute variables from metadata","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-hint":"Relation type pattern, use ${metaKeyName} to substitute variables from metadata","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","originator-alarm-originator":"Alarm Originator","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","use-metadata-period-in-seconds-patterns":"Use metadata period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds metadata pattern","period-in-seconds-pattern-required":"Period in seconds pattern is required","period-in-seconds-pattern-hint":"Period in seconds pattern, use ${metaKeyName} to substitute variables from metadata","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata",topic:"Topic","topic-required":"Topic is required","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","device-id":"Device ID","device-id-required":"Device ID is required.","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","use-metadata-interval-patterns":"Use metadata interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","start-interval-pattern-hint":"Start interval pattern, use ${metaKeyName} to substitute variables from metadata","end-interval-pattern-hint":"End interval pattern, use ${metaKeyName} to substitute variables from metadata","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{\\"ts\\":1574329385897,\\"value\\":42}"',"use-redis-queue":"Use redis queue for message persistence","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}},!0)}(e)}return e.ctorParameters=function(){return[{type:n.TranslateService}]},e=b([t.NgModule({declarations:[F],imports:[r.CommonModule,a.SharedModule],exports:[ve,Ve,De,je,F]}),h("design:paramtypes",[n.TranslateService])],e)}();e.RuleNodeCoreConfigModule=He,e.ɵa=F,e.ɵb=ve,e.ɵba=be,e.ɵbb=he,e.ɵbc=Ce,e.ɵbd=le,e.ɵbe=ne,e.ɵbf=ae,e.ɵbg=oe,e.ɵbh=ie,e.ɵbi=Ve,e.ɵbj=Fe,e.ɵbk=xe,e.ɵbl=Te,e.ɵbm=qe,e.ɵbn=Se,e.ɵbo=Ie,e.ɵbp=ke,e.ɵbq=Ne,e.ɵbr=De,e.ɵbs=Ee,e.ɵbt=Ae,e.ɵbu=Le,e.ɵbv=Me,e.ɵbw=Pe,e.ɵbx=Re,e.ɵby=we,e.ɵbz=Oe,e.ɵc=x,e.ɵca=je,e.ɵcb=Ke,e.ɵcc=Be,e.ɵcd=Ue,e.ɵd=T,e.ɵe=q,e.ɵf=S,e.ɵg=I,e.ɵh=k,e.ɵi=N,e.ɵj=V,e.ɵk=E,e.ɵl=A,e.ɵm=L,e.ɵn=X,e.ɵo=ee,e.ɵp=te,e.ɵq=re,e.ɵr=se,e.ɵs=me,e.ɵt=ue,e.ɵu=de,e.ɵv=pe,e.ɵw=ce,e.ɵx=fe,e.ɵy=ge,e.ɵz=ye,Object.defineProperty(e,"__esModule",{value:!0})})); //# sourceMappingURL=rulenode-core-config.umd.min.js.map \ No newline at end of file diff --git a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html index b53ca8f0b0..c696272d78 100644 --- a/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html @@ -49,7 +49,6 @@ [queueType]="serviceType" formControlName="defaultQueueName"> -
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html index 0eb4c07367..468e946a74 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device-profile.component.html @@ -57,7 +57,6 @@ [queueType]="serviceType" formControlName="defaultQueueName"> -
device-profile.select-queue-hint
device-profile.type diff --git a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts index 53449aad10..de404f49e2 100644 --- a/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-chain/rule-chain-autocomplete.component.ts @@ -202,8 +202,8 @@ export class RuleChainAutocompleteComponent implements ControlValueAccessor, OnI createDefaultRuleChain($event: Event, ruleChainName: string) { $event.preventDefault(); this.ruleChainAutocomplete.closePanel(); - this.ruleChainService.createDefaultRuleChain(ruleChainName).subscribe((ruleChain) => { - this.updateView(ruleChain.id.id); + this.ruleChainService.createDefaultRuleChain(ruleChainName.trim()).subscribe((ruleChain) => { + this.selectRuleChainFormGroup.get('ruleChainId').patchValue(ruleChain); }); } } diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index fe7849d3e1..362b3c14ce 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -102,7 +102,6 @@ [queueType]="serviceType" formControlName="defaultQueueName"> -
device-profile.select-queue-hint
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts index a539b48917..6a7c05e0ac 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.ts @@ -25,8 +25,12 @@ import { createDeviceProfileConfiguration, createDeviceProfileTransportConfiguration, DeviceProfile, - DeviceProfileType, DeviceProvisionConfiguration, DeviceProvisionType, - DeviceTransportType, deviceTransportTypeConfigurationInfoMap, deviceTransportTypeHintMap, + DeviceProfileType, + DeviceProvisionConfiguration, + DeviceProvisionType, + DeviceTransportType, + deviceTransportTypeConfigurationInfoMap, + deviceTransportTypeHintMap, deviceTransportTypeTranslationMap } from '@shared/models/device.models'; import { MatHorizontalStepper } from '@angular/material/stepper'; @@ -43,7 +47,8 @@ import { StepperSelectionEvent } from '@angular/cdk/stepper'; import { BreakpointObserver, BreakpointState } from '@angular/cdk/layout'; import { MediaBreakpoints } from '@shared/models/constants'; import { RuleChainId } from '@shared/models/id/rule-chain-id'; -import {ServiceType} from "@shared/models/queue.models"; +import { ServiceType } from '@shared/models/queue.models'; +import { deepTrim } from '@core/utils'; @Component({ selector: 'tb-device-wizard', @@ -108,7 +113,7 @@ export class DeviceWizardDialogComponent extends deviceProfileId: [null, Validators.required], newDeviceProfileTitle: [{value: null, disabled: true}], defaultRuleChainId: [{value: null, disabled: true}], - defaultQueueName: [''], + defaultQueueName: [{value: null, disabled: true}], description: [''] } ); @@ -288,7 +293,7 @@ export class DeviceWizardDialogComponent extends if (this.deviceWizardFormGroup.get('defaultRuleChainId').value) { deviceProfile.defaultRuleChainId = new RuleChainId(this.deviceWizardFormGroup.get('defaultRuleChainId').value); } - return this.deviceProfileService.saveDeviceProfile(deviceProfile).pipe( + return this.deviceProfileService.saveDeviceProfile(deepTrim(deviceProfile)).pipe( map(profile => profile.id), tap((profileId) => { this.deviceWizardFormGroup.patchValue({ @@ -319,7 +324,7 @@ export class DeviceWizardDialogComponent extends id: this.customerFormGroup.get('customerId').value }; } - return this.data.entitiesTableConfig.saveEntity(device); + return this.data.entitiesTableConfig.saveEntity(deepTrim(device)); } private saveCredentials(device: BaseData): Observable { diff --git a/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html index ccdf4a76ec..1380e7e28b 100644 --- a/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html +++ b/ui-ngx/src/app/shared/components/queue/queue-type-list.component.html @@ -40,4 +40,5 @@ {{ 'queue.name_required' | translate }} + device-profile.select-queue-hint
From 75686ac16757f6a32405eed3894f4c1c59e5c8de Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Thu, 29 Oct 2020 19:03:20 +0200 Subject: [PATCH 060/102] Added new language Brazilian Portuguese --- .../assets/locale/locale.constant-en_US.json | 3 +- .../assets/locale/locale.constant-pl_BR.json | 2048 +++++++++++++++++ 2 files changed, 2050 insertions(+), 1 deletion(-) create mode 100644 ui-ngx/src/assets/locale/locale.constant-pl_BR.json diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 571d4ed5dc..e3b7b4b81d 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -2342,7 +2342,8 @@ "el_GR": "Ελληνικά", "ro_RO": "Română", "lv_LV": "Latviešu", - "ka_GE": "ქართული" + "ka_GE": "ქართული", + "pt_BR": "Português do Brasil" } } } diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_BR.json b/ui-ngx/src/assets/locale/locale.constant-pl_BR.json new file mode 100644 index 0000000000..f08a38e9d9 --- /dev/null +++ b/ui-ngx/src/assets/locale/locale.constant-pl_BR.json @@ -0,0 +1,2048 @@ +{ + "access": { + "unauthorized": "Não autorizado", + "unauthorized-access": "Acesso não autorizado", + "unauthorized-access-text": "Para acessar este recurso, você precisa se inscrever!", + "access-forbidden": "Acesso proibido", + "access-forbidden-text": "Você não tem direito de acesso a esta localização!
Se ainda deseja acessar esta localização, tente inscrever-se com um nome de usuário diferente.", + "refresh-token-expired": "A sessão expirou", + "refresh-token-failed": "Impossível atualizar a sessão", + "permission-denied": "Permissão negada", + "permission-denied-text": "Você não tem permissão para executar esta operação!" + }, + "action": { + "activate": "Ativar", + "suspend": "Suspender", + "save": "Salvar", + "saveAs": "Salvar como", + "cancel": "Cancelar", + "ok": "OK", + "delete": "Excluir", + "add": "Adicionar", + "yes": "Sim", + "no": "Não", + "update": "Atualizar", + "remove": "Remover", + "select": "Selecionar", + "search": "Pesquisar", + "clear-search": "Limpar pesquisa", + "assign": "Atribuir", + "unassign": "Cancelar atribuição", + "share": "Compartilhar", + "make-private": "Tornar privado", + "apply": "Aplicar", + "apply-changes": "Aplicar alterações", + "edit-mode": "Modo de edição", + "enter-edit-mode": "Entrar no modo de edição", + "decline-changes": "Rejeitar alterações", + "close": "Fechar", + "back": "Voltar", + "run": "Executar", + "sign-in": "Inscreva-se!", + "edit": "Editar", + "view": "Visualizar", + "create": "Criar", + "drag": "Arrastar", + "refresh": "Atualizar", + "undo": "Desfazer", + "copy": "Copiar", + "paste": "Colar", + "copy-reference": "Copiar referência", + "paste-reference": "Colar referência", + "import": "Importar", + "export": "Exportar", + "share-via": "Compartilhar via {{provider}}", + "continue": "Continuar", + "discard-changes": "Descartar alterações", + "download": "Download" + }, + "aggregation": { + "aggregation": "Agregação", + "function": "Função de agregação de dados", + "limit": "Valores máx.", + "group-interval": "Intervalo de agrupamento", + "min": "Mín.", + "max": "Máx.", + "avg": "Média", + "sum": "Soma", + "count": "Contagem", + "none": "Nenhum" + }, + "admin": { + "general": "Geral", + "general-settings": "Configuração geral", + "outgoing-mail": "Servidor de e-mail", + "outgoing-mail-settings": "Configuração do servidor de saída de e-mail", + "system-settings": "Configuração do sistema", + "test-mail-sent": "O e-mail de teste foi enviado corretamente!", + "base-url": "URL de base", + "base-url-required": "O URL de base é obrigatório.", + "mail-from": "E-mail de", + "mail-from-required": "'E-mail de' é obrigatório.", + "smtp-protocol": "Protocolo SMTP", + "smtp-host": "Host SMTP", + "smtp-host-required": "O host SMTP é obrigatório.", + "smtp-port": "Porta SMTP", + "smtp-port-required": "É necessário informar a porta SMPT.", + "smtp-port-invalid": "Esta não parece ser uma porta SMTP válida.", + "timeout-msec": "Tempo limite (ms)", + "timeout-required": "O tempo limite é obrigatório.", + "timeout-invalid": "Este não parece ser um tempo limite válido.", + "enable-tls": "Habilitar TLS", + "tls-version": "Versão de TLS", + "enable-proxy": "Habilitar proxy", + "proxy-host": "Host de proxy", + "proxy-host-required": "O host de proxy é obrigatório.", + "proxy-port": "Porta do proxy", + "proxy-port-required": "A porta do proxy é obrigatória.", + "proxy-port-range": "A porta do proxy deve estar em um intervalo de 1 a 65535.", + "proxy-user": "Usuário do proxy", + "proxy-password": "Senha do proxy", + "send-test-mail": "Enviar e-mail de teste", + "security-settings": "Configuração de segurança", + "password-policy": "Política de senhas", + "minimum-password-length": "Comprimento mínimo da senha", + "minimum-password-length-required": "O comprimento mínimo da senha é obrigatório", + "minimum-password-length-range": "O comprimento mínimo da senha deve estar em um intervalo de 5 a 50.", + "minimum-uppercase-letters": "Número mínimo de letras maiúsculas", + "minimum-uppercase-letters-range": "O número mínimo de letras maiúsculas não pode ser negativo", + "minimum-lowercase-letters": "Número mínimo de letras minúsculas", + "minimum-lowercase-letters-range": "O número mínimo de letras minúsculas não pode ser negativo", + "minimum-digits": "Número mínimo de dígitos", + "minimum-digits-range": "O número mínimo de dígitos não pode ser negativo", + "minimum-special-characters": "Número mínimo de caracteres especiais", + "minimum-special-characters-range": "O número mínimo de caracteres especiais não pode ser negativo", + "password-expiration-period-days": "Período de expiração de senha em dias", + "password-expiration-period-days-range": "O período de expiração de senha não pode ser negativo", + "password-reuse-frequency-days": "Frequência de reutilização de senha em dias", + "password-reuse-frequency-days-range": "A frequência em dias de reutilização da senha não pode ser negativa", + "general-policy": "Política geral", + "max-failed-login-attempts": "Número máximo de tentativas de login com falha antes de bloquear a conta", + "minimum-max-failed-login-attempts-range": "O número máximo de tentativas de login com falha não pode ser negativo", + "user-lockout-notification-email": "Em caso de bloqueio de conta do usuário, enviar notificação para o endereço de e-mail" + }, + "alarm": { + "alarm": "Alarme", + "alarms": "Alarmes", + "select-alarm": "Selecionar alarme", + "no-alarms-matching": "Nenhum alarme encontrado que coincida com '{{entity}}'.", + "alarm-required": "O alarme é obrigatório", + "alarm-status": "Status do alarme", + "alarm-status-list": "Lista de status de alarmes", + "any-status": "Qualquer status", + "search-status": { + "ANY": "Qualquer", + "ACTIVE": "Ativo", + "CLEARED": "Limpo", + "ACK": "Confirmado", + "UNACK": "Não confirmado" + }, + "display-status": { + "ACTIVE_UNACK": "Não confirmado ativo", + "ACTIVE_ACK": "Confirmado ativo", + "CLEARED_UNACK": "Não confirmado limpo", + "CLEARED_ACK": "Confirmado limpo" + }, + "no-alarms-prompt": "Nenhum alarme encontrado", + "created-time": "Hora de criação", + "type": "Tipo", + "severity": "Severidade", + "originator": "Originador", + "originator-type": "Tipo de originador", + "details": "Detalhes", + "status": "Status", + "alarm-details": "Detalhes do alarme", + "start-time": "Hora de início", + "end-time": "Hora de término", + "ack-time": "Horário de confirmação", + "clear-time": "Horário de limpeza", + "alarm-severity-list": "Lista de severidade de alarmes", + "any-severity": "Qualquer severidade", + "severity-critical": "Crítica", + "severity-major": "Principal", + "severity-minor": "Menor", + "severity-warning": "Aviso", + "severity-indeterminate": "Indeterminado", + "acknowledge": "Confirmar", + "clear": "Limpar", + "search": "Pesquisar alarmes", + "selected-alarms": "{ count, plural, 1 {1 alarm} other {# alarms} } selecionado(s)", + "no-data": "Nenhum dado para exibição", + "polling-interval": "Intervalo seletivo de alarmes (s)", + "polling-interval-required": "O intervalo de chamada seletiva de alarmes é obrigatório.", + "min-polling-interval-message": "É permitido no mínimo 1 segundo de intervalo de chamada seletiva.", + "aknowledge-alarms-title": "Confirmar { count, plural, 1 {1 alarm} other {# alarms} }", + "aknowledge-alarms-text": "Tem certeza de que deseja confirmar { count, plural, 1 {1 alarm} other {# alarms} }?", + "aknowledge-alarm-title": "Confirmar alarme", + "aknowledge-alarm-text": "Tem certeza de que deseja confirmar o alarme?", + "clear-alarms-title": "Limpar { count, plural, 1 {1 alarm} other {# alarms} }", + "clear-alarms-text": "Tem certeza de que deseja limpar { count, plural, 1 {1 alarm} other {# alarms} }?", + "clear-alarm-title": "Limpar alarme", + "clear-alarm-text": "Tem certeza de que deseja limpar alarme?", + "alarm-status-filter": "Filtro de status de alarmes", + "alarm-filter": "Filtro de alarmes", + "max-count-load": "Número máximo de alarmes a serem carregados (0 - ilimitado)", + "max-count-load-required": "O número máximo de alarmes é obrigatório.", + "max-count-load-error-min": "O valor mínimo é 0.", + "fetch-size": "Tamanho da busca", + "fetch-size-required": "O tamanho da busca é obrigatório.", + "fetch-size-error-min": "O valor mínimo é 10.", + "alarm-type-list": "Lista de tipos de alarmes", + "any-type": "Qualquer tipo", + "search-propagated-alarms": "Pesquisar alarmes propagados" + }, + "alias": { + "add": "Adicionar alias", + "edit": "Editar alias", + "name": "Nome do alias", + "name-required": "O nome do alias é obrigatório", + "duplicate-alias": "Já existe um alias com o mesmo nome.", + "filter-type-single-entity": "Entidade individual", + "filter-type-entity-list": "Lista de entidades", + "filter-type-entity-name": "Nome da entidade", + "filter-type-state-entity": "Entidade do estado do dashboard", + "filter-type-state-entity-description": "Entidade obtida dos parâmetros de estado do dashboard", + "filter-type-asset-type": "Tipo de ativo", + "filter-type-asset-type-description": "Ativos do tipo '{{assetType}}'", + "filter-type-asset-type-and-name-description": "Ativos do tipo '{{assetType}}' cujo nome começa com '{{prefix}}'", + "filter-type-device-type": "Tipo de dispositivo", + "filter-type-device-type-description": "Dispositivos do tipo '{{deviceType}}'", + "filter-type-device-type-and-name-description": "Dispositivos do tipo '{{deviceType}}' cujo nome começa com '{{prefix}}'", + "filter-type-entity-view-type": "Tipo de exibições de entidades", + "filter-type-entity-view-type-description": "Exibições de entidades do tipo '{{entityView}}'", + "filter-type-entity-view-type-and-name-description": "Exibições de entidades do tipo '{{entityView}}' cujo nome começa com '{{prefix}}'", + "filter-type-relations-query": "Consulta de relações", + "filter-type-relations-query-description": "{{entities}} que tem relação de {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-asset-search-query": "Consulta de pesquisa de ativo", + "filter-type-asset-search-query-description": "Ativos dos tipos {{assetTypes}} que têm relação de {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-device-search-query": "Consulta de pesquisa de dispositivo", + "filter-type-device-search-query-description": "Dispositivos dos tipos {{deviceTypes}} que têm relação de {{relationType}} {{direction}} {{rootEntity}}", + "filter-type-entity-view-search-query": "Consulta de pesquisa de exibição de entidade", + "filter-type-entity-view-search-query-description": "Exibições de entidades dos tipos {{entityViewTypes}} que têm relação de {{relationType}} {{direction}} {{rootEntity}}", + "entity-filter": "Filtro de entidades", + "resolve-multiple": "Resolver como entidades múltiplas", + "filter-type": "Tipo de filtro", + "filter-type-required": "O tipo de filtro é obrigatório.", + "entity-filter-no-entity-matched": "Nenhuma entidade encontrada que coincida com o filtro especificado.", + "no-entity-filter-specified": "Nenhum filtro de entidade especificado", + "root-state-entity": "Usar entidade de estado do dashboard como raiz", + "last-level-relation": "Procurar apenas último nível de relação", + "root-entity": "Entidade raiz", + "state-entity-parameter-name": "Nome do parâmetro de entidade de estado", + "default-state-entity": "Entidade de estado padrão", + "default-entity-parameter-name": "Predefinido", + "max-relation-level": "Nível máx. de relação", + "unlimited-level": "Nível ilimitado", + "state-entity": "Entidade de estado do dashboard", + "all-entities": "Todas as entidades", + "any-relation": "qualquer" + }, + "asset": { + "asset": "Ativo", + "assets": "Ativos", + "management": "Gerenciamento de ativos", + "view-assets": "Visualizar ativos", + "add": "Adicionar ativo", + "assign-to-customer": "Atribuir a cliente", + "assign-asset-to-customer": "Atribuir ativo(s) a cliente", + "assign-asset-to-customer-text": "Selecione os ativos a serem atribuídos ao cliente", + "no-assets-text": "Nenhum ativo encontrado", + "assign-to-customer-text": "Selecione o cliente para atribuir o(s) ativo(s)", + "public": "Público", + "assignedToCustomer": "Atribuído ao cliente", + "make-public": "Tornar ativo público", + "make-private": "Tornar ativo privado", + "unassign-from-customer": "Remover atribuição a cliente", + "delete": "Excluir ativo", + "asset-public": "Ativo é público", + "asset-type": "Tipo de ativo", + "asset-type-required": "O tipo de ativo é obrigatório.", + "select-asset-type": "Selecionar tipo de ativo", + "enter-asset-type": "Inserir tipo de ativo", + "any-asset": "Qualquer ativo", + "no-asset-types-matching": "Nenhum tipo de ativo encontrado que coincida com '{{entitySubtype}}'.", + "asset-type-list-empty": "Nenhum tipo de ativo selecionado.", + "asset-types": "Tipos de ativos", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "type": "Tipo", + "type-required": "O tipo é obrigatório.", + "details": "Detalhes", + "events": "Eventos", + "add-asset-text": "Adicionar novo ativo", + "asset-details": "Detalhes do ativo", + "assign-assets": "Atribuir ativos", + "assign-assets-text": "Atribuir { count, plural, 1 {1 asset} other {# assets} } a cliente", + "delete-assets": "Excluir ativos", + "unassign-assets": "Remover atribuição de ativos", + "unassign-assets-action-title": "Remover atribuição de { count, plural, 1 {1 asset} other {# assets} } a cliente", + "assign-new-asset": "Atribuir novo ativo", + "delete-asset-title": "Tem certeza de que deseja excluir o ativo '{{assetName}}'?", + "delete-asset-text": "Cuidado, após confirmar, não será possível recuperar o ativo e nenhum dado associado.", + "delete-assets-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 asset} other {# assets} }?", + "delete-assets-action-title": "Excluir { count, plural, 1 {1 asset} other {# assets} }", + "delete-assets-text": "Cuidado, após confirmar, todos os ativos selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "make-public-asset-title": "Tem certeza de que deseja tornar o ativo '{{assetName}}' público?", + "make-public-asset-text": "Após confirmar, o ativo e todos os dados associados a ele se tornarão públicos e poderão ser acessados por outros.", + "make-private-asset-title": "Tem certeza de que deseja tornar o ativo '{{assetName}}' privado?", + "make-private-asset-text": "Após confirmar, o ativo e todos os dados associados a ele se tornarão privados e não poderão ser acessados por outros.", + "unassign-asset-title": "Tem certeza de que deseja remover a atribuição do ativo '{{assetName}}'?", + "unassign-asset-text": "Após confirmar, a atribuição do ativo será removida e ele não poderá ser acessado pelo cliente.", + "unassign-asset": "Remover atribuição de ativo", + "unassign-assets-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 asset} other {# assets} }?", + "unassign-assets-text": "Após confirmar, a atribuição de todos os ativos selecionados será removida e eles não poderão ser acessados pelo cliente.", + "copyId": "Copiar ID do ativo", + "idCopiedMessage": "O ID do ativo foi copiado para a área de transferência", + "select-asset": "Selecionar ativo", + "no-assets-matching": "Nenhum ativo encontrado que coincida com '{{entity}}'.", + "asset-required": "O ativo é obrigatório", + "name-starts-with": "O nome do ativo começa com", + "import": "Importar ativos", + "asset-file": "Arquivo de ativos", + "search": "Pesquisar ativo", + "selected-assets": "{ count, plural, 1 {1 asset} other {# assets} } selecionado(s)", + "label": "Etiqueta" + }, + "attribute": { + "attributes": "Atributos", + "latest-telemetry": "Última telemetria", + "attributes-scope": "Escopo de atributos de entidade", + "scope-latest-telemetry": "Última telemetria", + "scope-client": "Atributos do cliente", + "scope-server": "Atributos do servidor", + "scope-shared": "Atributos compartilhados", + "add": "Adicionar atributo", + "key": "Chave", + "last-update-time": "Horário da última atualização", + "key-required": "A chave de atributo é obrigatória.", + "value": "Valor", + "value-required": "O valor do atributo é obrigatório.", + "delete-attributes-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 attribute} other {# attributes} }?", + "delete-attributes-text": "Cuidado, após confirmar, todos os atributos selecionados serão removidos.", + "delete-attributes": "Excluir atributos", + "enter-attribute-value": "Inserir valor do atributo", + "show-on-widget": "Mostrar no widget", + "widget-mode": "Modo de widget", + "next-widget": "Próximo widget", + "prev-widget": "Widget anterior", + "add-to-dashboard": "Adicionar ao dashboard", + "add-widget-to-dashboard": "Adicionar widget ao dashboard", + "selected-attributes": "{ count, plural, 1 {1 attribute} other {# attributes} } selecionado(s)", + "selected-telemetry": "{ count, plural, 1 {1 telemetry unit} other {# telemetry units} } selecionado(s)", + "no-attributes-text": "Nenhum atributo encontrado", + "no-telemetry-text": "Nenhuma telemetria encontrada" + }, + "audit-log": { + "audit": "Auditoria", + "audit-logs": "Logs de auditoria", + "timestamp": "Carimbo de data/hora", + "entity-type": "Tipo de entidade", + "entity-name": "Nome da entidade", + "user": "Usuário", + "type": "Tipo", + "status": "Status", + "details": "Detalhes", + "type-added": "Adicionado", + "type-deleted": "Excluído", + "type-updated": "Atualizado", + "type-attributes-updated": "Atributos atualizados", + "type-attributes-deleted": "Atributos excluídos", + "type-rpc-call": "Chamada RPC", + "type-credentials-updated": "Credenciais atualizadas", + "type-assigned-to-customer": "Atribuído ao cliente", + "type-unassigned-from-customer": "Atribuição a cliente removida", + "type-activated": "Ativado", + "type-suspended": "Suspenso", + "type-credentials-read": "Credenciais lidas", + "type-attributes-read": "Atributos lidos", + "type-relation-add-or-update": "Relação atualizada", + "type-relation-delete": "Relação excluída", + "type-relations-delete": "Todas as relações excluídas", + "type-alarm-ack": "Confirmado", + "type-alarm-clear": "Limpo", + "type-login": "Login", + "type-logout": "Logout", + "type-lockout": "Bloqueio", + "status-success": "Êxito", + "status-failure": "Falha", + "audit-log-details": "Detalhes da trilha de auditoria", + "no-audit-logs-prompt": "Nenhum log encontrado", + "action-data": "Dados da ação", + "failure-details": "Detalhes da falha", + "search": "Pesquisar logs de auditoria", + "clear-search": "Limpar pesquisa", + "type-assigned-from-tenant": "Atribuído do locatário", + "type-assigned-to-tenant": "Atribuído a locatário" + }, + "confirm-on-exit": { + "message": "Existem alterações sem salvar. Tem certeza de que deseja sair desta página?", + "html-message": "Existem alterações sem salvar.
Tem certeza de que deseja sair desta página?", + "title": "Alterações sem salvar" + }, + "contact": { + "country": "País", + "city": "Cidade", + "state": "Estado", + "postal-code": "CEP / Código postal", + "postal-code-invalid": "Formato de código postal / CEP inválido.", + "address": "Endereço", + "address2": "Endereço 2", + "phone": "Telefone", + "email": "E-mail", + "no-address": "Em endereço" + }, + "common": { + "username": "Nome de usuário", + "password": "Senha", + "enter-username": "Inserir nome de usuário", + "enter-password": "Inserir senha", + "enter-search": "Inserir pesquisa", + "created-time": "Hora de criação", + "loading": "Carregando..." + }, + "content-type": { + "json": "Json", + "text": "Texto", + "binary": "Binário (Base64)" + }, + "customer": { + "customer": "Cliente", + "customers": "Clientes", + "management": "Gerenciamento de clientes", + "dashboard": "Dashboard de clientes", + "dashboards": "Dashboards de clientes", + "devices": "Dispositivos do cliente", + "entity-views": "Exibições de entidades do cliente", + "assets": "Ativos do cliente", + "public-dashboards": "Dashboards públicos", + "public-devices": "Dispositivos públicos", + "public-assets": "Ativos públicos", + "public-entity-views": "Exibições de entidades públicas", + "add": "Adicionar cliente", + "delete": "Excluir cliente", + "manage-customer-users": "Gerenciar usuários do cliente", + "manage-customer-devices": "Gerenciar dispositivos do cliente", + "manage-customer-dashboards": "Gerenciar dashboards do cliente", + "manage-public-devices": "Gerenciar dispositivos públicos", + "manage-public-dashboards": "Gerenciar dashboards públicos", + "manage-customer-assets": "Gerenciar ativos do cliente", + "manage-public-assets": "Gerenciar ativos públicos", + "add-customer-text": "Adicionar novo cliente", + "no-customers-text": "Nenhum cliente encontrado", + "customer-details": "Detalhes do cliente", + "delete-customer-title": "Tem certeza de que deseja excluir o ativo cliente '{{customerTitle}}'?", + "delete-customer-text": "Cuidado, após confirmar, não será possível recuperar o cliente e nenhum dado associado.", + "delete-customers-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 customer} other {# customers} }?", + "delete-customers-action-title": "Excluir { count, plural, 1 {1 customer} other {# customers} }", + "delete-customers-text": "Cuidado, após confirmar, todos os clientes selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "manage-users": "Gerenciar usuários", + "manage-assets": "Gerenciar ativos", + "manage-devices": "Gerenciar dispositivos", + "manage-dashboards": "Gerenciar dashboards", + "title": "Título", + "title-required": "O título é obrigatório.", + "description": "Descrição", + "details": "Detalhes", + "events": "Eventos", + "copyId": "Copiar ID de cliente", + "idCopiedMessage": "O ID do cliente foi copiado para a área de transferência", + "select-customer": "Selecionar cliente", + "no-customers-matching": "Nenhum cliente encontrado que coincida com '{{entity}}'.", + "customer-required": "O cliente é obrigatório", + "select-default-customer": "Selecionar cliente predefinido", + "default-customer": "Cliente predefinido", + "default-customer-required": "O cliente predefinido é necessário para depurar o dashboard no nível de locatário", + "search": "Pesquisar clientes", + "selected-customers": "{ count, plural, 1 {1 customer} other {# customers} } selecionado(s)" + }, + "datetime": { + "date-from": "Data de", + "time-from": "Hora de", + "date-to": "Data até", + "time-to": "Hora até" + }, + "dashboard": { + "dashboard": "Dashboard", + "dashboards": "Dashboards", + "management": "Gerenciamento de dashboards", + "view-dashboards": "Visualizar dashboards", + "add": "Adicionar dashboard", + "assign-dashboard-to-customer": "Atribuir dashboard(s) a cliente", + "assign-dashboard-to-customer-text": "Selecione os dashboards a serem atribuídos ao cliente", + "assign-to-customer-text": "Selecione o cliente para atribuir o(s) dashboard(s)", + "assign-to-customer": "Atribuir a cliente", + "unassign-from-customer": "Remover atribuição a cliente", + "make-public": "Tornar dashboards público", + "make-private": "Tornar dashboards privado", + "manage-assigned-customers": "Gerenciar clientes atribuídos", + "assigned-customers": "Clientes atribuídos", + "assign-to-customers": "Atribuir Dashboard(s) a Clientes", + "assign-to-customers-text": "Selecione os clientes para atribuir o(s) dashboard(s)", + "unassign-from-customers": "Remover Atribuição de Dashboard(s) a Clientes", + "unassign-from-customers-text": "Selecione os clientes para remover a atribuição do(s) dashboard(s)", + "no-dashboards-text": "Nenhum dashboard encontrado", + "no-widgets": "Nenhum widgets configurado", + "add-widget": "Adicionar novo widget", + "title": "Título", + "select-widget-title": "Selecionar widget", + "select-widget-subtitle": "Lista de tipos de widget disponíveis", + "delete": "Excluir dashboard", + "title-required": "O título é obrigatório.", + "description": "Descrição", + "details": "Detalhes", + "dashboard-details": "Detalhes do dashboard", + "add-dashboard-text": "Adicionar novo dashboard", + "assign-dashboards": "Atribuir dashboards", + "assign-new-dashboard": "Atribuir novo dashboard", + "assign-dashboards-text": "Atribuir { count, plural, 1 {1 dashboard} other {# dashboards} } a clientes", + "unassign-dashboards-action-text": "Remover atribuição de { count, plural, 1 {1 dashboard} other {# dashboards} } a clientes", + "delete-dashboards": "Excluir dashboards", + "unassign-dashboards": "Remover atribuição de dashboards", + "unassign-dashboards-action-title": "Remover atribuição de { count, plural, 1 {1 dashboard} other {# dashboards} } a cliente", + "delete-dashboard-title": "Tem certeza de que deseja excluir o dashboard '{{dashboardTitle}}'?", + "delete-dashboard-text": "Cuidado, após confirmar, não será possível recuperar o dashboard e nenhum dado associado.", + "delete-dashboards-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "delete-dashboards-action-title": "Excluir { count, plural, 1 {1 dashboard} other {# dashboards} }", + "delete-dashboards-text": "Cuidado, após confirmar, todos os dashboards selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "unassign-dashboard-title": "Tem certeza de que deseja remover a atribuição do dashboard '{{dashboardTitle}}'?", + "unassign-dashboard-text": "Após confirmar, a atribuição do dashboard será removida e ele não poderá ser acessado pelo cliente.", + "unassign-dashboard": "Remover atribuição de dashboard", + "unassign-dashboards-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 dashboard} other {# dashboards} }?", + "unassign-dashboards-text": "Após confirmar, a atribuição de todos os dashboards selecionados será removida e eles não poderão ser acessados pelo cliente.", + "public-dashboard-title": "Agora o dashboard é público", + "public-dashboard-text": "Agora o dashboard {{dashboardTitle}} é público e pode ser acessado pelo próximo link público:", + "public-dashboard-notice": "Observação: Não se esqueça de tornar dispositivos relacionados públicos para poder acessar seus dados.", + "make-private-dashboard-title": "Tem certeza de que deseja tornar o dashboard '{{dashboardTitle}}' privado?", + "make-private-dashboard-text": "Após confirmar, o dashboard e todos os dados associados a ele se tornarão privados e não poderão ser acessados por outros.", + "make-private-dashboard": "Tornar dashboards privado", + "socialshare-text": "'{{dashboardTitle}}' da plataforma ThingsBoard", + "socialshare-title": "'{{dashboardTitle}}' da plataforma ThingsBoard", + "select-dashboard": "Selecionar dashboard", + "no-dashboards-matching": "Nenhum dashboard encontrado que coincida com '{{entity}}'.", + "dashboard-required": "O dashboard é obrigatório.", + "select-existing": "Selecionar dashboard existente", + "create-new": "Criar novo dashboard", + "new-dashboard-title": "Novo título de dashboard", + "open-dashboard": "Abrir dashboard", + "set-background": "Definir plano de fundo", + "background-color": "Cor de fundo", + "background-image": "Imagem de fundo", + "background-size-mode": "Modo de tamanho de fundo", + "no-image": "Nenhuma imagem selecionada", + "drop-image": "Solte uma imagem ou clique para selecionar um arquivo para carregar.", + "settings": "Configurações", + "columns-count": "Contagem de colunas", + "columns-count-required": "A contagem de colunas é obrigatória.", + "min-columns-count-message": "Somente é permitida uma contagem de no mínimo 10 colunas.", + "max-columns-count-message": "Somente é permitida uma contagem de no máximo 1000 colunas.", + "widgets-margins": "Margem entre widgets", + "margin-required": "O valor da margem é obrigatório.", + "min-margin-message": "Somente 0 é permitido como valor mínimo de margem.", + "max-margin-message": "Somente 50 é permitido como valor máximo de margem.", + "horizontal-margin": "Margem horizontal", + "horizontal-margin-required": "O valor da margem horizontal é obrigatório.", + "min-horizontal-margin-message": "Somente 0 é permitido como valor mínimo de margem horizontal.", + "max-horizontal-margin-message": "Somente 50 é permitido como valor máximo de margem horizontal.", + "vertical-margin": "Margem vertical", + "vertical-margin-required": "O valor da margem vertical é obrigatório.", + "min-vertical-margin-message": "Somente 0 é permitido como valor mínimo de margem vertical.", + "max-vertical-margin-message": "Somente 50 é permitido como valor máximo de margem vertical.", + "autofill-height": "Altura do layout de preenchimento automático", + "mobile-layout": "Configuração de layout móvel", + "mobile-row-height": "Altura de linha móvel, px", + "mobile-row-height-required": "O valor da altura de linha móvel é obrigatório.", + "min-mobile-row-height-message": "Somente 5 pixels é permitido como valor mínimo da altura de linha móvel.", + "max-mobile-row-height-message": "Somente 200 pixels é permitido como valor máximo da altura de linha móvel.", + "display-title": "Exibir título de dashboard", + "toolbar-always-open": "Manter barra de ferramentas aberto", + "title-color": "Cor do título", + "display-dashboards-selection": "Exibir seleção de dashboards", + "display-entities-selection": "Exibir seleção de entidades", + "display-filters": "Exibir filtros", + "display-dashboard-timewindow": "Exibir timewindow", + "display-dashboard-export": "Exibir exportação", + "import": "Importar dashboard", + "export": "Exportar dashboard", + "export-failed-error": "Impossível exportar dashboard: {{error}}", + "create-new-dashboard": "Criar novo dashboard", + "dashboard-file": "Arquivo de dashboard", + "invalid-dashboard-file-error": "Impossível importar dashboard: Estrutura de dados do dashboard inválida", + "dashboard-import-missing-aliases-title": "Configurar aliases usados por dashboard importado", + "create-new-widget": "Criar novo widget", + "import-widget": "Importar widget", + "widget-file": "Arquivo de widget", + "invalid-widget-file-error": "Impossível importar widget: Estrutura de dados do widget inválida.", + "widget-import-missing-aliases-title": "Configurar aliases usados por widget importado", + "open-toolbar": "Abrir barra de ferramentas de dashboard", + "close-toolbar": "Fechar barra de ferramentas", + "configuration-error": "Erro de configuração", + "alias-resolution-error-title": "Erro de configuração de aliases de dashboard", + "invalid-aliases-config": "Impossível encontrar dispositivos que coincidam com algum dos filtros de aliases.
Para resolver esta questão, encontre em contato com seu administrador.", + "select-devices": "Selecionar dispositivos", + "assignedToCustomer": "Atribuído ao cliente", + "assignedToCustomers": "Atribuído aos clientes", + "public": "Público", + "public-link": "Link público", + "copy-public-link": "Copiar link público", + "public-link-copied-message": "O link público da dashboard foi copiado para a área de transferência", + "manage-states": "Gerenciar estados de dashboards", + "states": "Estados de dashboards", + "search-states": "Pesquisar estados de dashboards", + "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selecionado(s)", + "edit-state": "Editar estado de dashboard", + "delete-state": "Excluir estado de dashboard", + "add-state": "Adicionar estado de dashboard", + "no-states-text": "Nenhum estado encontrado", + "state": "Estado do dashboard", + "state-name": "Nome", + "state-name-required": "O nome do estado do dashboard é obrigatório.", + "state-id": "ID do estado", + "state-id-required": "O ID do estado do dashboard é obrigatório.", + "state-id-exists": "Já existe um estado de dashboard com o mesmo ID.", + "is-root-state": "Estado raiz", + "delete-state-title": "Excluir estado de dashboard", + "delete-state-text": "Tem certeza de que deseja excluir o estado de dashboard com o nome '{{stateName}}'?", + "show-details": "Mostrar detalhes", + "hide-details": "Ocultar detalhes", + "select-state": "Selecionar estado alvo", + "state-controller": "Controlador de estado", + "search": "Selecionar dashboards", + "selected-dashboards": "{ count, plural, 1 {1 dashboard} other {# dashboards} } selecionado(s)" + }, + "datakey": { + "settings": "Configurações", + "advanced": "Avançado", + "label": "Etiqueta", + "color": "Cor", + "units": "Símbolo especial a ser exibido ao lado do valor", + "decimals": "Número de dígitos após ponto de flutuação", + "data-generation-func": "Função de geração de dados", + "use-data-post-processing-func": "Usar função de pós-processamento de dados", + "configuration": "Configuração de dados-chave", + "timeseries": "Intervalos de tempo", + "attributes": "Atributos", + "entity-field": "Campo de entidade", + "alarm": "Campos de alarmes", + "timeseries-required": "Os intervalos de tempo de entidade são obrigatórios.", + "timeseries-or-attributes-required": "Os intervalos de tempo/atributos de entidade são obrigatórios.", + "alarm-fields-timeseries-or-attributes-required": "Os campos de alarmes ou intervalos de tempo/atributos de entidade são obrigatórios.", + "maximum-timeseries-or-attributes": "No máximo { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }", + "alarm-fields-required": "Os campos de alarmes são obrigatórios.", + "function-types": "Tipos de função", + "function-types-required": "Os tipos de função são obrigatórios.", + "maximum-function-types": "No máximo { count, plural, 1 {1 function type is allowed.} other {# function types are allowed} }", + "time-description": "carimbo de data/horário do valor atual;", + "value-description": "o valor atual;", + "prev-value-description": "resultado da chamada de função anterior;", + "time-prev-description": "carimbo de data/horário do valor anterior;", + "prev-orig-value-description": "valor original anterior;" + }, + "datasource": { + "type": "Tipo de fonte de dados", + "name": "Nome", + "add-datasource-prompt": "Adicione a fonte de dados" + }, + "details": { + "details": "Detalhes", + "edit-mode": "Modo de edição", + "edit-json": "Editar JSON", + "toggle-edit-mode": "Alternar o modo de edição" + }, + "device": { + "device": "Dispositivo", + "device-required": "O nome do dispositivo é obrigatório.", + "devices": "Dispositivos", + "management": "Gerenciamento de dispositivos", + "view-devices": "Exibir Dispositivos", + "device-alias": "Alias do dispositivo", + "aliases": "Aliases de dispositivos", + "no-alias-matching": "'{{alias}}' não encontrado.", + "no-aliases-found": "Nenhum alias encontrado.", + "no-key-matching": "'{{key}}' não encontrado.", + "no-keys-found": "Nenhuma chave encontrado.", + "create-new-alias": "Criar um novo!", + "create-new-key": "Criar um novo!", + "duplicate-alias-error": "Alias '{{alias}}' duplicado encontrado.
Os aliases de dispositivos devem ser exclusivos no dashboard.", + "configure-alias": "Configurar alias de '{{alias}}'", + "no-devices-matching": "Nenhum dispositivo encontrado que coincida com '{{entity}}'.", + "alias": "Alias", + "alias-required": "O alias do dispositivo é obrigatório.", + "remove-alias": "Remover alias do dispositivo", + "add-alias": "Adicionar alias do dispositivo", + "name-starts-with": "O nome do dispositivo começa com", + "device-list": "Lista de dispositivos", + "use-device-name-filter": "Usar filtro", + "device-list-empty": "Nenhum dispositivo selecionado.", + "device-name-filter-required": "O filtro do nome do dispositivo é obrigatório.", + "device-name-filter-no-device-matched": "Nenhum dispositivo encontrado que comece com '{{device}}'.", + "add": "Adicionar Dispositivo", + "assign-to-customer": "Atribuir a cliente", + "assign-device-to-customer": "Atribuir Dispositivo(s) a Cliente", + "assign-device-to-customer-text": "Selecione os dispositivos a serem atribuídos ao cliente", + "make-public": "Tornar dispositivo público", + "make-private": "Tornar dispositivo privado", + "no-devices-text": "Nenhum dispositivo encontrado", + "assign-to-customer-text": "Selecione cliente para atribuir o(s) dispositivo(s)", + "device-details": "Detalhes do dispositivo", + "add-device-text": "Adicionar novo dispositivo", + "credentials": "Credenciais", + "manage-credentials": "Gerenciar credenciais", + "delete": "Excluir dispositivo", + "assign-devices": "Atribuir dispositivos", + "assign-devices-text": "Atribuir { count, plural, 1 {1 device} other {# devices} } a cliente", + "delete-devices": "Excluir dispositivos", + "unassign-from-customer": "Remover atribuição a cliente", + "unassign-devices": "Remover atribuição de dispositivos", + "unassign-devices-action-title": "Remover atribuição de { count, plural, 1 {1 device} other {# devices} } a cliente", + "assign-new-device": "Atribuir novo dispositivo", + "make-public-device-title": "Tem certeza de que deseja tornar o dispositivo '{{deviceName}}' público?", + "make-public-device-text": "Após confirmar, o dispositivo e todos os dados associados a ele se tornarão públicos e poderão ser acessados por outros.", + "make-private-device-title": "Tem certeza de que deseja tornar o dispositivo '{{deviceName}}' privado?", + "make-private-device-text": "Após confirmar, o dispositivo e todos os dados associados a ele se tornarão privados e não poderão ser acessados por outros.", + "view-credentials": "Exibir credenciais", + "delete-device-title": "Tem certeza de que deseja excluir o dispositivo '{{deviceName}}'?", + "delete-device-text": "Cuidado, após confirmar, não será possível recuperar o dispositivo e nenhum dado associado.", + "delete-devices-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 device} other {# devices} }?", + "delete-devices-action-title": "Excluir { count, plural, 1 {1 device} other {# devices} }", + "delete-devices-text": "Cuidado, após confirmar, todos os dispositivos selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "unassign-device-title": "Tem certeza de que deseja remover a atribuição do dispositivo '{{deviceName}}'?", + "unassign-device-text": "Após confirmar, a atribuição do dispositivo será removida e ele não poderá ser acessado pelo cliente.", + "unassign-device": "Remover atribuição de dispositivo", + "unassign-devices-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 device} other {# devices} }?", + "unassign-devices-text": "Após confirmar, a atribuição de todos os dispositivos selecionados será removida e eles não poderão ser acessados pelo cliente.", + "device-credentials": "Credenciais do dispositivo", + "credentials-type": "Tipo de credencial", + "access-token": "Token de acesso", + "access-token-required": "O token de acesso é obrigatório.", + "access-token-invalid": "O token de acesso deve ter de 1 a 20 caracteres.", + "rsa-key": "Chave pública RSA", + "rsa-key-required": "A chave pública RSA é obrigatória.", + "secret": "Segredo", + "secret-required": "O segredo é obrigatório.", + "device-type": "Tipo de dispositivo", + "device-type-required": "O tipo de dispositivo é obrigatório.", + "select-device-type": "Selecionar tipo de dispositivo", + "enter-device-type": "Inserir tipo de dispositivo", + "any-device": "Qualquer dispositivo", + "no-device-types-matching": "Nenhum tipo de dispositivo encontrado que coincida com '{{entitySubtype}}'.", + "device-type-list-empty": "Nenhum tipo de dispositivo selecionado.", + "device-types": "Tipos de dispositivos", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "label": "Etiqueta", + "events": "Eventos", + "details": "Detalhes", + "copyId": "Copiar ID de dispositivo", + "copyAccessToken": "Copiar token de acesso", + "idCopiedMessage": "O ID do dispositivo foi copiado para a área de transferência", + "accessTokenCopiedMessage": "O token de acesso foi copiado para a área de transferência", + "assignedToCustomer": "Atribuído ao cliente", + "unable-delete-device-alias-title": "Impossível excluir alias do dispositivo", + "unable-delete-device-alias-text": "Não é possível excluir o alias do dispositivo '{{deviceAlias}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "is-gateway": "É gateway", + "public": "Público", + "device-public": "O dispositivo é público", + "select-device": "Selecionar dispositivo", + "import": "Importar dispositivo", + "device-file": "Arquivo de dispositivo", + "search": "Pesquisar dispositivos", + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selecionado(s)" + }, + "dialog": { + "close": "Fechar diálogo" + }, + "direction": { + "column": "Coluna", + "row": "Linha" + }, + "error": { + "unable-to-connect": "Impossível conectar ao servidor! Verifique sua conexão à Internet.", + "unhandled-error-code": "Código de erro sem tratamento: {{errorCode}}", + "unknown-error": "Erro desconhecido" + }, + "entity": { + "entity": "Entidade", + "entities": "Entidades", + "aliases": "Aliases de entidades", + "entity-alias": "Alias de entidade", + "unable-delete-entity-alias-title": "Impossível excluir alias de entidade", + "unable-delete-entity-alias-text": "Não é possível excluir o alias de entidade '{{entityAlias}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "duplicate-alias-error": "Alias '{{alias}}' duplicado encontrado.
Os aliases de entidades devem ser exclusivos no dashboard.", + "missing-entity-filter-error": "Filtro ausente para o alias '{{alias}}'.", + "configure-alias": "Configurar alias de '{{alias}}'", + "alias": "Alias", + "alias-required": "O alias de entidade é obrigatório.", + "remove-alias": "Remover alias de entidade", + "add-alias": "Adicionar alias de entidade", + "entity-list": "Lista de entidades", + "entity-type": "Tipo de entidade", + "entity-types": "Tipos de entidades", + "entity-type-list": "Lista de tipos de entidades", + "any-entity": "Qualquer entidade", + "enter-entity-type": "Inserir tipo de entidade", + "no-entities-matching": "Nenhuma entidade encontrada que coincida com '{{entity}}'.", + "no-entity-types-matching": "Nenhum tipo de entidade encontrado que coincida com '{{entityType}}'.", + "name-starts-with": "O nome começa com", + "use-entity-name-filter": "Usar filtro", + "entity-list-empty": "Nenhuma entidade selecionada.", + "entity-type-list-empty": "Nenhum tipo de entidade selecionado.", + "entity-name-filter-required": "O filtro do nome de entidade é obrigatório.", + "entity-name-filter-no-entity-matched": "Nenhuma entidade encontrada que comece com '{{entity}}'.", + "all-subtypes": "Tudo", + "select-entities": "Selecionar entidades", + "no-aliases-found": "Nenhum alias encontrado.", + "no-alias-matching": "'{{alias}}' não encontrado.", + "create-new-alias": "Criar um novo!", + "key": "Chave", + "key-name": "Nome da chave", + "no-keys-found": "Nenhuma chave encontrado.", + "no-key-matching": "'{{key}}' não encontrado.", + "create-new-key": "Criar um novo!", + "type": "Tipo", + "type-required": "O tipo de entidade é obrigatório.", + "type-device": "Dispositivo", + "type-devices": "Dispositivos", + "list-of-devices": "{ count, plural, 1 {One device} other {List of # devices} }", + "device-name-starts-with": "Dispositivos cujos nomes começam com '{{prefix}}'", + "type-asset": "Ativo", + "type-assets": "Ativos", + "list-of-assets": "{ count, plural, 1 {One asset} other {List of # assets} }", + "asset-name-starts-with": "Ativos cujos nomes começam com '{{prefix}}'", + "type-entity-view": "Exibição de entidade", + "type-entity-views": "Exibições de entidades", + "list-of-entity-views": "{ count, plural, 1 {One entity view} other {List of # entity views} }", + "entity-view-name-starts-with": "Exibições de entidades cujos nomes começam com '{{prefix}}'", + "type-rule": "Regra", + "type-rules": "Regras", + "list-of-rules": "{ count, plural, 1 {One rule} other {List of # rules} }", + "rule-name-starts-with": "Regras cujos nomes começam com '{{prefix}}'", + "type-plugin": "Plug-in", + "type-plugins": "Plug-ins", + "list-of-plugins": "{ count, plural, 1 {One plugin} other {List of # plugins} }", + "plugin-name-starts-with": "Plug-ins cujos nomes começam com '{{prefix}}'", + "type-tenant": "Locatário", + "type-tenants": "Locatários", + "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }", + "tenant-name-starts-with": "Locatários cujos nomes começam com '{{prefix}}'", + "type-customer": "Cliente", + "type-customers": "Clientes", + "list-of-customers": "{ count, plural, 1 {One customer} other {List of # customers} }", + "customer-name-starts-with": "Clientes cujos nomes começam com '{{prefix}}'", + "type-user": "Usuário", + "type-users": "Usuários", + "list-of-users": "{ count, plural, 1 {One user} other {List of # users} }", + "user-name-starts-with": "Usuários cujos nomes começam com '{{prefix}}'", + "type-dashboard": "Dashboard", + "type-dashboards": "Dashboards", + "list-of-dashboards": "{ count, plural, 1 {One dashboard} other {List of # dashboards} }", + "dashboard-name-starts-with": "Dashboards cujos nomes começam com '{{prefix}}'", + "type-alarm": "Alarme", + "type-alarms": "Alarmes", + "list-of-alarms": "{ count, plural, 1 {One alarms} other {List of # alarms} }", + "alarm-name-starts-with": "Alarmes cujos nomes começam com '{{prefix}}'", + "type-rulechain": "Cadeia de regras", + "type-rulechains": "Cadeias de regras", + "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }", + "rulechain-name-starts-with": "Cadeias de regras cujos nomes começam com '{{prefix}}'", + "type-rulenode": "Nó de regra", + "type-rulenodes": "Nós de regras", + "list-of-rulenodes": "{ count, plural, 1 {One rule node} other {List of # rule nodes} }", + "rulenode-name-starts-with": "Nós de regras cujos nomes começam com '{{prefix}}'", + "type-current-customer": "Cliente atual", + "type-current-tenant": "Locatário atual", + "type-current-user": "Usuário atual", + "type-current-user-owner": "Proprietário de usuário atual", + "search": "Pesquisar entidades", + "selected-entities": "{ count, plural, 1 {1 entity} other {# entities} } selecionado(s)", + "entity-name": "Nome da entidade", + "entity-label": "Etiqueta de entidade", + "details": "Detalhes de entidades", + "no-entities-prompt": "Nenhuma entidade encontrada", + "no-data": "Nenhum dado para exibição", + "columns-to-display": "Colunas para Exibição" + }, + "entity-field": { + "created-time": "Hora de criação", + "name": "Nome", + "type": "Tipo", + "first-name": "Nome", + "last-name": "Sobrenome", + "email": "E-mail", + "title": "Título", + "country": "País", + "state": "Estado", + "city": "Cidade", + "address": "Endereço", + "address2": "Endereço 2", + "zip": "CEP", + "phone": "Telefone", + "label": "Etiqueta" + }, + "entity-view": { + "entity-view": "Exibição de entidade", + "entity-view-required": "A exibição de entidade é obrigatória.", + "entity-views": "Exibições de entidades", + "management": "Habilitar gerenciamento de exibições", + "view-entity-views": "Visualizar exibições de entidades", + "entity-view-alias": "Alias de exibição de entidade", + "aliases": "Aliases de exibições de entidades", + "no-alias-matching": "'{{alias}}' não encontrado.", + "no-aliases-found": "Nenhum alias encontrado.", + "no-key-matching": "'{{key}}' não encontrado.", + "no-keys-found": "Nenhuma chave encontrado.", + "create-new-alias": "Criar um novo!", + "create-new-key": "Criar um novo!", + "duplicate-alias-error": "Alias '{{alias}}' duplicado encontrado.
Os aliases de exibições de entidades devem ser exclusivos no dashboard.", + "configure-alias": "Configurar alias de '{{alias}}'", + "no-entity-views-matching": "Nenhuma exibição de entidade encontrada que coincida com '{{entity}}'.", + "public": "Público", + "alias": "Alias", + "alias-required": "O alias de exibição de entidade é obrigatório.", + "remove-alias": "Remover alias de exibição de entidade", + "add-alias": "Adicionar alias de exibição de entidade", + "name-starts-with": "O nome da exibição de entidade começa com", + "entity-view-list": "Lista de exibições de entidades", + "use-entity-view-name-filter": "Usar filtro", + "entity-view-list-empty": "Nenhuma exibição de entidade selecionada.", + "entity-view-name-filter-required": "O filtro de nomes de exibições de entidades é obrigatório.", + "entity-view-name-filter-no-entity-view-matched": "Nenhuma exibição de entidade encontrada que comece com '{{entityView}}'.", + "add": "Adicionar exibição de entidade", + "entity-view-public": "A exibição de entidade é pública", + "assign-to-customer": "Atribuir a cliente", + "assign-entity-view-to-customer": "Atribuir Exibição(ões) de entidade(s) a Cliente", + "assign-entity-view-to-customer-text": "Selecione as exibições de entidades a serem atribuídas ao cliente", + "no-entity-views-text": "Nenhuma exibição de entidade encontrada", + "assign-to-customer-text": "Selecione o cliente para atribuir a(s) exibição(ões) de entidade(s)", + "entity-view-details": "Detalhes de exibições de entidades", + "add-entity-view-text": "Adicionar nova exibição de entidade", + "delete": "Excluir exibição de entidade", + "assign-entity-views": "Atribuir exibições de entidades", + "assign-entity-views-text": "Atribuir { count, plural, 1 {1 entity view} other {# entity views} } a cliente", + "delete-entity-views": "Excluir exibições de entidades", + "unassign-from-customer": "Remover atribuição a cliente", + "unassign-entity-views": "Remover atribuição de exibições de entidades", + "unassign-entity-views-action-title": "Remover atribuição de { count, plural, 1 {1 entity view} other {# entity views} } a cliente", + "assign-new-entity-view": "Atribuir nova exibição de entidade", + "delete-entity-view-title": "Tem certeza de que deseja excluir a exibição de entidade '{{entityViewName}}'?", + "delete-entity-view-text": "Cuidado, após confirmar, não será possível recuperar a exibição de entidade e todos os dados associados.", + "delete-entity-views-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 entity view} other {# entity views} }?", + "delete-entity-views-action-title": "Excluir { count, plural, 1 {1 entity view} other {# entity views} }", + "delete-entity-views-text": "Cuidado, após confirmar, todas as exibições de entidades selecionadas serão removidas e não será possível recuperar nenhum dado associado.", + "unassign-entity-view-title": "Tem certeza de que deseja remover a atribuição da exibição de entidade '{{entityViewName}}'?", + "unassign-entity-view-text": "Após confirmar, a atribuição da exibição de entidade será removida e ela não poderá ser acessada pelo cliente.", + "unassign-entity-view": "Remover atribuição de exibição de entidade", + "unassign-entity-views-title": "Tem certeza de que deseja remover a atribuição de { count, plural, 1 {1 entity view} other {# entity views} }?", + "unassign-entity-views-text": "Após confirmar, a atribuição de todas as exibições de entidade selecionadas será removida e elas não poderão ser acessadas pelo cliente.", + "entity-view-type": "Tipo de exibições de entidades", + "entity-view-type-required": "O tipo de exibição de entidade é obrigatório.", + "select-entity-view-type": "Selecionar tipo de exibição de entidade", + "enter-entity-view-type": "Inserir tipo de exibição de entidade", + "any-entity-view": "Qualquer exibição de entidade", + "no-entity-view-types-matching": "Nenhum tipo de exibição de entidade encontrado que coincida com '{{entitySubtype}}'.", + "entity-view-type-list-empty": "Nenhum tipo de exibição de entidade selecionado.", + "entity-view-types": "Tipos de exibição de entidade", + "created-time": "Hora de criação", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "events": "Eventos", + "details": "Detalhes", + "copyId": "Copiar ID de exibição de entidade", + "idCopiedMessage": "O ID de exibição de entidade foi copiado para a área de transferência", + "assignedToCustomer": "Atribuído ao cliente", + "unable-entity-view-device-alias-title": "Impossível excluir alias de exibição de entidade", + "unable-entity-view-device-alias-text": "Não é possível excluir o alias do dispositivo '{{entityViewAlias}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "select-entity-view": "Selecionar exibição de entidade", + "make-public": "Tornar exibição de entidade pública", + "make-private": "Tornar exibição de entidade privada", + "start-date": "Data de início", + "start-ts": "Hora de início", + "end-date": "Data de término", + "end-ts": "Hora de término", + "date-limits": "Limites de datas", + "client-attributes": "Atributos do cliente", + "shared-attributes": "Atributos compartilhados", + "server-attributes": "Atributos do servidor", + "timeseries": "Intervalos de tempo", + "client-attributes-placeholder": "Atributos do cliente", + "shared-attributes-placeholder": "Atributos compartilhados", + "server-attributes-placeholder": "Atributos do servidor", + "timeseries-placeholder": "Intervalos de tempo", + "target-entity": "Entidade alvo", + "attributes-propagation": "Propagação de atributos", + "attributes-propagation-hint": "A exibição de entidade copiará automaticamente atributos especificados da entidade alvo sempre que essa exibição de entidade for salva ou atualizada. Por questões de desempenho, os atributos da entidade alvo não são propagados para a exibição de entidade em cada alteração de atributo. A propagação automática pode ser habilitada configurando o nó de regra \"copiar para exibição\" na cadeia de regras e vinculando mensagens de \"Pós-Atributos\" e \"Atributos Atualizados\" ao novo nó de regra.", + "timeseries-data": "Dados de intervalos de tempo", + "timeseries-data-hint": "Configurar chaves de dados de intervalos de tempo da entidade alvo que estará acessível para a exibição de entidade. Esses dados de intervalo de tempo são apenas de leitura.", + "make-public-entity-view-title": "Tem certeza de que deseja tornar a exibição de entidade '{{entityViewName}}' pública?", + "make-public-entity-view-text": "Após confirmar, a exibição de entidade e todos os dados associados a ela se tornarão públicos e poderão ser acessados por outros.", + "make-private-entity-view-title": "Tem certeza de que deseja tornar a exibição de entidade '{{entityViewName}}' privada?", + "make-private-entity-view-text": "Após confirmar, a exibição de entidade e todos os dados associados a ela se tornarão privados e não poderão ser acessados por outros.", + "search": "Pesquisar exibições de entidades", + "selected-entity-views": "{ count, plural, 1 {1 entity view} other {# entity views} } selecionado(s)" + }, + "event": { + "event-type": "Tipo de evento", + "type-error": "Erro", + "type-lc-event": "Evento de ciclo de vida", + "type-stats": "Estatísticas", + "type-debug-rule-node": "Depuração", + "type-debug-rule-chain": "Depuração", + "no-events-prompt": "Nenhum evento encontrado", + "error": "Erro", + "alarm": "Alarme", + "event-time": "Tempo de evento", + "server": "Servidor", + "body": "Corpo", + "method": "Método", + "type": "Tipo", + "entity": "Entidade", + "message-id": "ID de mensagem", + "message-type": "Tipo de Mensagem", + "data-type": "Tipo de Dados", + "relation-type": "Tipo de Relação", + "metadata": "Metadados", + "data": "Dados", + "event": "Evento", + "status": "Status", + "success": "Êxito", + "failed": "Falhou", + "messages-processed": "Mensagens processadas", + "errors-occurred": "Erros" + }, + "extension": { + "extensions": "Extensões", + "selected-extensions": "{ count, plural, 1 {1 extension} other {# extensions} } selecionado(s)", + "type": "Tipo", + "key": "Chave", + "value": "Valor", + "id": "ID", + "extension-id": "ID da extensão", + "extension-type": "Tipo de extensão", + "transformer-json": "JSON *", + "unique-id-required": "O ID de extensão atual já existe.", + "delete": "Excluir extensão", + "add": "Adicionar extensão", + "edit": "Editar extensão", + "delete-extension-title": "Tem certeza de que deseja excluir a extensão '{{extensionId}}'?", + "delete-extension-text": "Cuidado, após confirmar, não será possível recuperar a extensão e nenhum dado associado.", + "delete-extensions-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 extension} other {# extensions} }?", + "delete-extensions-text": "Cuidado, após confirmar, todas as extensões selecionadas serão removidas.", + "converters": "Conversores", + "converter-id": "ID do conversor", + "configuration": "Configuração", + "converter-configurations": "Configurações de conversores", + "token": "Token de segurança", + "add-converter": "Adicionar conversor", + "add-config": "Adicionar configuração de conversor", + "device-name-expression": "Expressão do nome do dispositivo", + "device-type-expression": "Expressão do tipo de dispositivo", + "custom": "Personalizado", + "to-double": "Duplicar", + "transformer": "Transformador", + "json-required": "O json do transformador é obrigatório", + "json-parse": "Impossível analisar json do transformador", + "attributes": "Atributos", + "add-attribute": "Adicionar atributo", + "add-map": "Adicionar elemento de mapeamento", + "timeseries": "Intervalos de tempo", + "add-timeseries": "Adicionar intervalos de tempo", + "field-required": "O campo é obrigatório", + "brokers": "Brokers", + "add-broker": "Adicionar broker", + "host": "Host", + "port": "Porta", + "port-range": "A porta deve estar em um intervalo de 1 a 65535.", + "ssl": "Ssl", + "credentials": "Credenciais", + "username": "Nome de usuário", + "password": "Senha", + "retry-interval": "Intervalo de repetição em milissegundos", + "anonymous": "Anônimo", + "basic": "Básico", + "pem": "PEM", + "ca-cert": "Arquivo de certificado da Autoridade de Certificação *", + "private-key": "Arquivo de chave privada *", + "cert": "Arquivo de certificado *", + "no-file": "Nenhum arquivo selecionado.", + "drop-file": "Solte um arquivo ou clique para selecionar um arquivo para carregar.", + "mapping": "Mapeamento", + "topic-filter": "Filtro de tópico", + "converter-type": "Tipo de conversor", + "converter-json": "Json", + "json-name-expression": "Expressão json do nome do dispositivo", + "topic-name-expression": "Expressão de tópico do nome do dispositivo", + "json-type-expression": "Expressão json do tipo de dispositivo", + "topic-type-expression": "Expressão de tópico do tipo de dispositivo", + "attribute-key-expression": "Expressão da chave de atributo", + "attr-json-key-expression": "Expressão json da chave de atributo", + "attr-topic-key-expression": "Expressão de tópico da chave de atributo", + "request-id-expression": "Expressão de ID da solicitação", + "request-id-json-expression": "Expressão json de ID da solicitação", + "request-id-topic-expression": "Expressão de tópico de ID da solicitação", + "response-topic-expression": "Expressão de tópico de resposta", + "value-expression": "Expressão do valor", + "topic": "Tópico", + "timeout": "Tempo limite em milissegundos", + "converter-json-required": "Json do conversor é obrigatório.", + "converter-json-parse": "Impossível analisar json do conversor", + "filter-expression": "Expressão do filtro", + "connect-requests": "Solicitações de conexão", + "add-connect-request": "Adicionar solicitação de conexão", + "disconnect-requests": "Solicitações de desconexão", + "add-disconnect-request": "Adicionar solicitação de desconexão", + "attribute-requests": "Solicitações de atributo", + "add-attribute-request": "Adicionar solicitação de atributo", + "attribute-updates": "Atualizações de atributos", + "add-attribute-update": "Adicionar atualização de atributo", + "server-side-rpc": "RPC no lado do servidor", + "add-server-side-rpc-request": "Adicionar solicitação de RPC no lado do servidor", + "device-name-filter": "Filtro do nome do dispositivo", + "attribute-filter": "Filtro de atributo", + "method-filter": "Filtro de método", + "request-topic-expression": "Expressão de tópico de solicitação", + "response-timeout": "Tempo limite de resposta em milissegundos", + "topic-expression": "Expressão de tópico", + "client-scope": "Escopo de cliente", + "add-device": "Adicionar dispositivo", + "opc-server": "Servidores", + "opc-add-server": "Adicionar servidor", + "opc-add-server-prompt": "Adicione servidor", + "opc-application-name": "Nome do aplicativo", + "opc-application-uri": "Uri do aplicativo", + "opc-scan-period-in-seconds": "Período de digitalização em segundos", + "opc-security": "Segurança", + "opc-identity": "Identidade", + "opc-keystore": "Keystore", + "opc-type": "Tipo", + "opc-keystore-type": "Tipo", + "opc-keystore-location": "Localização*", + "opc-keystore-password": "Senha", + "opc-keystore-alias": "Alias", + "opc-keystore-key-password": "Senha principal", + "opc-device-node-pattern": "Padrão de nó de dispositivo", + "opc-device-name-pattern": "Padrão de nome de dispositivo", + "modbus-server": "Servidores/subordinados", + "modbus-add-server": "Adicionar servidor/subordinado", + "modbus-add-server-prompt": "Adicione servidor/subordinado", + "modbus-transport": "Transporte", + "modbus-tcp-reconnect": "Reconectar automaticamente", + "modbus-rtu-over-tcp": "RTU sobre TCP", + "modbus-port-name": "Nome da porta serial", + "modbus-encoding": "Codificação", + "modbus-parity": "Paridade", + "modbus-baudrate": "Taxa de transmissão", + "modbus-databits": "Bits de dados", + "modbus-stopbits": "Bits de parada", + "modbus-databits-range": "Os bits de dados devem estar em um intervalo de 7 a 8.", + "modbus-stopbits-range": "Os bits de parada devem estar em um intervalo de 1 a 2.", + "modbus-unit-id": "ID da unidade", + "modbus-unit-id-range": "O ID da unidade deve estar em um intervalo de 1 a 247.", + "modbus-device-name": "Nome do dispositivo", + "modbus-poll-period": "Período de sondagem (ms)", + "modbus-attributes-poll-period": "Atributos do período de sondagem (ms)", + "modbus-timeseries-poll-period": "Intervalos de tempo do período de sondagem (ms)", + "modbus-poll-period-range": "O período de sondagem deve ser um valor positivo.", + "modbus-tag": "Etiqueta", + "modbus-function": "Função", + "modbus-register-address": "Endereço do registro", + "modbus-register-address-range": "O endereço do registro deve estar em um intervalo de 0 a 65535.", + "modbus-register-bit-index": "Índice de bits", + "modbus-register-bit-index-range": "O índice de bits deve estar em um intervalo de 0 a 15.", + "modbus-register-count": "Contagem de registro", + "modbus-register-count-range": "A contagem de registro deve ser um valor positivo.", + "modbus-byte-order": "Ordem de byte", + "sync": { + "status": "Status", + "sync": "Sincronizar", + "not-sync": "Não sincronizar", + "last-sync-time": "Horário da última sincronização", + "not-available": "Não disponível" + }, + "export-extensions-configuration": "Exportar configuração de extensões", + "import-extensions-configuration": "Importar configuração de extensões", + "import-extensions": "Importar extensões", + "import-extension": "Importar extensão", + "export-extension": "Exportar extensão", + "file": "Arquivo de extensões", + "invalid-file-error": "Arquivo de extensão inválido" + }, + "filter": { + "add": "Adicionar filtro", + "edit": "Editar filtro", + "name": "Nome do filtro", + "name-required": "O nome do filtro é obrigatório.", + "duplicate-filter": "Já existe um filtro com o mesmo nome.", + "filters": "Filtros", + "unable-delete-filter-title": "Impossível excluir filtro", + "unable-delete-filter-text": "Não é possível excluir o filtro '{{filter}}' porque ele é usado pelo(s) seguinte(s) widget(s):
{{widgetsList}}", + "duplicate-filter-error": "Filtro '{{filter}}' duplicado encontrado.
Os filtros devem ser exclusivos no dashboard.", + "missing-key-filters-error": "Filtros chave ausentes para o filtro '{{filter}}'.", + "filter": "Filtro", + "editable": "Editável", + "no-filters-found": "Nenhum filtro encontrado.", + "no-filter-matching": "'{{filter}}' não encontrado.", + "create-new-filter": "Criar um novo!", + "filter-required": "O filtro é obrigatório.", + "operation": { + "operation": "Operação", + "equal": "igual", + "not-equal": "diferente", + "starts-with": "começa com", + "ends-with": "termina com", + "contains": "contém", + "not-contains": "não contém", + "greater": "maior que", + "less": "menor que", + "greater-or-equal": "maior ou igual", + "less-or-equal": "menor ou igual", + "and": "e", + "or": "ou" + }, + "ignore-case": "Ignorar caixa", + "value": "Valor", + "remove-filter": "Remover filtro", + "no-filters": "Nenhum filtro configurado", + "add-filter": "Adicionar filtro", + "add-complex-filter": "Adicionar filtro complexo", + "add-complex": "Adicionar complexo", + "complex-filter": "Filtro complexo", + "edit-complex-filter": "Editar filtro complexo", + "edit-filter-user-params": "Editar parâmetros de usuário de predicado de filtro", + "user-parameters": "Parâmetros de usuário", + "display-label": "Etiqueta para exibição", + "autogenerated-label": "Gerar etiqueta automaticamente", + "order-priority": "Prioridade de ordem de campo", + "key-filter": "Filtro chave", + "key-filters": "Filtros chave", + "key-name": "Nome da chave", + "key-name-required": "O nome da chave é obrigatório.", + "key-type": { + "key-type": "Tipo de chave", + "attribute": "Atributo", + "timeseries": "Intervalos de tempo", + "entity-field": "Campo de entidade" + }, + "value-type": { + "value-type": "Tipo de valor", + "string": "Cadeia de caracteres", + "numeric": "Numérico", + "boolean": "Booliano", + "date-time": "Data/Hora" + }, + "value-type-required": "O tipo de valor chave é obrigatório.", + "key-value-type-change-title": "Tem certeza de que deseja alterar o tipo de valor chave?", + "key-value-type-change-message": "Se confirmar o novo tipo de valor, todos os filtros chave inseridos serão removidos.", + "no-key-filters": "Nenhum filtro chave configurado", + "add-key-filter": "Adicionar filtro chave", + "remove-key-filter": "Remover filtro chave", + "edit-key-filter": "Editar filtro chave", + "date": "Data", + "time": "Hora", + "current-tenant": "Locatário atual", + "current-customer": "Cliente atual", + "current-user": "Usuário atual", + "default-value": "Valor padrão", + "dynamic-source-type": "Tipo de fonte dinâmica", + "no-dynamic-value": "Nenhum valor dinâmico", + "source-attribute": "Atributo da fonte", + "switch-to-dynamic-value": "Alternar para valor dinâmico", + "switch-to-default-value": "Alternar para valor padrão" + }, + "fullscreen": { + "expand": "Expandir para tela inteira", + "exit": "Sair de tela inteira", + "toggle": "Alternar modo de tela inteira", + "fullscreen": "Tela inteira" + }, + "function": { + "function": "Função" + }, + "gateway": { + "add-entry": "Adicionar configuração", + "connector-add": "Adicionar novo conector", + "connector-enabled": "Habilitar conector", + "connector-name": "Nome do conector", + "connector-name-required": "O nome do conector é obrigatório.", + "connector-type": "Tipo de conector", + "connector-type-required": "O tipo de conector é obrigatório.", + "connectors": "Configuração de conectores", + "create-new-gateway": "Criar um novo gateway", + "create-new-gateway-text": "Tem certeza de que deseja criar um novo gateway com o nome: '{{gatewayName}}'?", + "delete": "Excluir configuração", + "download-tip": "Download de arquivo de configuração", + "gateway": "Gateway", + "gateway-exists": "Já existe um dispositivo com o mesmo nome.", + "gateway-name": "Nome do gateway", + "gateway-name-required": "O nome do gateway é obrigatório.", + "gateway-saved": "A configuração do gateway foi salva corretamente.", + "json-parse": "JSON inválido.", + "json-required": "O campo não pode estar em branco.", + "no-connectors": "Sem conectores", + "no-data": "Sem configurações", + "no-gateway-found": "Nenhum gateway encontrado.", + "no-gateway-matching": " '{{item}}' não encontrado.", + "path-logs": "Caminho para arquivos de log", + "path-logs-required": "O caminho é obrigatório", + "remote": "Configuração remota", + "remote-logging-level": "Nível de registro em log", + "remove-entry": "Remover configuração", + "save-tip": "Salvar arquivo de configuração", + "security-type": "Tipo de segurança", + "security-types": { + "access-token": "Token de Acesso", + "tls": "TLS" + }, + "storage": "Armazenamento", + "storage-max-file-records": "Número máximo de registros em arquivo", + "storage-max-files": "Número máximo de arquivos", + "storage-max-files-min": "O número mínimo é 1.", + "storage-max-files-pattern": "O número não é válido.", + "storage-max-files-required": "O número é obrigatório.", + "storage-max-records": "Número máximo de registros em armazenamento", + "storage-max-records-min": "O número mínimo de registros é 1.", + "storage-max-records-pattern": "O número não é válido.", + "storage-max-records-required": "O número máximo de registros é obrigatório.", + "storage-pack-size": "Tamanho máximo de pacote de eventos", + "storage-pack-size-min": "O número mínimo é 1.", + "storage-pack-size-pattern": "O número não é válido.", + "storage-pack-size-required": "O tamanho máximo de pacote de eventos é obrigatório.", + "storage-path": "Caminho de armazenamento", + "storage-path-required": "O caminho de armazenamento é obrigatório.", + "storage-type": "Tipo de armazenamento", + "storage-types": { + "file-storage": "Armazenamento de arquivo", + "memory-storage": "Armazenamento de memória" + }, + "thingsboard": "ThingsBoard", + "thingsboard-host": "Host ThingsBoard", + "thingsboard-host-required": "O host é obrigatório.", + "thingsboard-port": "Porta ThingsBoard", + "thingsboard-port-max": "O número máximo de portas é 65535.", + "thingsboard-port-min": "O número mínimo de portas é 1.", + "thingsboard-port-pattern": "A porta não é válida.", + "thingsboard-port-required": "A porta é obrigatória.", + "tidy": "Tidy", + "tidy-tip": "Config Tidy JSON", + "title-connectors-json": "Configuração do conector {{typeName}}", + "tls-path-ca-certificate": "Caminho para certificado de Autoridade de Certificação no gateway", + "tls-path-client-certificate": "Caminho para certificado de cliente no gateway", + "tls-path-private-key": "Caminho para chave privada no gateway", + "toggle-fullscreen": "Alternar tela inteira", + "transformer-json-config": "Configuração JSON*", + "update-config": "Adicionar/atualizar configuração de JSON" + }, + "grid": { + "delete-item-title": "Tem certeza de que deseja excluir este item?", + "delete-item-text": "Cuidado, após confirmar, não será possível recuperar este item e nenhum dado associado.", + "delete-items-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 item} other {# items} }?", + "delete-items-action-title": "Excluir { count, plural, 1 {1 item} other {# items} }", + "delete-items-text": "Cuidado, após confirmar, todos os itens selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "add-item-text": "Adicionar novo item", + "no-items-text": "Nenhum item encontrado", + "item-details": "Detalhes do item", + "delete-item": "Excluir item", + "delete-items": "Excluir itens", + "scroll-to-top": "Rolar para o início" + }, + "help": { + "goto-help-page": "Ir para a página de Ajuda" + }, + "home": { + "home": "Página Inicial", + "profile": "Perfil", + "logout": "Logout", + "menu": "Menu", + "avatar": "Avatar", + "open-user-menu": "Abrir menu de usuário" + }, + "import": { + "no-file": "Nenhum arquivo selecionado", + "drop-file": "Solte um arquivo JSON ou clique para selecionar um arquivo para carregar.", + "drop-file-csv": "Solte um arquivo CSV ou clique para selecionar um arquivo para carregar.", + "column-value": "Valor", + "column-title": "Título", + "column-example": "Exemplo de dados de valor", + "column-key": "Chave de atributo/telemetria", + "csv-delimiter": "Delimitador de CSV", + "csv-first-line-header": "A primeira linha contém nomes de coluna", + "csv-update-data": "Atualizar atributos/telemetria", + "import-csv-number-columns-error": "Um arquivo deve conter no mínimo duas colunas", + "import-csv-invalid-format-error": "Formato de arquivo inválido. Linha: '{{line}}'", + "column-type": { + "name": "Nome", + "type": "Tipo", + "label": "Etiqueta", + "column-type": "Tipo de coluna", + "client-attribute": "Atributo do cliente", + "shared-attribute": "Atributo compartilhado", + "server-attribute": "Atributo do servidor", + "timeseries": "Intervalos de tempo", + "entity-field": "Campo de entidade", + "access-token": "Token de acesso", + "isgateway": "É Gateway", + "description": "Descrição" + }, + "stepper-text": { + "select-file": "Selecionar um arquivo", + "configuration": "Importar configuração", + "column-type": "Selecionar tipos de colunas", + "creat-entities": "Criar novas entidades", + "done": "Concluído" + }, + "message": { + "create-entities": "{{count}} novas entidades foram criadas corretamente.", + "update-entities": "{{count}} novas entidades foram atualizadas corretamente.", + "error-entities": "Ocorreu um erro ao criar {{count}} entidades." + } + }, + "item": { + "selected": "Selecionado" + }, + "js-func": { + "no-return-error": "A função deve retornar um valor!", + "return-type-mismatch": "A função deve retornar um valor do tipo '{{type}}'!", + "tidy": "Tidy", + "mini": "Mini" + }, + "key-val": { + "key": "Chave", + "value": "Valor", + "remove-entry": "Remover entrada", + "add-entry": "Adicionar entrada", + "no-data": "Sem entradas" + }, + "layout": { + "layout": "Layout", + "manage": "Gerenciar layouts", + "settings": "Configuração de layout", + "color": "Cor", + "main": "Principal", + "right": "Direita", + "select": "Selecionar layout alvo" + }, + "legend": { + "direction": "Direção da legenda", + "position": "Posição da legenda", + "show-max": "Mostrar valor máximo", + "show-min": "Mostrar valor mínimo", + "show-avg": "Mostrar valor médio", + "show-total": "Mostrar valor total", + "settings": "Configuração de legenda", + "min": "mín.", + "max": "máx.", + "avg": "méd.", + "total": "total", + "comparison-time-ago": { + "days": "(dia atrás)", + "weeks": "(semana atrás)", + "months": "(mês atrás)", + "years": "(ano atrás)" + } + }, + "login": { + "login": "Login", + "request-password-reset": "Solicitar redefinição de senha", + "reset-password": "Redefinir senha", + "create-password": "Criar senha", + "passwords-mismatch-error": "As senhas inseridas devem ser idênticas!", + "password-again": "Repetir senha", + "sign-in": "Inscreva-se", + "username": "Nome de usuário (e-mail)", + "remember-me": "Lembrar-me", + "forgot-password": "Esqueceu a senha?", + "password-reset": "Redefinir senha", + "expired-password-reset-message": "As credenciais de expiraram! Crie uma nova senha.", + "new-password": "Nova senha", + "new-password-again": "Repetir nova senha", + "password-link-sent-message": "O link de redefinição de senha foi enviado corretamente!", + "email": "E-mail", + "login-with": "Login com {{name}}", + "or": "ou", + "error": "Erro de login" + }, + "position": { + "top": "Acima", + "bottom": "Abaixo", + "left": "Esquerda", + "right": "Direita" + }, + "profile": { + "profile": "Perfil", + "last-login-time": "Último login", + "change-password": "Modificar senha", + "current-password": "Senha atual" + }, + "relation": { + "relations": "Relações", + "direction": "Direção", + "search-direction": { + "FROM": "De", + "TO": "Para" + }, + "direction-type": { + "FROM": "de", + "TO": "para" + }, + "from-relations": "Relações de saída", + "to-relations": "Relações de entrada", + "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selecionado(s)", + "type": "Tipo", + "to-entity-type": "Para tipo de entidade", + "to-entity-name": "Para nome de entidade", + "from-entity-type": "De tipo de entidade", + "from-entity-name": "De nome de entidade", + "to-entity": "Para entidade", + "from-entity": "De entidade", + "delete": "Excluir relação", + "relation-type": "Tipo de relação", + "relation-type-required": "O tipo de relação é obrigatório.", + "any-relation-type": "Qualquer tipo", + "add": "Adicionar relação", + "edit": "Editar relação", + "delete-to-relation-title": "Tem certeza de que deseja excluir a relação com a entidade '{{entityName}}'?", + "delete-to-relation-text": "Cuidado, após confirmar, a relação da entidade '{{entityName}}' com a entidade atual será anulada.", + "delete-to-relations-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 relation} other {# relations} }?", + "delete-to-relations-text": "Cuidado, após confirmar, todas as relações serão removidas e a relação das entidades correspondentes com a entidade atual será anulada.", + "delete-from-relation-title": "Tem certeza de que deseja excluir a relação da entidade '{{entityName}}'?", + "delete-from-relation-text": "Cuidado, após confirmar, a relação da entidade atual com a entidade '{{entityName}}' será anulada.", + "delete-from-relations-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 relation} other {# relations} }?", + "delete-from-relations-text": "Cuidado, após confirmar, todas as relações serão removidas e a relação da entidade atual com as entidades correspondentes será anulada.", + "remove-relation-filter": "Remover filtro de relação", + "add-relation-filter": "Adicionar filtro de relação", + "any-relation": "Qualquer relação", + "relation-filters": "Filtros de relação", + "additional-info": "Informações adicionais (JSON)", + "invalid-additional-info": "Impossível analisar informações adicionais de json", + "no-relations-text": "Nenhuma relação encontrada" + }, + "rulechain": { + "rulechain": "Cadeia de regras", + "rulechains": "Cadeias de regras", + "root": "Raiz", + "delete": "Excluir cadeia de regras", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "description": "Descrição", + "add": "Adicionar cadeia de regras", + "set-root": "Tornar cadeia de regras em raiz", + "set-root-rulechain-title": "Tem certeza de que deseja tornar a cadeia de regras '{{ruleChainName}}' em raiz?", + "set-root-rulechain-text": "Após confirmar, a cadeia de regras se tornará em raiz e irá tratar todas as mensagens de transporte recebidas.", + "delete-rulechain-title": "Tem certeza de que deseja excluir a cadeia de regras '{{ruleChainName}}'?", + "delete-rulechain-text": "Cuidado, após confirmar, não será possível recuperar a cadeia de regras e todos os dados associados.", + "delete-rulechains-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 rule chain} other {# rule chains} }?", + "delete-rulechains-action-title": "Excluir { count, plural, 1 {1 rule chain} other {# rule chains} }", + "delete-rulechains-text": "Cuidado, após confirmar, todas as cadeias de regras serão removidas e não será possível recuperar nenhum dado associado.", + "add-rulechain-text": "Adicionar nova cadeia de regras", + "no-rulechains-text": "Nenhuma cadeia de regras encontrada", + "rulechain-details": "Detalhes de cadeia de regras", + "details": "Detalhes", + "events": "Eventos", + "system": "Sistema", + "import": "Importar cadeia de regras", + "export": "Exportar cadeia de regras", + "export-failed-error": "Impossível exportar cadeia de regras: {{error}}", + "create-new-rulechain": "Criar nova cadeia de regras", + "rulechain-file": "Arquivo de cadeia de regras", + "invalid-rulechain-file-error": "Impossível importar cadeia de regras: Estrutura de dados de cadeia de regras inválida.", + "copyId": "Copiar ID de cadeia de regras", + "idCopiedMessage": "O ID da cadeia de regras foi copiado para a área de transferência", + "select-rulechain": "Selecionar cadeia de regras", + "no-rulechains-matching": "Nenhuma cadeia de regras encontrada que coincida com '{{entity}}'.", + "rulechain-required": "A cadeia de regras é obrigatória", + "management": "Gerenciamento de regras", + "debug-mode": "Modo de depuração", + "search": "Pesquisar cadeias de regras", + "selected-rulechains": "{ count, plural, 1 {1 rule chain} other {# rule chains} } selecionada(s)", + "open-rulechain": "Abrir cadeia de regras" + }, + "rulenode": { + "details": "Detalhes", + "events": "Eventos", + "search": "Pesquisar nós", + "open-node-library": "Abrir biblioteca de nós", + "add": "Adicionar nó de regra", + "name": "Nome", + "name-required": "O nome é obrigatório.", + "type": "Tipo", + "description": "Descrição", + "delete": "Excluir nó de regras", + "select-all-objects": "Selecionar todos os nós e conexões", + "deselect-all-objects": "Desmarcar todos os nós e conexões", + "delete-selected-objects": "Excluir nós e conexões selecionados", + "delete-selected": "Excluir selecionado", + "select-all": "Selecionar tudo", + "copy-selected": "Copiar selecionado(s)", + "deselect-all": "Desmarcar tudo", + "rulenode-details": "Detalhes do nó de regras", + "debug-mode": "Modo de depuração", + "configuration": "Configuração", + "link": "Link", + "link-details": "Detalhes do link do nó de regras", + "add-link": "Adicionar link", + "link-label": "Etiqueta de link", + "link-label-required": "A etiqueta de link é obrigatória.", + "custom-link-label": "Etiqueta de link personalizada", + "custom-link-label-required": "A etiqueta de link personalizada é obrigatória.", + "link-labels": "Etiquetas de links", + "link-labels-required": "As etiquetas de links são obrigatórias.", + "no-link-labels-found": "Nenhuma etiqueta de link encontrada", + "no-link-label-matching": "'{{label}}' não encontrado.", + "create-new-link-label": "Criar um novo!", + "type-filter": "Filtrar", + "type-filter-details": "Filtrar mensagens recebidas com condições configuradas", + "type-enrichment": "Enriquecimento", + "type-enrichment-details": "Adicionar informações adicionais aos Metadados de mensagens", + "type-transformation": "Transformação", + "type-transformation-details": "Alterar payload e metadados de mensagens", + "type-action": "Ação", + "type-action-details": "Executar ação especial", + "type-external": "Externo", + "type-external-details": "Interage com sistema externo", + "type-rule-chain": "Cadeia de Regras", + "type-rule-chain-details": "Encaminha mensagens recebidas para cadeia de regras especificada", + "type-input": "Entrada", + "type-input-details": "Entrada lógica de cadeia de regras, encaminha mensagens recebidas para nó de regras associado", + "type-unknown": "Desconhecido", + "type-unknown-details": "Nó de regras não resolvido", + "directive-is-not-loaded": "Diretriz de configuração '{{directiveName}}' definida não está disponível.", + "ui-resources-load-error": "Erro ao carregar configuração de recursos de interface de usuário.", + "invalid-target-rulechain": "Impossível resolver cadeia de regras alvo!", + "test-script-function": "Testar funcionamento de script", + "message": "Mensagem", + "message-type": "Tipo de mensagem", + "select-message-type": "Selecionar tipo de mensagem", + "message-type-required": "O tipo de mensagem é obrigatório", + "metadata": "Metadados", + "metadata-required": "As entradas de metadados não podem estar em branco.", + "output": "Saída", + "test": "Teste", + "help": "Ajuda", + "reset-debug-mode": "Redefinir modo de depuração em todos os nós" + }, + "timezone": { + "timezone": "Fuso horário", + "select-timezone": "Selecionar fuso horário", + "no-timezones-matching": "Nenhum fuso horário encontrado que coincida com '{{timezone}}'.", + "timezone-required": "O fuso horário é obrigatório." + }, + "queue": { + "select_name": "Selecionar nome de fila", + "name": "Nome de Fila", + "name_required": "O nome de fila é obrigatório!" + }, + "tenant": { + "tenant": "Locatário", + "tenants": "Locatários", + "management": "Gerenciamento de locatários", + "add": "Adicionar Locatário", + "admins": "Administradores", + "manage-tenant-admins": "Gerenciar administradores de locatários", + "delete": "Excluir locatário", + "add-tenant-text": "Adicionar novo locatário", + "no-tenants-text": "Nenhum locatário encontrado", + "tenant-details": "Detalhes do locatário", + "delete-tenant-title": "Tem certeza de que deseja excluir o locatário '{{tenantTitle}}'?", + "delete-tenant-text": "Cuidado, após confirmar, não será possível recuperar o locatário e nenhum dado associado.", + "delete-tenants-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 tenant} other {# tenants} }?", + "delete-tenants-action-title": "Excluir { count, plural, 1 {1 tenant} other {# tenants} }", + "delete-tenants-text": "Cuidado, após confirmar, todos os locatários selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "title": "Título", + "title-required": "O título é obrigatório.", + "description": "Descrição", + "details": "Detalhes", + "events": "Eventos", + "copyId": "Copiar ID do locatário", + "idCopiedMessage": "O ID do locatário foi copiado para a área de transferência", + "select-tenant": "Selecionar locatário", + "no-tenants-matching": "Nenhum locatário encontrado que coincida com '{{entity}}'.", + "tenant-required": "O locatário é obrigatório", + "search": "Pesquisar locatários", + "selected-tenants": "{ count, plural, 1 {1 tenant} other {# tenants} } selecionado(s)", + "isolated-tb-core": "Processamento em contêiner isolado do ThingsBoard Core", + "isolated-tb-rule-engine": "Processamento em contêiner isolado do ThingsBoard Rule Engine", + "isolated-tb-core-details": "Exige microsserviço(s) separado(s) para cada locatário isolado", + "isolated-tb-rule-engine-details": "Exige microsserviço(s) separado(s) para cada locatário isolado" + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {1 second} other {# seconds} }", + "minutes-interval": "{ minutes, plural, 1 {1 minute} other {# minutes} }", + "hours-interval": "{ hours, plural, 1 {1 hour} other {# hours} }", + "days-interval": "{ days, plural, 1 {1 day} other {# days} }", + "days": "Dias", + "hours": "Horas", + "minutes": "Minutos", + "seconds": "Segundos", + "advanced": "Avançado" + }, + "timewindow": { + "days": "{ days, plural, 1 { day } other {# days } }", + "hours": "{ hours, plural, 0 { hour } 1 {1 hour } other {# hours } }", + "minutes": "{ minutes, plural, 0 { minute } 1 {1 minute } other {# minutes } }", + "seconds": "{ seconds, plural, 0 { second } 1 {1 second } other {# seconds } }", + "realtime": "Tempo real", + "history": "Histórico", + "last-prefix": "último", + "period": "de {{ startTime }} até {{ endTime }}", + "edit": "Editar janela de tempo", + "date-range": "Intervalo de datas", + "last": "Última", + "time-period": "Período de tempo", + "hide": "Ocultar" + }, + "user": { + "user": "Usuário", + "users": "Usuários", + "customer-users": "Usuários do cliente", + "tenant-admins": "Administradores de locatários", + "sys-admin": "Administrador do sistema", + "tenant-admin": "Administrador de locatários", + "customer": "Cliente", + "anonymous": "Anônimo", + "add": "Adicionar usuário", + "delete": "Excluir usuário", + "add-user-text": "Adicionar novo usuário", + "no-users-text": "Nenhum usuário encontrado", + "user-details": "Detalhes do usuário", + "delete-user-title": "Tem certeza de que deseja excluir o usuário '{{userEmail}}'?", + "delete-user-text": "Cuidado, após confirmar, não será possível recuperar o usuário e nenhum dado associado.", + "delete-users-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 user} other {# users} }?", + "delete-users-action-title": "Excluir { count, plural, 1 {1 user} other {# users} }", + "delete-users-text": "Cuidado, após confirmar, todos os usuários selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "activation-email-sent-message": "O e-mail de ativação foi enviado corretamente!", + "resend-activation": "Reenviar ativação", + "email": "E-mail", + "email-required": "O e-mail é obrigatório.", + "invalid-email-format": "Formato de e-mail inválido.", + "first-name": "Nome", + "last-name": "Sobrenome", + "description": "Descrição", + "default-dashboard": "Dashboard padrão", + "always-fullscreen": "Sempre tela inteira", + "select-user": "Selecionar usuário", + "no-users-matching": "Nenhum usuário encontrado que coincida com '{{entity}}'.", + "user-required": "O usuário é obrigatório", + "activation-method": "Método de ativação", + "display-activation-link": "Exibir link de ativação", + "send-activation-mail": "Enviar e-mail de ativação", + "activation-link": "Link de ativação de usuário", + "activation-link-text": "Para ativar o usuário, utilize o seguinte link de ativação:", + "copy-activation-link": "Copiar link de ativação", + "activation-link-copied-message": "O link de ativação foi copiado para a área de transferência", + "details": "Detalhes", + "login-as-tenant-admin": "Login como administrador de locatários", + "login-as-customer-user": "Login como usuário do cliente", + "search": "Pesquisar usuários", + "selected-users": "{ count, plural, 1 {1 user} other {# users} } selecionado(s)", + "disable-account": "Desativar conta de usuário", + "enable-account": "Ativar conta de usuário", + "enable-account-message": "A conta de usuário foi ativada corretamente!", + "disable-account-message": "A conta de usuário foi desativada corretamente!" + }, + "value": { + "type": "Tipo de valor", + "string": "Cadeia de caracteres", + "string-value": "Valor da cadeia de caracteres", + "string-value-required": "O valor da cadeia de caracteres é obrigatório", + "integer": "Número inteiro", + "integer-value": "Valor do número inteiro", + "integer-value-required": "O valor do número inteiro é obrigatório", + "invalid-integer-value": "Valor de número inteiro inválido", + "double": "Duplo", + "double-value": "Valor duplo", + "double-value-required": "O valor duplo de caracteres é obrigatório", + "boolean": "Booliano", + "boolean-value": "Valor booliano", + "false": "Falso", + "true": "Verdadeiro", + "long": "Longo", + "json": "JSON", + "json-value": "Valor de JSON", + "json-value-invalid": "O formato do valor de JSON é inválido", + "json-value-required": "O valor de JSON é obrigatório." + }, + "widget": { + "widget-library": "Biblioteca de widgets", + "widget-bundle": "Pacote de widgets", + "select-widgets-bundle": "Selecionar pacote de widgets", + "management": "Gerenciamento de widgets", + "editor": "Editor de widgets", + "widget-type-not-found": "Problema ao carregar configuração de widget.
Provavelmente\n o tipo de widget foi removido.", + "widget-type-load-error": "O widget não foi carregado devido aos seguintes erros:", + "remove": "Remover widget", + "edit": "Editar widget", + "remove-widget-title": "Tem certeza de que deseja remover o widget '{{widgetTitle}}'?", + "remove-widget-text": "Após confirmar, não será possível recuperar o widget e nenhum dado associado.", + "timeseries": "Intervalos de tempo", + "search-data": "Pesquisar dados", + "no-data-found": "Nenhum dado encontrado", + "latest-values": "Últimos valores", + "rpc": "Widget de controle", + "alarm": "Widget de alarme", + "static": "Widget estático", + "select-widget-type": "Selecionar tipo de widget", + "missing-widget-title-error": "É necessário especificar o título de widget!", + "widget-saved": "Widget salvo", + "unable-to-save-widget-error": "Impossível salvar widget! O widget tem erros!", + "save": "Salvar widget", + "saveAs": "Salvar widget como", + "save-widget-type-as": "Salvar tipo de widget como", + "save-widget-type-as-text": "Inserir novo título de widget e/ou selecionar pacote de widgets alvo", + "toggle-fullscreen": "Alternar tela inteira", + "run": "Executar widget", + "title": "Título de widget", + "title-required": "O título do widget é obrigatório.", + "type": "Tipo de widget", + "resources": "Recursos", + "resource-url": "URL de JavaScript/CSS", + "resource-is-module": "É módulo", + "remove-resource": "Remover recurso", + "add-resource": "Adicionar recurso", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "Esquema de configuração", + "datakey-settings-schema": "Esquema de configuração de chave de dados", + "javascript": "Javascript", + "js": "JS", + "remove-widget-type-title": "Tem certeza de que deseja remover o tipo de widget '{{widgetName}}'?", + "remove-widget-type-text": "Após confirmar, não será possível recuperar o tipo de widget e nenhum dado associado.", + "remove-widget-type": "Remover tipo de widget", + "add-widget-type": "Adicionar novo tipo de widget", + "widget-type-load-failed-error": "Erro ao carregar tipo de widget!", + "widget-template-load-failed-error": "Erro ao carregar modelo de widget!", + "add": "Adicionar widget", + "undo": "Desfazer alterações de widget", + "export": "Exportar widget", + "no-data": "Nenhum dado para exibição em widget", + "data-overflow": "O widget exibe {{count}} de um total de {{total}} entidades", + "alarm-data-overflow": "O widget exibe alarmes para (no máximo) {{allowedEntities}} entidades de um total de {{totalEntities}} entidades" + }, + "widget-action": { + "header-button": "Botão de cabeçalho de widget", + "open-dashboard-state": "Navegar para novo estado de dashboard", + "update-dashboard-state": "Atualizar estado de dashboard atual", + "open-dashboard": "Navegar para outro dashboard", + "custom": "Personalizar ação", + "custom-pretty": "Personalizar ação (com modelo de HTML)", + "target-dashboard-state": "Estado de dashboard alvo", + "target-dashboard-state-required": "O estado do dashboard alvo é obrigatório", + "set-entity-from-widget": "Definir entidade em widget", + "target-dashboard": "Dashboard alvo", + "open-right-layout": "Abrir layout do dashboard à direita (exibição móvel)" + }, + "widgets-bundle": { + "current": "Pacote atual", + "widgets-bundles": "Pacotes de widgets", + "add": "Adicionar pacote de widgets", + "delete": "Excluir pacote de widgets", + "title": "Título", + "title-required": "O título é obrigatório.", + "add-widgets-bundle-text": "Adicionar novo pacote de widgets", + "no-widgets-bundles-text": "Nenhum pacote de widgets encontrado", + "empty": "O pacote de widgets está vazio", + "details": "Detalhes", + "widgets-bundle-details": "Detalhes do pacote de widgets", + "delete-widgets-bundle-title": "Tem certeza de que deseja excluir o pacote de widgets '{{widgetsBundleTitle}}'?", + "delete-widgets-bundle-text": "Cuidado, após confirmar, não será possível recuperar o pacote de widgets e nenhum dado associado.", + "delete-widgets-bundles-title": "Tem certeza de que deseja excluir { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }?", + "delete-widgets-bundles-action-title": "Excluir { count, plural, 1 {1 widgets bundle} other {# widgets bundles} }", + "delete-widgets-bundles-text": "Cuidado, após confirmar, todos os pacotes de widgets selecionados serão removidos e não será possível recuperar nenhum dado associado.", + "no-widgets-bundles-matching": "Nenhum conversor de pacotes de widgets encontrado que coincida com '{{widgetsBundle}}'.", + "widgets-bundle-required": "O pacote de widgets é obrigatório.", + "system": "Sistema", + "import": "Importar pacote de widgets", + "export": "Exportar pacote de widgets", + "export-failed-error": "Impossível exportar pacote de widgets: {{error}}", + "create-new-widgets-bundle": "Criar novo pacote de widgets", + "widgets-bundle-file": "Arquivo de pacote de widgets", + "invalid-widgets-bundle-file-error": "Impossível importar pacote de widgets: Estrutura de dados de pacote de widgets inválida.", + "search": "Pesquisar pacotes de widgets", + "selected-widgets-bundles": "{ count, plural, 1 {1 widgets bundle} other {# widgets bundles} } selecionado(s)", + "open-widgets-bundle": "Abrir pacote de widgets" + }, + "widget-config": { + "data": "Dados", + "settings": "Configurações", + "advanced": "Avançado", + "title": "Título", + "title-tooltip": "Título da dica de ferramenta", + "general-settings": "Configuração geral", + "display-title": "Exibir título", + "drop-shadow": "Sombra projetada", + "enable-fullscreen": "Habilitar tela inteira", + "background-color": "Cor de fundo", + "text-color": "Cor de texto", + "padding": "Preenchimento", + "margin": "Margem", + "widget-style": "Estilo de widget", + "title-style": "Estilo do título", + "mobile-mode-settings": "Configuração do modo móvel", + "order": "Ordem", + "height": "Altura", + "units": "Símbolo especial a ser exibido ao lado do valor", + "decimals": "Número de dígitos após ponto de flutuação", + "timewindow": "Janela de tempo", + "use-dashboard-timewindow": "Usar janela de tempo do dashboard", + "display-timewindow": "Exibir timewindow", + "display-legend": "Exibir legenda", + "datasources": "Fontes de dados", + "maximum-datasources": "No máximo { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", + "datasource-type": "Tipo", + "datasource-parameters": "Parâmetros", + "remove-datasource": "Remover fonte de dados", + "add-datasource": "Adicionar fonte de dados", + "target-device": "Dispositivo alvo", + "alarm-source": "Fonte do alarme", + "actions": "Ações", + "action": "Ação", + "add-action": "Adicionar ação", + "search-actions": "Pesquisar ações", + "no-actions-text": "Nenhuma ação encontrada", + "action-source": "Fonte da ação", + "action-source-required": "A fonte da ação é obrigatória.", + "action-name": "Nome", + "action-name-required": "O nome da ação é obrigatório!", + "action-name-not-unique": "Já existe outra ação com o mesmo nome.
O nome da ação na mesma fonte de ação deve ser exclusivo.", + "action-icon": "Ícone", + "action-type": "Tipo", + "action-type-required": "O tipo de ação é obrigatório.", + "edit-action": "Editar ação", + "delete-action": "Excluir ação", + "delete-action-title": "Excluir ação de widget", + "delete-action-text": "Tem certeza de que deseja excluir a ação de widget com o nome '{{actionName}}'?", + "display-icon": "Exibir ícone de título", + "icon-color": "Cor do ícone", + "icon-size": "Tamanho do ícone" + }, + "widget-type": { + "import": "Importar tipo de widget", + "export": "Exportar tipo de widget", + "export-failed-error": "Impossível exportar tipo de widget: {{error}}", + "create-new-widget-type": "Criar novo tipo de widget", + "widget-type-file": "Arquivo de tipo de widget", + "invalid-widget-type-file-error": "Impossível importar tipo de widget: Estrutura de dados de tipo de widget inválida." + }, + "widgets": { + "date-range-navigator": { + "localizationMap": { + "Sun": "Dom", + "Mon": "Seg", + "Tue": "Ter", + "Wed": "Qua", + "Thu": "Qui", + "Fri": "Sex", + "Sat": "Sáb", + "Jan": "Jan", + "Feb": "Fev", + "Mar": "Mar", + "Apr": "Abr", + "May": "Maio", + "Jun": "Jun", + "Jul": "Jul", + "Aug": "Ago", + "Sep": "Set", + "Oct": "Out", + "Nov": "Nov", + "Dec": "Dez", + "January": "Janeiro", + "February": "Fevereiro", + "March": "Março", + "April": "Abril", + "June": "Junho", + "July": "Julho", + "August": "Agosto", + "September": "Setembro", + "October": "Outubro", + "November": "Novembro", + "December": "Dezembro", + "Custom Date Range": "Personalizar intervalo de datas", + "Date Range Template": "Modelo de intervalo de datas", + "Today": "Hoje", + "Yesterday": "Ontem", + "This Week": "Esta semana", + "Last Week": "Semana passada", + "This Month": "Este mês", + "Last Month": "Mês passado", + "Year": "Ano", + "This Year": "Este ano", + "Last Year": "Ano passado", + "Date picker": "Seletor de data", + "Hour": "Hora", + "Day": "Dia", + "Week": "Semana", + "2 weeks": "2 semanas", + "Month": "Mês", + "3 months": "3 meses", + "6 months": "6 meses", + "Custom interval": "Intervalo personalizado", + "Interval": "Intervalo", + "Step size": "Tamanho da etapa", + "Ok": "OK" + } + }, + "input-widgets": { + "attribute-not-allowed": "O parâmetro de atributo não pode ser usado neste widget", + "blocked-location": "A geolocalização está bloqueada no seu navegador", + "claim-device": "Obter dispositivo", + "claim-failed": "Erro ao obter dispositivo!", + "claim-not-found": "Dispositivo não encontrado!", + "claim-successful": "O dispositivo foi obtido corretamente!", + "date": "Data", + "device-name": "Nome do dispositivo", + "device-name-required": "O nome do dispositivo é obrigatório", + "discard-changes": "Descartar alterações", + "entity-attribute-required": "O atributo da entidade é obrigatório", + "entity-coordinate-required": "Os dois campos, latitude e longitude, são obrigatórios", + "entity-timeseries-required": "Os intervalos de tempo de entidade são obrigatórios", + "get-location": "Obter localização atual", + "latitude": "Latitude", + "longitude": "Longitude", + "not-allowed-entity": "A entidade selecionada não pode ter atributos compartilhados", + "no-attribute-selected": "Nenhum atributo selecionado", + "no-datakey-selected": "Nenhuma chave de dados selecionada", + "no-coordinate-specified": "Chave de dados de latitude/longitude não especificada", + "no-entity-selected": "Nenhuma entidade selecionada", + "no-image": "Sem imagem", + "no-support-geolocation": "Seu navegador não é compatível com geolocalização", + "no-support-web-camera": "Nenhuma câmera Web compatível", + "no-timeseries-selected": "Nenhum intervalo de tempo selecionado", + "secret-key": "Chave de segredo", + "secret-key-required": "A chave de segredo é obrigatória", + "switch-attribute-value": "Alternar valor de atributo da entidade", + "switch-camera": "Alternar câmera", + "switch-timeseries-value": "Alternar valor de intervalo de tempo da entidade", + "take-photo": "Tirar foto", + "time": "Hora", + "timeseries-not-allowed": "O parâmetro de intervalo de série não pode ser usado neste widget", + "update-failed": "Atualização falhou", + "update-successful": "Atualização bem-sucedida", + "update-attribute": "Atualizar atributo", + "update-timeseries": "Atualizar intervalos de tempo", + "value": "Valor" + } + }, + "icon": { + "icon": "Ícone", + "select-icon": "Selecionar ícone", + "material-icons": "Ícones de materiais", + "show-all": "Mostrar todos os ícones" + }, + "custom": { + "widget-action": { + "action-cell-button": "Botão da célula de ação", + "row-click": "Clicar em linha", + "polygon-click": "Clique no polígono", + "marker-click": "Clique no marcador", + "tooltip-tag-action": "Ação do rótulo de dica de ferramenta", + "node-selected": "Selecionado no nó", + "element-click": "Clique em elemento de HTML", + "pie-slice-click": "Clique na fatia", + "row-double-click": "Dois clique na fila" + } + }, + "language": { + "language": "Idioma" + } +} From a64c4e3e203e1854c0ced8349dfb56e948a24ded Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 3 Nov 2020 13:05:31 +0200 Subject: [PATCH 061/102] DeviceProfileNode fix for conditions based on the attributes --- .../service/apiusage/DefaultTbApiUsageStateService.java | 6 +++--- .../controller/BaseTenantProfileControllerTest.java | 5 ++++- .../server/controller/ControllerSqlTestSuite.java | 2 +- .../server/dao/tenant/TenantProfileServiceImpl.java | 8 +++++++- .../server/dao/usagerecord/ApiUsageStateServiceImpl.java | 2 +- .../thingsboard/rule/engine/profile/AlarmRuleState.java | 4 ++++ 6 files changed, 20 insertions(+), 7 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 02d6e88284..f7e00b7242 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -156,7 +156,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsWsService.saveAndNotify(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); if (!result.isEmpty()) { persistAndNotify(tenantState, result); } @@ -256,7 +256,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } if (!profileThresholds.isEmpty()) { - tsWsService.saveAndNotify(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); } } @@ -266,7 +266,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long ts = System.currentTimeMillis(); List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); - tsWsService.saveAndNotify(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); } private void checkStartOfNextCycle() { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java index 311a93bf82..4f0d1e201b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseTenantProfileControllerTest.java @@ -23,6 +23,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.server.common.data.EntityInfo; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -285,7 +286,9 @@ public abstract class BaseTenantProfileControllerTest extends AbstractController TenantProfile tenantProfile = new TenantProfile(); tenantProfile.setName(name); tenantProfile.setDescription(name + " Test"); - tenantProfile.setProfileData(new TenantProfileData()); + TenantProfileData tenantProfileData = new TenantProfileData(); + tenantProfileData.setConfiguration(new DefaultTenantProfileConfiguration()); + tenantProfile.setProfileData(tenantProfileData); tenantProfile.setDefault(false); tenantProfile.setIsolatedTbCore(false); tenantProfile.setIsolatedTbRuleEngine(false); diff --git a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java index 64f9b8fddf..bc2c36b501 100644 --- a/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/controller/ControllerSqlTestSuite.java @@ -27,7 +27,7 @@ import java.util.Arrays; @RunWith(ClasspathSuite.class) @ClasspathSuite.ClassnameFilters({ // "org.thingsboard.server.controller.sql.WebsocketApiSqlTest", -// "org.thingsboard.server.controller.sql.EntityQueryControllerSqlTest", +// "org.thingsboard.server.controller.sql.TenantProfileControllerSqlTest", "org.thingsboard.server.controller.sql.*Test", }) public class ControllerSqlTestSuite { diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index af327d3505..5bafe4f799 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -216,6 +216,12 @@ public class TenantProfileServiceImpl extends AbstractEntityService implements T if (StringUtils.isEmpty(tenantProfile.getName())) { throw new DataValidationException("Tenant profile name should be specified!"); } + if (tenantProfile.getProfileData() == null) { + throw new DataValidationException("Tenant profile data should be specified!"); + } + if (tenantProfile.getProfileData().getConfiguration() == null) { + throw new DataValidationException("Tenant profile data configuration should be specified!"); + } if (tenantProfile.isDefault()) { TenantProfile defaultTenantProfile = findDefaultTenantProfile(tenantId); if (defaultTenantProfile != null && !defaultTenantProfile.getId().equals(tenantProfile.getId())) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index e1b494e2e8..26184a5824 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java index 8be2b81a59..b1eeb82b15 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmRuleState.java @@ -99,6 +99,10 @@ class AlarmRuleState { } } for (EntityKey key : changedKeys) { + EntityKeyType keyType = key.getType(); + if (EntityKeyType.CLIENT_ATTRIBUTE.equals(keyType) || EntityKeyType.SERVER_ATTRIBUTE.equals(keyType) || EntityKeyType.SHARED_ATTRIBUTE.equals(keyType)) { + key = new EntityKey(EntityKeyType.ATTRIBUTE, key.getKey()); + } if (entityKeys.contains(key)) { return true; } From 35dbe509f071d94fb1cf53fc637a3a8e4ff5e36f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 3 Nov 2020 13:43:27 +0200 Subject: [PATCH 062/102] Data Points storage --- .../DefaultTbApiUsageStateService.java | 4 +- .../AbstractSubscriptionService.java | 6 +-- .../DefaultTelemetrySubscriptionService.java | 40 +++++++++++-------- .../telemetry/InternalTelemetryService.java | 2 +- .../dao/timeseries/TimeseriesService.java | 4 +- 5 files changed, 32 insertions(+), 24 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index f7e00b7242..88868d2258 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -71,9 +71,9 @@ import java.util.concurrent.locks.ReentrantLock; public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; - public static final FutureCallback VOID_CALLBACK = new FutureCallback() { + public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Void result) {} + public void onSuccess(@Nullable Integer result) {} @Override public void onFailure(Throwable t) {} diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index bc4099e81b..4178502c77 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -105,10 +105,10 @@ public abstract class AbstractSubscriptionService implements ApplicationListener } } - protected void addWsCallback(ListenableFuture> saveFuture, Consumer callback) { - Futures.addCallback(saveFuture, new FutureCallback>() { + protected void addWsCallback(ListenableFuture> saveFuture, Consumer callback) { + Futures.addCallback(saveFuture, new FutureCallback>() { @Override - public void onSuccess(@Nullable List result) { + public void onSuccess(@Nullable List result) { callback.accept(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index d4786a6f54..53d0e08da2 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -20,10 +20,9 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.context.event.EventListener; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.id.EntityId; @@ -42,10 +41,9 @@ import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.queue.TbClusterService; -import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; import javax.annotation.Nullable; @@ -59,12 +57,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; -import java.util.function.Consumer; -import java.util.stream.Collectors; /** * Created by ashvayka on 27.03.18. @@ -76,6 +70,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private final AttributesService attrService; private final TimeseriesService tsService; private final EntityViewService entityViewService; + private final TbApiUsageClient apiUsageClient; private ExecutorService tsCallBackExecutor; @@ -83,11 +78,13 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer TimeseriesService tsService, EntityViewService entityViewService, TbClusterService clusterService, - PartitionService partitionService) { + PartitionService partitionService, + TbApiUsageClient apiUsageClient) { super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; this.entityViewService = entityViewService; + this.apiUsageClient = apiUsageClient; } @PostConstruct @@ -117,12 +114,25 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { checkInternalEntity(entityId); - saveAndNotifyInternal(tenantId, entityId, ts, ttl, callback); + saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback() { + @Override + public void onSuccess(Integer result) { + if (result != null && result > 0) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); + } + callback.onSuccess(null); + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }); } @Override - public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { - ListenableFuture> saveFuture = tsService.save(tenantId, entityId, ts, ttl); + public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { + ListenableFuture> saveFuture = tsService.save(tenantId, entityId, ts, ttl); addMainCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) { @@ -147,9 +157,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer Optional tsKvEntry = entries.stream() .filter(entry -> entry.getTs() > startTs && entry.getTs() <= endTs) .max(Comparator.comparingLong(TsKvEntry::getTs)); - if (tsKvEntry.isPresent()) { - entityViewLatest.add(tsKvEntry.get()); - } + tsKvEntry.ifPresent(entityViewLatest::add); } } if (!entityViewLatest.isEmpty()) { diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index 275630a161..53cc23f23a 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -29,7 +29,7 @@ import java.util.List; */ public interface InternalTelemetryService extends RuleEngineTelemetryService { - void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); + void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 2a5a701f9e..81dedff132 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -36,9 +36,9 @@ public interface TimeseriesService { ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId); - ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); + ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); - ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); + ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); ListenableFuture> saveLatest(TenantId tenantId, EntityId entityId, List tsKvEntry); From 123ea76c94718c4eaf251a738887727ad2564abb Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 3 Nov 2020 18:59:12 +0200 Subject: [PATCH 063/102] Improvements for data points storage --- .../DefaultTbApiUsageStateService.java | 30 ++++---- .../DefaultRuleEngineStatisticsService.java | 8 +-- .../AbstractSubscriptionService.java | 6 +- .../DefaultTelemetrySubscriptionService.java | 68 +++++++++++++------ .../telemetry/InternalTelemetryService.java | 2 + .../dao/timeseries/TimeseriesService.java | 4 +- .../server/common/data/kv/BasicTsKvEntry.java | 19 +++++- .../server/common/data/kv/TsKvEntry.java | 5 ++ ...stractChunkedAggregationTimeseriesDao.java | 2 +- .../dao/sqlts/AbstractSqlTimeseriesDao.java | 21 ++++++ .../dao/sqlts/hsql/JpaHsqlTimeseriesDao.java | 7 +- .../dao/sqlts/psql/JpaPsqlTimeseriesDao.java | 7 +- .../timescale/TimescaleTimeseriesDao.java | 11 +-- .../dao/tenant/TenantProfileServiceImpl.java | 2 +- .../dao/timeseries/BaseTimeseriesService.java | 34 +++++++--- .../CassandraBaseTimeseriesDao.java | 19 ++++-- .../server/dao/timeseries/TimeseriesDao.java | 4 +- .../usagerecord/ApiUsageStateServiceImpl.java | 2 +- .../dao/service/BaseEntityServiceTest.java | 4 +- 19 files changed, 181 insertions(+), 74 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 88868d2258..42f980909a 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -18,7 +18,9 @@ package org.thingsboard.server.service.apiusage; import com.google.common.util.concurrent.FutureCallback; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Lazy; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; @@ -51,7 +53,6 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.profile.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; -import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import javax.annotation.PostConstruct; import java.util.ArrayList; @@ -73,20 +74,25 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public static final String HOURLY = "Hourly"; public static final FutureCallback VOID_CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Integer result) {} + public void onSuccess(@Nullable Integer result) { + } @Override - public void onFailure(Throwable t) {} + public void onFailure(Throwable t) { + } }; private final TbClusterService clusterService; private final PartitionService partitionService; private final TenantService tenantService; - private final ApiUsageStateService apiUsageStateService; private final TimeseriesService tsService; - private final InternalTelemetryService tsWsService; + private final ApiUsageStateService apiUsageStateService; private final SchedulerComponent scheduler; private final TbTenantProfileCache tenantProfileCache; + @Lazy + @Autowired + private InternalTelemetryService tsWsService; + // Tenants that should be processed on this server private final Map myTenantStates = new ConcurrentHashMap<>(); // Tenants that should be processed on other servers @@ -102,16 +108,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public DefaultTbApiUsageStateService(TbClusterService clusterService, PartitionService partitionService, - TenantService tenantService, ApiUsageStateService apiUsageStateService, - TimeseriesService tsService, TelemetrySubscriptionService tsWsService, + TenantService tenantService, + TimeseriesService tsService, + ApiUsageStateService apiUsageStateService, SchedulerComponent scheduler, TbTenantProfileCache tenantProfileCache) { this.clusterService = clusterService; this.partitionService = partitionService; this.tenantService = tenantService; - this.apiUsageStateService = apiUsageStateService; this.tsService = tsService; - this.tsWsService = tsWsService; + this.apiUsageStateService = apiUsageStateService; this.scheduler = scheduler; this.tenantProfileCache = tenantProfileCache; } @@ -156,7 +162,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } finally { updateLock.unlock(); } - tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, tenantState.getApiUsageState().getId(), updatedEntries, VOID_CALLBACK); if (!result.isEmpty()) { persistAndNotify(tenantState, result); } @@ -256,7 +262,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } if (!profileThresholds.isEmpty()) { - tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(tenantId, id, profileThresholds, VOID_CALLBACK); } } @@ -266,7 +272,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long ts = System.currentTimeMillis(); List stateTelemetry = new ArrayList<>(); result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); - tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, 0L, VOID_CALLBACK); + tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java index b46033389b..a832eb0870 100644 --- a/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java +++ b/application/src/main/java/org/thingsboard/server/service/stats/DefaultRuleEngineStatisticsService.java @@ -48,9 +48,9 @@ import java.util.stream.Collectors; public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsService { public static final String TB_SERVICE_QUEUE = "TbServiceQueue"; - public static final FutureCallback CALLBACK = new FutureCallback() { + public static final FutureCallback CALLBACK = new FutureCallback() { @Override - public void onSuccess(@Nullable Void result) { + public void onSuccess(@Nullable Integer result) { } @@ -85,7 +85,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS .map(kv -> new BasicTsKvEntry(ts, new LongDataEntry(kv.getKey(), (long) kv.getValue().get()))) .collect(Collectors.toList()); if (!tsList.isEmpty()) { - tsService.saveAndNotify(tenantId, serviceAssetId, tsList, CALLBACK); + tsService.saveAndNotifyInternal(tenantId, serviceAssetId, tsList, CALLBACK); } } } catch (DataValidationException e) { @@ -97,7 +97,7 @@ public class DefaultRuleEngineStatisticsService implements RuleEngineStatisticsS ruleEngineStats.getTenantExceptions().forEach((tenantId, e) -> { TsKvEntry tsKv = new BasicTsKvEntry(ts, new JsonDataEntry("ruleEngineException", e.toJsonString())); try { - tsService.saveAndNotify(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), CALLBACK); + tsService.saveAndNotifyInternal(tenantId, getServiceAssetId(tenantId, queueName), Collections.singletonList(tsKv), CALLBACK); } catch (DataValidationException e2) { if (!e2.getMessage().equalsIgnoreCase("Asset is referencing to non-existent tenant!")) { throw e2; diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java index 4178502c77..468c3c26b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/AbstractSubscriptionService.java @@ -105,10 +105,10 @@ public abstract class AbstractSubscriptionService implements ApplicationListener } } - protected void addWsCallback(ListenableFuture> saveFuture, Consumer callback) { - Futures.addCallback(saveFuture, new FutureCallback>() { + protected void addWsCallback(ListenableFuture saveFuture, Consumer callback) { + Futures.addCallback(saveFuture, new FutureCallback() { @Override - public void onSuccess(@Nullable List result) { + public void onSuccess(@Nullable T result) { callback.accept(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 53d0e08da2..7a35e6b317 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, @@ -40,9 +40,11 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.timeseries.TimeseriesService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; @@ -71,6 +73,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private final TimeseriesService tsService; private final EntityViewService entityViewService; private final TbApiUsageClient apiUsageClient; + private final TbApiUsageStateService apiUsageStateService; private ExecutorService tsCallBackExecutor; @@ -79,12 +82,14 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer EntityViewService entityViewService, TbClusterService clusterService, PartitionService partitionService, - TbApiUsageClient apiUsageClient) { + TbApiUsageClient apiUsageClient, + TbApiUsageStateService apiUsageStateService) { super(clusterService, partitionService); this.attrService = attrService; this.tsService = tsService; this.entityViewService = entityViewService; this.apiUsageClient = apiUsageClient; + this.apiUsageStateService = apiUsageStateService; } @PostConstruct @@ -114,25 +119,34 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotify(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { checkInternalEntity(entityId); - saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback() { - @Override - public void onSuccess(Integer result) { - if (result != null && result > 0) { - apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); + if (apiUsageStateService.getApiUsageState(tenantId).isDbStorageEnabled()) { + saveAndNotifyInternal(tenantId, entityId, ts, ttl, new FutureCallback() { + @Override + public void onSuccess(Integer result) { + if (result != null && result > 0) { + apiUsageClient.report(tenantId, ApiUsageRecordKey.STORAGE_DP_COUNT, result); + } + callback.onSuccess(null); } - callback.onSuccess(null); - } - @Override - public void onFailure(Throwable t) { - callback.onFailure(t); - } - }); + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }); + } else{ + callback.onFailure(new RuntimeException("DB storage writes are disabled due to API limits!")); + } + } + + @Override + public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) { + saveAndNotifyInternal(tenantId, entityId, ts, 0L, callback); } @Override public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback) { - ListenableFuture> saveFuture = tsService.save(tenantId, entityId, ts, ttl); + ListenableFuture saveFuture = tsService.save(tenantId, entityId, ts, ttl); addMainCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); if (EntityType.DEVICE.equals(entityId.getEntityType()) || EntityType.ASSET.equals(entityId.getEntityType())) { @@ -197,7 +211,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback) { ListenableFuture> saveFuture = attrService.save(tenantId, entityId, scope, attributes); - addMainCallback(saveFuture, callback); + addVoidCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onAttributesUpdate(tenantId, entityId, scope, attributes, notifyDevice)); } @@ -210,7 +224,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback) { ListenableFuture> saveFuture = tsService.saveLatest(tenantId, entityId, ts); - addMainCallback(saveFuture, callback); + addVoidCallback(saveFuture, callback); addWsCallback(saveFuture, success -> onTimeSeriesUpdate(tenantId, entityId, ts)); } @@ -223,7 +237,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback) { ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys); - addMainCallback(deleteFuture, callback); + addVoidCallback(deleteFuture, callback); addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys)); } @@ -236,7 +250,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback) { ListenableFuture> deleteFuture = tsService.removeLatest(tenantId, entityId, keys); - addMainCallback(deleteFuture, callback); + addVoidCallback(deleteFuture, callback); } @Override @@ -321,7 +335,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer } } - private void addMainCallback(ListenableFuture saveFuture, final FutureCallback callback) { + private void addVoidCallback(ListenableFuture saveFuture, final FutureCallback callback) { Futures.addCallback(saveFuture, new FutureCallback() { @Override public void onSuccess(@Nullable S result) { @@ -335,6 +349,20 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer }, tsCallBackExecutor); } + private void addMainCallback(ListenableFuture saveFuture, final FutureCallback callback) { + Futures.addCallback(saveFuture, new FutureCallback() { + @Override + public void onSuccess(@Nullable S result) { + callback.onSuccess(result); + } + + @Override + public void onFailure(Throwable t) { + callback.onFailure(t); + } + }, tsCallBackExecutor); + } + private void checkInternalEntity(EntityId entityId) { if (EntityType.API_USAGE_STATE.equals(entityId.getEntityType())) { throw new RuntimeException("Can't update API Usage State!"); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index 53cc23f23a..1ca2cdeacb 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -29,6 +29,8 @@ import java.util.List; */ public interface InternalTelemetryService extends RuleEngineTelemetryService { + void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback); + void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, long ttl, FutureCallback callback); void saveAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List attributes, boolean notifyDevice, FutureCallback callback); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 81dedff132..0e11aedaf5 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java @@ -36,9 +36,9 @@ public interface TimeseriesService { ListenableFuture> findAllLatest(TenantId tenantId, EntityId entityId); - ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); + ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry); - ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); + ListenableFuture save(TenantId tenantId, EntityId entityId, List tsKvEntry, long ttl); ListenableFuture> saveLatest(TenantId tenantId, EntityId entityId, List tsKvEntry); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java index c2d6688004..bcf49899dc 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BasicTsKvEntry.java @@ -19,7 +19,7 @@ import java.util.Objects; import java.util.Optional; public class BasicTsKvEntry implements TsKvEntry { - + private static final int MAX_CHARS_PER_DATA_POINT = 512; private final long ts; private final KvEntry kv; @@ -99,4 +99,21 @@ public class BasicTsKvEntry implements TsKvEntry { public String getValueAsString() { return kv.getValueAsString(); } + + @Override + public int getDataPoints() { + int length; + switch (getDataType()) { + case STRING: + length = getStrValue().get().length(); + break; + case JSON: + length = getJsonValue().get().length(); + break; + default: + return 1; + } + return Math.max(1, (length + MAX_CHARS_PER_DATA_POINT - 1) / MAX_CHARS_PER_DATA_POINT); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java index 315d3940df..e2f8eaf1e5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvEntry.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.common.data.kv; +import com.fasterxml.jackson.annotation.JsonIgnore; + /** * Represents time series KV data entry * @@ -25,4 +27,7 @@ public interface TsKvEntry extends KvEntry { long getTs(); + @JsonIgnore + int getDataPoints(); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 3cee730ee3..616260ba45 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -100,7 +100,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { return Futures.immediateFuture(null); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java index 8dcefeca61..c585804b82 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractSqlTimeseriesDao.java @@ -30,11 +30,14 @@ import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import javax.annotation.Nullable; import java.util.List; import java.util.Objects; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @Slf4j public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseriesDao implements AggregationTimeseriesDao { + protected static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); + @Autowired protected ScheduledLogExecutorComponent logExecutor; @@ -56,6 +59,9 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries @Value("${sql.batch_sort:false}") protected boolean batchSortEnabled; + @Value("${sql.ttl.ts.ts_key_value_ttl:0}") + private long systemTtl; + protected ListenableFuture> processFindAllAsync(TenantId tenantId, EntityId entityId, List queries) { List>> futures = queries .stream() @@ -75,4 +81,19 @@ public abstract class AbstractSqlTimeseriesDao extends BaseAbstractSqlTimeseries } }, service); } + + protected long computeTtl(long ttl) { + if (systemTtl > 0) { + if (ttl == 0) { + ttl = systemTtl; + } else { + ttl = Math.min(systemTtl, ttl); + } + } + return ttl; + } + + protected int getDataPointDays(TsKvEntry tsKvEntry, long ttl) { + return tsKvEntry.getDataPoints() * Math.max(1, (int) (ttl / SECONDS_IN_DAY)); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java index 5ea387328d..025134e187 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/hsql/JpaHsqlTimeseriesDao.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.dao.sqlts.hsql; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.EntityId; @@ -35,7 +37,8 @@ import org.thingsboard.server.dao.util.SqlTsDao; public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDao implements TimeseriesDao { @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); TsKvEntity entity = new TsKvEntity(); @@ -48,7 +51,7 @@ public class JpaHsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); log.trace("Saving entity: {}", entity); - return tsQueue.add(entity); + return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java index 8c50bcb38c..3d971f61c6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/psql/JpaPsqlTimeseriesDao.java @@ -15,7 +15,9 @@ */ package org.thingsboard.server.dao.sqlts.psql; +import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -71,7 +73,8 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); savePartitionIfNotExist(tsKvEntry.getTs()); String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); @@ -85,7 +88,7 @@ public class JpaPsqlTimeseriesDao extends AbstractChunkedAggregationTimeseriesDa entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); log.trace("Saving entity: {}", entity); - return tsQueue.add(entity); + return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } private void savePartitionIfNotExist(long ts) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 6d5923c508..f6fb027d7e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.MoreExecutors; import com.google.common.util.concurrent.SettableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Sort; import org.springframework.stereotype.Component; @@ -101,7 +102,8 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + int dataPointDays = getDataPointDays(tsKvEntry, computeTtl(ttl)); String strKey = tsKvEntry.getKey(); Integer keyId = getOrSaveKeyId(strKey); TimescaleTsKvEntity entity = new TimescaleTsKvEntity(); @@ -113,14 +115,13 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements entity.setLongValue(tsKvEntry.getLongValue().orElse(null)); entity.setBooleanValue(tsKvEntry.getBooleanValue().orElse(null)); entity.setJsonValue(tsKvEntry.getJsonValue().orElse(null)); - log.trace("Saving entity to timescale db: {}", entity); - return tsQueue.add(entity); + return Futures.transform(tsQueue.add(entity), v -> dataPointDays, MoreExecutors.directExecutor()); } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { - return Futures.immediateFuture(null); + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + return Futures.immediateFuture(0); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java index 5bafe4f799..95dd0ab45a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/TenantProfileServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index b86d6128f6..3b97e2bb82 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.dao.timeseries; +import com.google.common.base.Function; import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @@ -55,6 +57,20 @@ public class BaseTimeseriesService implements TimeseriesService { private static final int INSERTS_PER_ENTRY = 3; private static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY; + public static final Function, Integer> SUM_ALL_INTEGERS = new Function, Integer>() { + @Override + public @Nullable Integer apply(@Nullable List input) { + int result = 0; + if (input != null) { + for (Integer tmp : input) { + if (tmp != null) { + result += tmp; + } + } + } + return result; + } + }; @Value("${database.ts_max_intervals}") private long maxTsIntervals; @@ -101,26 +117,26 @@ public class BaseTimeseriesService implements TimeseriesService { } @Override - public ListenableFuture> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) { validate(entityId); if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } - List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); + List> futures = Lists.newArrayListWithExpectedSize(INSERTS_PER_ENTRY); saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, 0L); - return Futures.allAsList(futures); + return Futures.transform(Futures.allAsList(futures), SUM_ALL_INTEGERS, MoreExecutors.directExecutor()); } @Override - public ListenableFuture> save(TenantId tenantId, EntityId entityId, List tsKvEntries, long ttl) { - List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY); + public ListenableFuture save(TenantId tenantId, EntityId entityId, List tsKvEntries, long ttl) { + List> futures = Lists.newArrayListWithExpectedSize(tsKvEntries.size() * INSERTS_PER_ENTRY); for (TsKvEntry tsKvEntry : tsKvEntries) { if (tsKvEntry == null) { throw new IncorrectParameterException("Key value entry can't be null"); } saveAndRegisterFutures(tenantId, futures, entityId, tsKvEntry, ttl); } - return Futures.allAsList(futures); + return Futures.transform(Futures.allAsList(futures), SUM_ALL_INTEGERS, MoreExecutors.directExecutor()); } @Override @@ -135,12 +151,12 @@ public class BaseTimeseriesService implements TimeseriesService { return Futures.allAsList(futures); } - private void saveAndRegisterFutures(TenantId tenantId, List> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + private void saveAndRegisterFutures(TenantId tenantId, List> futures, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) { throw new IncorrectParameterException("Telemetry data can't be stored for entity view. Read only"); } futures.add(timeseriesDao.savePartition(tenantId, entityId, tsKvEntry.getTs(), tsKvEntry.getKey(), ttl)); - futures.add(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry)); + futures.add(Futures.transform(timeseriesLatestDao.saveLatest(tenantId, entityId, tsKvEntry), v -> 0, MoreExecutors.directExecutor())); futures.add(timeseriesDao.save(tenantId, entityId, tsKvEntry, ttl)); } @@ -216,7 +232,7 @@ public class BaseTimeseriesService implements TimeseriesService { } else if (query.getAggregation() == null) { throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty"); } - if(!Aggregation.NONE.equals(query.getAggregation())) { + if (!Aggregation.NONE.equals(query.getAggregation())) { long step = Math.max(query.getInterval(), 1000); long intervalCounts = (query.getEndTs() - query.getStartTs()) / step; if (intervalCounts > maxTsIntervals || intervalCounts < 0) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 2f3fe4854c..b9d8c62833 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -60,6 +60,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import static com.datastax.oss.driver.api.querybuilder.QueryBuilder.literal; @@ -74,6 +75,8 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD protected static final int MIN_AGGREGATION_STEP_MS = 1000; public static final String ASC_ORDER = "ASC"; + public static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); + protected static List FIXED_PARTITION = Arrays.asList(new Long[]{0L}); @Autowired @@ -141,9 +144,10 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } @Override - public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { + public ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl) { List> futures = new ArrayList<>(); ttl = computeTtl(ttl); + int dataPointDays = tsKvEntry.getDataPoints() * Math.max(1, (int) (ttl / SECONDS_IN_DAY)); long partition = toPartitionTs(tsKvEntry.getTs()); DataType type = tsKvEntry.getDataType(); if (setNullValuesEnabled) { @@ -161,11 +165,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } BoundStatement stmt = stmtBuilder.build(); futures.add(getFuture(executeAsyncWrite(tenantId, stmt), rs -> null)); - return Futures.transform(Futures.allAsList(futures), result -> null, MoreExecutors.directExecutor()); + return Futures.transform(Futures.allAsList(futures), result -> dataPointDays, MoreExecutors.directExecutor()); } @Override - public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { + public ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl) { if (isFixedPartitioning()) { return Futures.immediateFuture(null); } @@ -181,7 +185,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD stmtBuilder.setInt(4, (int) ttl); } BoundStatement stmt = stmtBuilder.build(); - return getFuture(executeAsyncWrite(tenantId, stmt), rs -> null); + return getFuture(executeAsyncWrite(tenantId, stmt), rs -> 0); } @Override @@ -649,9 +653,10 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD } /** - // * Select existing partitions from the table - // * {@link ModelConstants#TS_KV_PARTITIONS_CF} for the given entity - // */ + * // * Select existing partitions from the table + * // * {@link ModelConstants#TS_KV_PARTITIONS_CF} for the given entity + * // + */ private TbResultSetFuture fetchPartitions(TenantId tenantId, EntityId entityId, String key, long minPartition, long maxPartition) { Select select = QueryBuilder.selectFrom(ModelConstants.TS_KV_PARTITIONS_CF).column(ModelConstants.PARTITION_COLUMN) .whereColumn(ModelConstants.ENTITY_TYPE_COLUMN).isEqualTo(literal(entityId.getEntityType().name())) diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java index dda2b227c3..2dd52580b6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java @@ -31,9 +31,9 @@ public interface TimeseriesDao { ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries); - ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl); + ListenableFuture save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry, long ttl); - ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl); + ListenableFuture savePartition(TenantId tenantId, EntityId entityId, long tsKvEntryTs, String key, long ttl); ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 26184a5824..e1b494e2e8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -5,7 +5,7 @@ * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * - * http://www.apache.org/licenses/LICENSE-2.0 + * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java index d04d18772d..ff87e17d56 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseEntityServiceTest.java @@ -793,7 +793,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { } } - List>> timeseriesFutures = new ArrayList<>(); + List> timeseriesFutures = new ArrayList<>(); for (int i = 0; i < devices.size(); i++) { Device device = devices.get(i); timeseriesFutures.add(saveLongTimeseries(device.getId(), "temperature", temperatures.get(i))); @@ -1272,7 +1272,7 @@ public abstract class BaseEntityServiceTest extends AbstractServiceTest { return attributesService.save(SYSTEM_TENANT_ID, entityId, scope, Collections.singletonList(attr)); } - private ListenableFuture> saveLongTimeseries(EntityId entityId, String key, Double value) { + private ListenableFuture saveLongTimeseries(EntityId entityId, String key, Double value) { TsKvEntity tsKv = new TsKvEntity(); tsKv.setStrKey(key); tsKv.setDoubleValue(value); From 91e13a69bc16faa8573b13b416b29fb4e06ffaa6 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 2 Nov 2020 10:21:44 +0200 Subject: [PATCH 064/102] removed TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES from queue-kafka.env --- docker/queue-kafka.env | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docker/queue-kafka.env b/docker/queue-kafka.env index 0207d64ef5..fec3cc50a6 100644 --- a/docker/queue-kafka.env +++ b/docker/queue-kafka.env @@ -1,3 +1,2 @@ TB_QUEUE_TYPE=kafka -TB_KAFKA_SERVERS=kafka:9092 -TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES=retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600;partitions:100 +TB_KAFKA_SERVERS=kafka:9092 \ No newline at end of file From 84ca08d36c64a3d27bd6a395ca01752a622fcf03 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 2 Nov 2020 10:27:24 +0200 Subject: [PATCH 065/102] added new line --- docker/queue-kafka.env | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/queue-kafka.env b/docker/queue-kafka.env index fec3cc50a6..63107942fb 100644 --- a/docker/queue-kafka.env +++ b/docker/queue-kafka.env @@ -1,2 +1,2 @@ TB_QUEUE_TYPE=kafka -TB_KAFKA_SERVERS=kafka:9092 \ No newline at end of file +TB_KAFKA_SERVERS=kafka:9092 From 938588c5bc0ad5491256d7c63947ed6f826e2c12 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 30 Oct 2020 12:23:08 +0200 Subject: [PATCH 066/102] sqs producer improvements --- .../server/queue/sqs/TbAwsSqsProducerTemplate.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java index 3e508b09e6..a4e977b6f0 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java @@ -87,8 +87,9 @@ public class TbAwsSqsProducerTemplate implements TbQueuePr sendMsgRequest.withQueueUrl(getQueueUrl(tpi.getFullTopicName())); sendMsgRequest.withMessageBody(gson.toJson(new DefaultTbQueueMsg(msg))); - sendMsgRequest.withMessageGroupId(tpi.getTopic()); - sendMsgRequest.withMessageDeduplicationId(UUID.randomUUID().toString()); + String sqsMsgId = UUID.randomUUID().toString(); + sendMsgRequest.withMessageGroupId(sqsMsgId); + sendMsgRequest.withMessageDeduplicationId(sqsMsgId); ListenableFuture future = producerExecutor.submit(() -> sqsClient.sendMessage(sendMsgRequest)); From eeec6bac9fe510e0db95d597bf14bc8d836a55eb Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Fri, 30 Oct 2020 13:29:48 +0200 Subject: [PATCH 067/102] sqs producer improvements --- msa/js-executor/queue/awsSqsTemplate.js | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/msa/js-executor/queue/awsSqsTemplate.js b/msa/js-executor/queue/awsSqsTemplate.js index 52d4997f33..24315e5cad 100644 --- a/msa/js-executor/queue/awsSqsTemplate.js +++ b/msa/js-executor/queue/awsSqsTemplate.js @@ -52,11 +52,13 @@ function AwsSqsProducer() { queueUrls.set(responseTopic, responseQueueUrl); } + let msgId = uuid(); + let params = { MessageBody: msgBody, QueueUrl: responseQueueUrl, - MessageGroupId: 'js_eval', - MessageDeduplicationId: uuid() + MessageGroupId: msgId, + MessageDeduplicationId: msgId }; return new Promise((resolve, reject) => { From ecf530c63ebc489e01b1857bc0ba55875078c10a Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 3 Nov 2020 17:33:57 +0200 Subject: [PATCH 068/102] alarm state improvements (added details to alarm from device profile details pattern) --- .../rule/engine/profile/AlarmState.java | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index df46a4bf88..56fe68f010 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.action.TbAlarmResult; @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -74,7 +76,7 @@ class AlarmState { public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; - AlarmSeverity resultSeverity = null; + AlarmRuleState resultState = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { if (!validateUpdate(update, state)) { @@ -84,15 +86,15 @@ class AlarmState { AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); if (AlarmEvalResult.TRUE.equals(evalResult)) { - resultSeverity = state.getSeverity(); + resultState = state; break; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { state.clear(); stateUpdate |= state.checkUpdate(); } } - if (resultSeverity != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); + if (resultState != null) { + TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); if (result != null) { pushMsg(ctx, result); } @@ -186,7 +188,8 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { + AlarmSeverity severity = ruleState.getSeverity(); if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). // Maybe we should fetch alarm every time? @@ -212,7 +215,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode()); + currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -225,6 +228,44 @@ class AlarmState { } } + private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { + ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); + + if (alarmDetails != null) { + for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { + EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); + alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + } + + details.put("data", alarmDetails); + } + + return details; + } + + private static String getValueAsString(EntityKeyValue entityKeyValue) { + Object result = null; + switch (entityKeyValue.getDataType()) { + case STRING: + result = entityKeyValue.getStrValue(); + break; + case JSON: + result = entityKeyValue.getJsonValue(); + break; + case LONG: + result = String.valueOf(entityKeyValue.getLngValue()); + break; + case DOUBLE: + result = String.valueOf(entityKeyValue.getDblValue()); + break; + case BOOLEAN: + result = String.valueOf(entityKeyValue.getBoolValue()); + break; + } + return String.valueOf(result); + } + public boolean processAlarmClear(TbContext ctx, Alarm alarmNf) { boolean updated = false; if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { From da8a03e98efb359ba40556ab22d9c29c40005207 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Nov 2020 13:40:38 +0200 Subject: [PATCH 069/102] refactored --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 56fe68f010..0d729c5d69 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -254,13 +254,13 @@ class AlarmState { result = entityKeyValue.getJsonValue(); break; case LONG: - result = String.valueOf(entityKeyValue.getLngValue()); + result = entityKeyValue.getLngValue(); break; case DOUBLE: - result = String.valueOf(entityKeyValue.getDblValue()); + result = entityKeyValue.getDblValue(); break; case BOOLEAN: - result = String.valueOf(entityKeyValue.getBoolValue()); + result = entityKeyValue.getBoolValue(); break; } return String.valueOf(result); From 1c4ab0e71162942353e2d2b4403da12a8b6c107b Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 4 Nov 2020 18:02:37 +0200 Subject: [PATCH 070/102] Revert "refactored" This reverts commit da8a03e98efb359ba40556ab22d9c29c40005207. --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 0d729c5d69..56fe68f010 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -254,13 +254,13 @@ class AlarmState { result = entityKeyValue.getJsonValue(); break; case LONG: - result = entityKeyValue.getLngValue(); + result = String.valueOf(entityKeyValue.getLngValue()); break; case DOUBLE: - result = entityKeyValue.getDblValue(); + result = String.valueOf(entityKeyValue.getDblValue()); break; case BOOLEAN: - result = entityKeyValue.getBoolValue(); + result = String.valueOf(entityKeyValue.getBoolValue()); break; } return String.valueOf(result); From a99a8e458c3bbfc0d25b960547154a3351daf2b4 Mon Sep 17 00:00:00 2001 From: Andrew Shvayka Date: Wed, 4 Nov 2020 18:02:37 +0200 Subject: [PATCH 071/102] Revert "alarm state improvements (added details to alarm from device profile details pattern)" This reverts commit ecf530c63ebc489e01b1857bc0ba55875078c10a. --- .../rule/engine/profile/AlarmState.java | 53 +++---------------- 1 file changed, 6 insertions(+), 47 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 56fe68f010..df46a4bf88 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -16,7 +16,6 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.action.TbAlarmResult; @@ -30,7 +29,6 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.query.EntityKeyType; -import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -76,7 +74,7 @@ class AlarmState { public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; - AlarmRuleState resultState = null; + AlarmSeverity resultSeverity = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { if (!validateUpdate(update, state)) { @@ -86,15 +84,15 @@ class AlarmState { AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); if (AlarmEvalResult.TRUE.equals(evalResult)) { - resultState = state; + resultSeverity = state.getSeverity(); break; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { state.clear(); stateUpdate |= state.checkUpdate(); } } - if (resultState != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); + if (resultSeverity != null) { + TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); if (result != null) { pushMsg(ctx, result); } @@ -188,8 +186,7 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { - AlarmSeverity severity = ruleState.getSeverity(); + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). // Maybe we should fetch alarm every time? @@ -215,7 +212,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); + currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode()); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -228,44 +225,6 @@ class AlarmState { } } - private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { - ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); - String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); - - if (alarmDetails != null) { - for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { - EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); - alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); - } - - details.put("data", alarmDetails); - } - - return details; - } - - private static String getValueAsString(EntityKeyValue entityKeyValue) { - Object result = null; - switch (entityKeyValue.getDataType()) { - case STRING: - result = entityKeyValue.getStrValue(); - break; - case JSON: - result = entityKeyValue.getJsonValue(); - break; - case LONG: - result = String.valueOf(entityKeyValue.getLngValue()); - break; - case DOUBLE: - result = String.valueOf(entityKeyValue.getDblValue()); - break; - case BOOLEAN: - result = String.valueOf(entityKeyValue.getBoolValue()); - break; - } - return String.valueOf(result); - } - public boolean processAlarmClear(TbContext ctx, Alarm alarmNf) { boolean updated = false; if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { From 6dbb4897b4816a4aa4733e0745f18dd5cd755a43 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 4 Nov 2020 20:17:56 +0200 Subject: [PATCH 072/102] UI: Fixed resize width windows device-wizard-dialog; improvement translate en_US --- .../wizard/device-wizard-dialog.component.html | 5 ++++- .../wizard/device-wizard-dialog.component.scss | 10 ++++++++++ ui-ngx/src/assets/locale/locale.constant-en_US.json | 2 +- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 362b3c14ce..8e91be0cc5 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -82,7 +82,8 @@ [selectFirstProfile]="true" [editProfileEnabled]="false"> - + device-profile.new-device-profile-name @@ -93,12 +94,14 @@
diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss index 55878d638b..b84ff8105f 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.scss @@ -22,6 +22,10 @@ max-height: 75vh; } } + + .invisible{ + visibility: hidden; + } } :host ::ng-deep { @@ -60,6 +64,12 @@ height: 100%; } } + + tb-device-profile-autocomplete, tb-queue-type-list{ + .mat-form-field-wrapper{ + width: 180px !important; + } + } } } } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index e3b7b4b81d..5c044cbff0 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -874,7 +874,7 @@ "profile-configuration": "Profile configuration", "transport-configuration": "Transport configuration", "default-rule-chain": "Default rule chain", - "select-queue-hint": "The queue name can be selected from a drop-down list or add a custom name.", + "select-queue-hint": "Select from a drop-down list or add a custom name.", "delete-device-profile-title": "Are you sure you want to delete the device profile '{{deviceProfileName}}'?", "delete-device-profile-text": "Be careful, after the confirmation the device profile and all related data will become unrecoverable.", "delete-device-profiles-title": "Are you sure you want to delete { count, plural, 1 {1 device profile} other {# device profiles} }?", From f5550f52cfcb7b041b6d5b40561ef2a6508d8758 Mon Sep 17 00:00:00 2001 From: Vladyslav Date: Wed, 4 Nov 2020 23:17:01 +0200 Subject: [PATCH 073/102] Refactoring: add invisible elements --- .../home/components/wizard/device-wizard-dialog.component.html | 1 + 1 file changed, 1 insertion(+) diff --git a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html index 8e91be0cc5..57bf9ed5cb 100644 --- a/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html @@ -76,6 +76,7 @@ [required]="!createProfile" [transportType]="deviceWizardFormGroup.get('transportType').value" formControlName="deviceProfileId" + [ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}" (deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}" [addNewProfile]="false" [selectDefaultProfile]="true" From 39c2e3c39dd11f2f4bd9cc41a0a72cf031f39d22 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Fri, 6 Nov 2020 15:58:00 +0200 Subject: [PATCH 074/102] UI: Fixed updated Alarm widget in change entity state parameters --- .../src/app/core/api/alarm-data-subscription.ts | 4 ++-- ui-ngx/src/app/core/api/alarm-data.service.ts | 17 ++++++++--------- ui-ngx/src/app/core/api/widget-subscription.ts | 4 ++-- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/core/api/alarm-data-subscription.ts b/ui-ngx/src/app/core/api/alarm-data-subscription.ts index 4d3524324e..35a43c3f0f 100644 --- a/ui-ngx/src/app/core/api/alarm-data-subscription.ts +++ b/ui-ngx/src/app/core/api/alarm-data-subscription.ts @@ -52,6 +52,7 @@ export interface AlarmDataSubscriptionOptions { export class AlarmDataSubscription { + private alarmDataSubscriptionOptions = this.listener.alarmDataSubscriptionOptions; private datasourceType: DatasourceType = this.alarmDataSubscriptionOptions.datasourceType; private history: boolean; @@ -65,8 +66,7 @@ export class AlarmDataSubscription { private subsTw: SubscriptionTimewindow; - constructor(public alarmDataSubscriptionOptions: AlarmDataSubscriptionOptions, - private listener: AlarmDataListener, + constructor(private listener: AlarmDataListener, private telemetryService: TelemetryService) { } diff --git a/ui-ngx/src/app/core/api/alarm-data.service.ts b/ui-ngx/src/app/core/api/alarm-data.service.ts index 23488c99c5..3454036db7 100644 --- a/ui-ngx/src/app/core/api/alarm-data.service.ts +++ b/ui-ngx/src/app/core/api/alarm-data.service.ts @@ -32,6 +32,7 @@ export interface AlarmDataListener { alarmSource: Datasource; alarmsLoaded: (pageData: PageData, allowedEntities: number, totalEntities: number) => void; alarmsUpdated: (update: Array, pageData: PageData) => void; + alarmDataSubscriptionOptions?: AlarmDataSubscriptionOptions; subscription?: AlarmDataSubscription; } @@ -47,11 +48,11 @@ export class AlarmDataService { pageLink: AlarmDataPageLink, keyFilters: KeyFilter[]) { const alarmSource = listener.alarmSource; + listener.alarmDataSubscriptionOptions = this.createAlarmSubscriptionOptions(listener, pageLink, keyFilters); if (alarmSource.type === DatasourceType.entity && (!alarmSource.entityFilter || !pageLink)) { return; } - listener.subscription = this.createSubscription(listener, - pageLink, alarmSource.keyFilters, keyFilters); + listener.subscription = new AlarmDataSubscription(listener, this.telemetryService); return listener.subscription.subscribe(); } @@ -61,10 +62,9 @@ export class AlarmDataService { } } - private createSubscription(listener: AlarmDataListener, - pageLink: AlarmDataPageLink, - keyFilters: KeyFilter[], - additionalKeyFilters: KeyFilter[]): AlarmDataSubscription { + private createAlarmSubscriptionOptions(listener: AlarmDataListener, + pageLink: AlarmDataPageLink, + additionalKeyFilters: KeyFilter[]): AlarmDataSubscriptionOptions { const alarmSource = listener.alarmSource; const alarmSubscriptionDataKeys: Array = []; alarmSource.dataKeys.forEach((dataKey) => { @@ -82,11 +82,10 @@ export class AlarmDataService { if (alarmDataSubscriptionOptions.datasourceType === DatasourceType.entity) { alarmDataSubscriptionOptions.entityFilter = alarmSource.entityFilter; alarmDataSubscriptionOptions.pageLink = pageLink; - alarmDataSubscriptionOptions.keyFilters = keyFilters; + alarmDataSubscriptionOptions.keyFilters = alarmSource.keyFilters; alarmDataSubscriptionOptions.additionalKeyFilters = additionalKeyFilters; } - return new AlarmDataSubscription(alarmDataSubscriptionOptions, - listener, this.telemetryService); + return alarmDataSubscriptionOptions; } } diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index f7017652d8..070538ddc5 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -960,8 +960,8 @@ export class WidgetSubscription implements IWidgetSubscription { private updateAlarmDataSubscription() { if (this.alarmDataListener) { - const pageLink = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.pageLink; - const keyFilters = this.alarmDataListener.subscription.alarmDataSubscriptionOptions.additionalKeyFilters; + const pageLink = this.alarmDataListener.alarmDataSubscriptionOptions.pageLink; + const keyFilters = this.alarmDataListener.alarmDataSubscriptionOptions.additionalKeyFilters; this.subscribeForAlarms(pageLink, keyFilters); } } From 5f50fd44d2e6fbb53f4cab1a32bb52fc69748c35 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Mon, 9 Nov 2020 14:06:20 +0200 Subject: [PATCH 075/102] Usage Records implementation --- .../server/controller/TenantController.java | 2 ++ .../apiusage/DefaultTbApiUsageStateService.java | 9 +++++++-- .../thingsboard/server/common/data/TenantProfile.java | 11 +++++++++-- 3 files changed, 18 insertions(+), 4 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TenantController.java b/application/src/main/java/org/thingsboard/server/controller/TenantController.java index 83e3d98b1b..a42cbb0f91 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TenantController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TenantController.java @@ -93,6 +93,8 @@ public class TenantController extends BaseController { } tenantProfileCache.evict(tenant.getId()); tbClusterService.onTenantChange(tenant, null); + tbClusterService.onEntityStateChange(tenant.getId(), tenant.getId(), + newTenant ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return tenant; } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 42f980909a..2c8879ba94 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -243,6 +243,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { TenantProfileData oldProfileData = state.getTenantProfileData(); + state.setTenantProfileId(tenantProfile.getId()); state.setTenantProfileData(tenantProfile.getProfileData()); Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { @@ -257,8 +258,10 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { long ts = System.currentTimeMillis(); List profileThresholds = new ArrayList<>(); for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - if (oldData == null || oldData.getProfileThreshold(key) != newData.getProfileThreshold(key)) { - profileThresholds.add(new BasicTsKvEntry(ts, new LongDataEntry(key.getApiLimitKey(), newData.getProfileThreshold(key)))); + long newProfileThreshold = newData.getProfileThreshold(key); + if (oldData == null || oldData.getProfileThreshold(key) != newProfileThreshold) { + log.info("[{}] Updating profile threshold [{}]:[{}]", tenantId, key, newProfileThreshold); + profileThresholds.add(new BasicTsKvEntry(ts, new LongDataEntry(key.getApiLimitKey(), newProfileThreshold))); } } if (!profileThresholds.isEmpty()) { @@ -267,6 +270,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } private void persistAndNotify(TenantApiUsageState state, Map result) { + log.info("[{}] Detected update of the API state: {}", state.getTenantId(), result); apiUsageStateService.update(state.getApiUsageState()); clusterService.onApiStateChange(state.getApiUsageState(), null); long ts = System.currentTimeMillis(); @@ -320,6 +324,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } } + log.debug("[{}] Initialized state: {}", tenantId, dbStateEntity); myTenantStates.put(tenantId, tenantState); } catch (InterruptedException | ExecutionException e) { log.warn("[{}] Failed to fetch api usage state from db.", tenantId, e); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java index 5e5f4a09de..acb9c239b8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantProfile.java @@ -21,6 +21,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import java.io.ByteArrayInputStream; @@ -79,15 +80,21 @@ public class TenantProfile extends SearchTextBased implements H profileData = mapper.readValue(new ByteArrayInputStream(profileDataBytes), TenantProfileData.class); } catch (IOException e) { log.warn("Can't deserialize tenant profile data: ", e); - return null; + return createDefaultTenantProfileData(); } return profileData; } else { - return null; + return createDefaultTenantProfileData(); } } } + public TenantProfileData createDefaultTenantProfileData() { + TenantProfileData tpd = new TenantProfileData(); + tpd.setConfiguration(new DefaultTenantProfileConfiguration()); + return tpd; + } + public void setProfileData(TenantProfileData data) { this.profileData = data; try { From d40f5823ff1c7d996f7deea208826f17b4284346 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Nov 2020 15:18:37 +0200 Subject: [PATCH 076/102] Add getEntityTimeseries method to Attributes Service --- ui-ngx/src/app/core/http/attribute.service.ts | 30 ++++++++++++++++++- .../models/telemetry/telemetry.models.ts | 14 +++++++++ 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 044b9314ba..775360be5c 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -19,8 +19,9 @@ import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; import { forkJoin, Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { EntityId } from '@shared/models/id/entity-id'; -import { AttributeData, AttributeScope } from '@shared/models/telemetry/telemetry.models'; +import { AttributeData, AttributeScope, DataSortOrder, TimeseriesData } from '@shared/models/telemetry/telemetry.models'; import { isDefinedAndNotNull } from '@core/utils'; +import { AggregationType } from '@shared/models/time/time.models'; @Injectable({ providedIn: 'root' @@ -110,4 +111,31 @@ export class AttributeService { } return forkJoin([saveEntityTimeseriesObservable, deleteEntityTimeseriesObservable]); } + + public getEntityTimeseries(entityId: EntityId, keys: Array, startTs: number, endTs: number, + limit: number = 100, agg: AggregationType = AggregationType.NONE, interval?: number, + orderBy: DataSortOrder = DataOrder.DESC, useStrictDataTypes: boolean = false, + config?: RequestConfig): Observable { + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries`; + url += `?keys=${keys.join(',')}`; + url += `&startTs=${startTs}`; + url += `&endTs=${endTs}`; + if (isDefinedAndNotNull(limit)) { + url += `&limit=${limit}`; + } + if (isDefinedAndNotNull(agg)) { + url += `&agg=${agg}`; + } + if (isDefinedAndNotNull(interval)) { + url += `&interval=${interval}`; + } + if (isDefinedAndNotNull(orderBy)) { + url += `&orderBy=${orderBy}`; + } + if (isDefinedAndNotNull(useStrictDataTypes)) { + url += `&useStrictDataTypes=${useStrictDataTypes}`; + } + + return this.http.get(url, defaultHttpOptionsFromConfig(config)); + } } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 18858bdbf7..28a887fe9b 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -81,6 +81,20 @@ export interface AttributeData { value: any; } +export interface TimeseriesData { + [key: string]: Array; +} + +export interface TimeseriesKeyData { + value: any; + ts: number; +} + +export enum DataSortOrder { + ASC = 'ASC', + DESC = 'DESC' +} + export interface WebsocketCmd { cmdId: number; } From 9f5d3a6c9533b146ca6fe3f0a455f531ff671a5e Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Nov 2020 15:32:19 +0200 Subject: [PATCH 077/102] Type correction --- ui-ngx/src/app/core/http/attribute.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 775360be5c..1fce00290d 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -114,7 +114,7 @@ export class AttributeService { public getEntityTimeseries(entityId: EntityId, keys: Array, startTs: number, endTs: number, limit: number = 100, agg: AggregationType = AggregationType.NONE, interval?: number, - orderBy: DataSortOrder = DataOrder.DESC, useStrictDataTypes: boolean = false, + orderBy: DataSortOrder = DataSortOrder.DESC, useStrictDataTypes: boolean = false, config?: RequestConfig): Observable { let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries`; url += `?keys=${keys.join(',')}`; From ae8686605f4dfe6d8a34dd877be124dbb7213021 Mon Sep 17 00:00:00 2001 From: Chantsova Ekaterina Date: Mon, 9 Nov 2020 15:54:05 +0200 Subject: [PATCH 078/102] Refactoring --- ui-ngx/src/app/core/http/attribute.service.ts | 7 ++----- .../shared/models/telemetry/telemetry.models.ts | 16 +++++++++------- 2 files changed, 11 insertions(+), 12 deletions(-) diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index 1fce00290d..c9e4246d86 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -116,10 +116,7 @@ export class AttributeService { limit: number = 100, agg: AggregationType = AggregationType.NONE, interval?: number, orderBy: DataSortOrder = DataSortOrder.DESC, useStrictDataTypes: boolean = false, config?: RequestConfig): Observable { - let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries`; - url += `?keys=${keys.join(',')}`; - url += `&startTs=${startTs}`; - url += `&endTs=${endTs}`; + let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/values/timeseries?keys=${keys.join(',')}&startTs=${startTs}&endTs=${endTs}`; if (isDefinedAndNotNull(limit)) { url += `&limit=${limit}`; } @@ -136,6 +133,6 @@ export class AttributeService { url += `&useStrictDataTypes=${useStrictDataTypes}`; } - return this.http.get(url, defaultHttpOptionsFromConfig(config)); + return this.http.get(url, defaultHttpOptionsFromConfig(config)); } } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 28a887fe9b..aa4fb51fd1 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -21,7 +21,14 @@ import { Observable, ReplaySubject, Subject } from 'rxjs'; import { EntityId } from '@shared/models/id/entity-id'; import { map } from 'rxjs/operators'; import { NgZone } from '@angular/core'; -import { AlarmData, AlarmDataQuery, EntityData, EntityDataQuery, EntityKey } from '@shared/models/query/query.models'; +import { + AlarmData, + AlarmDataQuery, + EntityData, + EntityDataQuery, + EntityKey, + TsValue +} from '@shared/models/query/query.models'; import { PageData } from '@shared/models/page/page-data'; export enum DataKeyType { @@ -82,12 +89,7 @@ export interface AttributeData { } export interface TimeseriesData { - [key: string]: Array; -} - -export interface TimeseriesKeyData { - value: any; - ts: number; + [key: string]: Array; } export enum DataSortOrder { From 6d90dc8420b20de82a47cbe77a7ecddf8e11cd0c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Mon, 9 Nov 2020 18:09:24 +0200 Subject: [PATCH 079/102] AccessValidator improvements --- .../server/service/security/AccessValidator.java | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java index b4bc79e06e..8c7c629414 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java +++ b/application/src/main/java/org/thingsboard/server/service/security/AccessValidator.java @@ -53,6 +53,7 @@ import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; @@ -152,7 +153,11 @@ public class AccessValidator { new FutureCallback>() { @Override public void onSuccess(@Nullable DeferredResult result) { - onSuccess.accept(response, currentUser.getTenantId(), entityId); + try { + onSuccess.accept(response, currentUser.getTenantId(), entityId); + } catch (Exception e) { + onFailure(e); + } } @Override @@ -404,9 +409,9 @@ public class AccessValidator { public static void handleError(Throwable e, final DeferredResult response, HttpStatus defaultErrorStatus) { ResponseEntity responseEntity; - if (e != null && e instanceof ToErrorResponseEntity) { + if (e instanceof ToErrorResponseEntity) { responseEntity = ((ToErrorResponseEntity) e).toErrorResponseEntity(); - } else if (e != null && e instanceof IllegalArgumentException) { + } else if (e instanceof IllegalArgumentException || e instanceof IncorrectParameterException) { responseEntity = new ResponseEntity<>(e.getMessage(), HttpStatus.BAD_REQUEST); } else { responseEntity = new ResponseEntity<>(defaultErrorStatus); From f32aed7d5812005275710451a97ad1c0e7e53015 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 10 Nov 2020 12:06:16 +0200 Subject: [PATCH 080/102] UI: Refactoring, rename locale file --- .../profile/tenant-profile.component.ts | 4 +- ...enant-profile-configuration.component.html | 244 +++++++++--------- ...-tenant-profile-configuration.component.ts | 51 ++-- ...-pl_BR.json => locale.constant-pt_BR.json} | 0 4 files changed, 145 insertions(+), 154 deletions(-) rename ui-ngx/src/assets/locale/{locale.constant-pl_BR.json => locale.constant-pt_BR.json} (100%) diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts index 7cca3ae414..4e960e18a3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.ts @@ -23,7 +23,7 @@ import { TenantProfile, TenantProfileData, TenantProfileType -} from '@app/shared/models/tenant.model'; +} from '@shared/models/tenant.model'; import { ActionNotificationShow } from '@app/core/notification/notification.actions'; import { TranslateService } from '@ngx-translate/core'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; @@ -73,7 +73,7 @@ export class TenantProfileComponent extends EntityComponent { this.entityForm.patchValue({name: entity.name}); this.entityForm.patchValue({isolatedTbCore: entity.isolatedTbCore}); this.entityForm.patchValue({isolatedTbRuleEngine: entity.isolatedTbRuleEngine}); - this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData: { + this.entityForm.patchValue({profileData: !this.isAdd ? entity.profileData : { configuration: createTenantProfileConfiguration(TenantProfileType.DEFAULT) } as TenantProfileData}); this.entityForm.patchValue({description: entity.description}); diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index cdb4febcc0..12258ffd5f 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -15,127 +15,125 @@ limitations under the License. --> -
-
- - tenant-profile.maximum-devices - - - {{ 'tenant-profile.maximum-devices-required' | translate}} - - - {{ 'tenant-profile.maximum-devices-range' | translate}} - - - - tenant-profile.maximum-assets - - - {{ 'tenant-profile.maximum-assets-required' | translate}} - - - {{ 'tenant-profile.maximum-assets-range' | translate}} - - - - tenant-profile.max-transport-messages - - - {{ 'tenant-profile.max-transport-messages-range' | translate}} - - - {{ 'tenant-profile.max-transport-messages-required' | translate}} - - - - tenant-profile.max-transport-data-points - - - {{ 'tenant-profile.max-transport-data-points-required' | translate}} - - - {{ 'tenant-profile.max-transport-data-points-range' | translate}} - - - - tenant-profile.max-r-e-executions - - - {{ 'tenant-profile.max-r-e-executions-required' | translate}} - - - {{ 'tenant-profile.max-r-e-executions-range' | translate}} - - - - tenant-profile.max-j-s-executions - - - {{ 'tenant-profile.max-j-s-executions-required' | translate}} - - - {{ 'tenant-profile.max-j-s-executions-range' | translate}} - - - - tenant-profile.max-d-p-storage-days - - - {{ 'tenant-profile.max-d-p-storage-days-required' | translate}} - - - {{ 'tenant-profile.max-d-p-storage-days-range' | translate}} - - - - tenant-profile.max-rule-node-executions-per-message - - - {{ 'tenant-profile.max-rule-node-executions-per-message-required' | translate}} - - - {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} - - - - tenant-profile.transport-tenant-msg-rate-limit - - - - tenant-profile.transport-tenant-telemetry-msg-rate-limit - - - - tenant-profile.transport-tenant-telemetry-data-points-rate-limit - - - - tenant-profile.transport-device-msg-rate-limit - - - - tenant-profile.transport-device-telemetry-msg-rate-limit - - - - tenant-profile.transport-device-telemetry-data-points-rate-limit - - -
+
+ + tenant-profile.maximum-devices + + + {{ 'tenant-profile.maximum-devices-required' | translate}} + + + {{ 'tenant-profile.maximum-devices-range' | translate}} + + + + tenant-profile.maximum-assets + + + {{ 'tenant-profile.maximum-assets-required' | translate}} + + + {{ 'tenant-profile.maximum-assets-range' | translate}} + + + + tenant-profile.max-transport-messages + + + {{ 'tenant-profile.max-transport-messages-range' | translate}} + + + {{ 'tenant-profile.max-transport-messages-required' | translate}} + + + + tenant-profile.max-transport-data-points + + + {{ 'tenant-profile.max-transport-data-points-required' | translate}} + + + {{ 'tenant-profile.max-transport-data-points-range' | translate}} + + + + tenant-profile.max-r-e-executions + + + {{ 'tenant-profile.max-r-e-executions-required' | translate}} + + + {{ 'tenant-profile.max-r-e-executions-range' | translate}} + + + + tenant-profile.max-j-s-executions + + + {{ 'tenant-profile.max-j-s-executions-required' | translate}} + + + {{ 'tenant-profile.max-j-s-executions-range' | translate}} + + + + tenant-profile.max-d-p-storage-days + + + {{ 'tenant-profile.max-d-p-storage-days-required' | translate}} + + + {{ 'tenant-profile.max-d-p-storage-days-range' | translate}} + + + + tenant-profile.max-rule-node-executions-per-message + + + {{ 'tenant-profile.max-rule-node-executions-per-message-required' | translate}} + + + {{ 'tenant-profile.max-rule-node-executions-per-message-range' | translate}} + + + + tenant-profile.transport-tenant-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-msg-rate-limit + + + + tenant-profile.transport-tenant-telemetry-data-points-rate-limit + + + + tenant-profile.transport-device-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-msg-rate-limit + + + + tenant-profile.transport-device-telemetry-data-points-rate-limit + +
diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts index f83b59c35a..1b54126fd5 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.ts @@ -19,11 +19,7 @@ import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR, Valida import { Store } from '@ngrx/store'; import { AppState } from '@app/core/core.state'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; -import { - DefaultTenantProfileConfiguration, - TenantProfileConfiguration, - TenantProfileType -} from '@shared/models/tenant.model'; +import { DefaultTenantProfileConfiguration, TenantProfileConfiguration } from '@shared/models/tenant.model'; import { isDefinedAndNotNull } from '@core/utils'; @Component({ @@ -56,6 +52,25 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA constructor(private store: Store, private fb: FormBuilder) { + this.defaultTenantProfileConfigurationFormGroup = this.fb.group({ + maxDevices: [null, [Validators.required, Validators.min(0)]], + maxAssets: [null, [Validators.required, Validators.min(0)]], + transportTenantMsgRateLimit: [null, []], + transportTenantTelemetryMsgRateLimit: [null, []], + transportTenantTelemetryDataPointsRateLimit: [null, []], + transportDeviceMsgRateLimit: [null, []], + transportDeviceTelemetryMsgRateLimit: [null, []], + transportDeviceTelemetryDataPointsRateLimit: [null, []], + maxTransportMessages: [null, [Validators.required, Validators.min(0)]], + maxTransportDataPoints: [null, [Validators.required, Validators.min(0)]], + maxREExecutions: [null, [Validators.required, Validators.min(0)]], + maxJSExecutions: [null, [Validators.required, Validators.min(0)]], + maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], + maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] + }); + this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { + this.updateModel(); + }); } registerOnChange(fn: any): void { @@ -66,27 +81,6 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA } ngOnInit() { - this.defaultTenantProfileConfigurationFormGroup = this.fb.group({ - configuration: this.fb.group({ - maxDevices: [null, [Validators.required, Validators.min(0)]], - maxAssets: [null, [Validators.required, Validators.min(0)]], - transportTenantMsgRateLimit: [null, []], - transportTenantTelemetryMsgRateLimit: [null, []], - transportTenantTelemetryDataPointsRateLimit: [null, []], - transportDeviceMsgRateLimit: [null, []], - transportDeviceTelemetryMsgRateLimit: [null, []], - transportDeviceTelemetryDataPointsRateLimit: [null, []], - maxTransportMessages: [null, [Validators.required, Validators.min(0)]], - maxTransportDataPoints: [null, [Validators.required, Validators.min(0)]], - maxREExecutions: [null, [Validators.required, Validators.min(0)]], - maxJSExecutions: [null, [Validators.required, Validators.min(0)]], - maxDPStorageDays: [null, [Validators.required, Validators.min(0)]], - maxRuleNodeExecutionsPerMessage: [null, [Validators.required, Validators.min(0)]] - }) - }); - this.defaultTenantProfileConfigurationFormGroup.valueChanges.subscribe(() => { - this.updateModel(); - }); } setDisabledState(isDisabled: boolean): void { @@ -100,15 +94,14 @@ export class DefaultTenantProfileConfigurationComponent implements ControlValueA writeValue(value: DefaultTenantProfileConfiguration | null): void { if (isDefinedAndNotNull(value)) { - this.defaultTenantProfileConfigurationFormGroup.patchValue({configuration: value}, {emitEvent: false}); + this.defaultTenantProfileConfigurationFormGroup.patchValue(value, {emitEvent: false}); } } private updateModel() { let configuration: TenantProfileConfiguration = null; if (this.defaultTenantProfileConfigurationFormGroup.valid) { - configuration = this.defaultTenantProfileConfigurationFormGroup.getRawValue().configuration; - configuration.type = TenantProfileType.DEFAULT; + configuration = this.defaultTenantProfileConfigurationFormGroup.getRawValue(); } this.propagateChange(configuration); } diff --git a/ui-ngx/src/assets/locale/locale.constant-pl_BR.json b/ui-ngx/src/assets/locale/locale.constant-pt_BR.json similarity index 100% rename from ui-ngx/src/assets/locale/locale.constant-pl_BR.json rename to ui-ngx/src/assets/locale/locale.constant-pt_BR.json From ac866faab08151301363d29ae3751cef6baf9b48 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 10 Nov 2020 16:19:44 +0200 Subject: [PATCH 081/102] Improvements to the ApiUsageState --- .../DefaultTbApiUsageStateService.java | 26 ++-- .../service/apiusage/TenantApiUsageState.java | 135 ++++++++---------- .../install/SqlDatabaseUpgradeService.java | 24 ++++ .../server/common/data}/ApiFeature.java | 8 +- .../server/common/data/ApiUsageRecordKey.java | 34 ++++- .../server/common/data/ApiUsageState.java | 32 +++-- .../common/data/ApiUsageStateValue.java | 26 ++++ .../DefaultTenantProfileConfiguration.java | 6 + .../profile/TenantProfileConfiguration.java | 3 + .../queue/settings/TbQueueCoreSettings.java | 2 +- .../server/dao/model/ModelConstants.java | 8 +- .../dao/model/sql/ApiUsageStateEntity.java | 44 +++--- .../usagerecord/ApiUsageStateServiceImpl.java | 5 + .../resources/sql/schema-entities-hsql.sql | 8 +- .../main/resources/sql/schema-entities.sql | 8 +- .../service/BaseApiUsageStateServiceTest.java | 3 +- .../src/main/resources/tb-coap-transport.yml | 1 + .../src/main/resources/tb-http-transport.yml | 1 + .../src/main/resources/tb-mqtt-transport.yml | 1 + 19 files changed, 239 insertions(+), 136 deletions(-) rename {application/src/main/java/org/thingsboard/server/service/apiusage => common/data/src/main/java/org/thingsboard/server/common/data}/ApiFeature.java (83%) create mode 100644 common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 2c8879ba94..c7fda26dac 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -23,16 +23,18 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Lazy; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; -import org.thingsboard.server.common.data.kv.BooleanDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; @@ -57,8 +59,10 @@ import org.thingsboard.server.service.telemetry.InternalTelemetryService; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Set; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutionException; @@ -137,7 +141,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantId tenantId = new TenantId(new UUID(statsMsg.getTenantIdMSB(), statsMsg.getTenantIdLSB())); TenantApiUsageState tenantState; List updatedEntries; - Map result = new HashMap<>(); + Map result; updateLock.lock(); try { tenantState = getOrFetchState(tenantId); @@ -148,17 +152,16 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { tenantState.setHour(newHourTs); } updatedEntries = new ArrayList<>(ApiUsageRecordKey.values().length); + Set apiFeatures = new HashSet<>(); for (UsageStatsKVProto kvProto : statsMsg.getValuesList()) { ApiUsageRecordKey recordKey = ApiUsageRecordKey.valueOf(kvProto.getKey()); long newValue = tenantState.add(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(ts, new LongDataEntry(recordKey.getApiCountKey(), newValue))); long newHourlyValue = tenantState.addToHourly(recordKey, kvProto.getValue()); updatedEntries.add(new BasicTsKvEntry(hourTs, new LongDataEntry(recordKey.getApiCountKey() + HOURLY, newHourlyValue))); - Pair update = tenantState.checkStateUpdatedDueToThreshold(recordKey); - if (update != null) { - result.put(update.getFirst(), update.getSecond()); - } + apiFeatures.add(recordKey.getApiFeature()); } + result = tenantState.checkStateUpdatedDueToThreshold(apiFeatures); } finally { updateLock.unlock(); } @@ -196,7 +199,9 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { state = otherTenantStates.get(tenantId); if (state == null) { state = apiUsageStateService.findTenantApiUsageState(tenantId); - otherTenantStates.put(tenantId, state); + if (state != null) { + otherTenantStates.put(tenantId, state); + } } } finally { updateLock.unlock(); @@ -245,7 +250,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { TenantProfileData oldProfileData = state.getTenantProfileData(); state.setTenantProfileId(tenantProfile.getId()); state.setTenantProfileData(tenantProfile.getProfileData()); - Map result = state.checkStateUpdatedDueToThresholds(); + Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { persistAndNotify(state, result); } @@ -269,14 +274,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } - private void persistAndNotify(TenantApiUsageState state, Map result) { + private void persistAndNotify(TenantApiUsageState state, Map result) { log.info("[{}] Detected update of the API state: {}", state.getTenantId(), result); apiUsageStateService.update(state.getApiUsageState()); clusterService.onApiStateChange(state.getApiUsageState(), null); long ts = System.currentTimeMillis(); List stateTelemetry = new ArrayList<>(); - result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new BooleanDataEntry(apiFeature.getApiStateKey(), aState))))); + result.forEach(((apiFeature, aState) -> stateTelemetry.add(new BasicTsKvEntry(ts, new StringDataEntry(apiFeature.getApiStateKey(), aState.name()))))); tsWsService.saveAndNotifyInternal(state.getTenantId(), state.getApiUsageState().getId(), stateTelemetry, VOID_CALLBACK); + //TODO: notify tenant admin via email! } private void checkStartOfNextCycle() { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java index 8fcc1d46cb..a43afcd2c5 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java @@ -18,17 +18,21 @@ package org.thingsboard.server.service.apiusage; import lombok.Getter; import lombok.Setter; import org.springframework.data.util.Pair; +import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.msg.tools.SchedulerUtils; +import java.util.Arrays; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; public class TenantApiUsageState { @@ -103,99 +107,80 @@ public class TenantApiUsageState { return tenantProfileData.getConfiguration().getProfileThreshold(key); } - public TenantId getTenantId() { - return apiUsageState.getTenantId(); - } - - public boolean isTransportEnabled() { - return apiUsageState.isTransportEnabled(); - } - - public boolean isDbStorageEnabled() { - return apiUsageState.isDbStorageEnabled(); - } - - public boolean isRuleEngineEnabled() { - return apiUsageState.isReExecEnabled(); - } - - public boolean isJsExecEnabled() { - return apiUsageState.isJsExecEnabled(); - } - - public void setTransportEnabled(boolean transportEnabled) { - apiUsageState.setTransportEnabled(transportEnabled); + public long getProfileWarnThreshold(ApiUsageRecordKey key) { + return tenantProfileData.getConfiguration().getWarnThreshold(key); } - public void setDbStorageEnabled(boolean dbStorageEnabled) { - apiUsageState.setDbStorageEnabled(dbStorageEnabled); - } - - public void setRuleEngineEnabled(boolean ruleEngineEnabled) { - apiUsageState.setReExecEnabled(ruleEngineEnabled); - } - - public void setJsExecEnabled(boolean jsExecEnabled) { - apiUsageState.setJsExecEnabled(jsExecEnabled); + public TenantId getTenantId() { + return apiUsageState.getTenantId(); } - public boolean isFeatureEnabled(ApiUsageRecordKey recordKey) { - switch (recordKey) { - case TRANSPORT_MSG_COUNT: - case TRANSPORT_DP_COUNT: - return isTransportEnabled(); - case RE_EXEC_COUNT: - return isRuleEngineEnabled(); - case STORAGE_DP_COUNT: - return isDbStorageEnabled(); - case JS_EXEC_COUNT: - return isJsExecEnabled(); + public ApiUsageStateValue getFeatureValue(ApiFeature feature) { + switch (feature) { + case TRANSPORT: + return apiUsageState.getTransportState(); + case RE: + return apiUsageState.getReExecState(); + case DB: + return apiUsageState.getDbStorageState(); + case JS: + return apiUsageState.getJsExecState(); default: - return true; + return ApiUsageStateValue.ENABLED; } } - public ApiFeature setFeatureValue(ApiUsageRecordKey recordKey, boolean value) { - ApiFeature feature = null; - boolean currentValue = isFeatureEnabled(recordKey); - switch (recordKey) { - case TRANSPORT_MSG_COUNT: - case TRANSPORT_DP_COUNT: - feature = ApiFeature.TRANSPORT; - setTransportEnabled(value); + public boolean setFeatureValue(ApiFeature feature, ApiUsageStateValue value) { + ApiUsageStateValue currentValue = getFeatureValue(feature); + switch (feature) { + case TRANSPORT: + apiUsageState.setTransportState(value); break; - case RE_EXEC_COUNT: - feature = ApiFeature.RE; - setRuleEngineEnabled(value); + case RE: + apiUsageState.setReExecState(value); break; - case STORAGE_DP_COUNT: - feature = ApiFeature.DB; - setDbStorageEnabled(value); + case DB: + apiUsageState.setDbStorageState(value); break; - case JS_EXEC_COUNT: - feature = ApiFeature.JS; - setJsExecEnabled(value); + case JS: + apiUsageState.setJsExecState(value); break; } - return currentValue == value ? null : feature; + return !currentValue.equals(value); } - public Map checkStateUpdatedDueToThresholds() { - Map result = new HashMap<>(); - for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { - Pair featureUpdate = checkStateUpdatedDueToThreshold(key); - if (featureUpdate != null) { - result.put(featureUpdate.getFirst(), featureUpdate.getSecond()); + public Map checkStateUpdatedDueToThresholds() { + return checkStateUpdatedDueToThreshold(new HashSet<>(Arrays.asList(ApiFeature.values()))); + } + + public Map checkStateUpdatedDueToThreshold(Set features) { + Map result = new HashMap<>(); + for (ApiFeature feature : features) { + Pair tmp = checkStateUpdatedDueToThreshold(feature); + if (tmp != null) { + result.put(tmp.getFirst(), tmp.getSecond()); } } return result; } - public Pair checkStateUpdatedDueToThreshold(ApiUsageRecordKey recordKey) { - long value = get(recordKey); - long threshold = getProfileThreshold(recordKey); - boolean featureValue = threshold == 0 || value < threshold; - ApiFeature feature = setFeatureValue(recordKey, featureValue); - return feature != null ? Pair.of(feature, featureValue) : null; + public Pair checkStateUpdatedDueToThreshold(ApiFeature feature) { + ApiUsageStateValue featureValue = ApiUsageStateValue.ENABLED; + for (ApiUsageRecordKey recordKey : ApiUsageRecordKey.getKeys(feature)) { + long value = get(recordKey); + long threshold = getProfileThreshold(recordKey); + long warnThreshold = getProfileWarnThreshold(recordKey); + ApiUsageStateValue tmpValue; + if (threshold == 0 || value < warnThreshold) { + tmpValue = ApiUsageStateValue.ENABLED; + } else if (value < threshold) { + tmpValue = ApiUsageStateValue.WARNING; + } else { + tmpValue = ApiUsageStateValue.DISABLED; + } + featureValue = ApiUsageStateValue.toMoreRestricted(featureValue, tmpValue); + } + return setFeatureValue(feature, featureValue) ? Pair.of(feature, featureValue) : null; } + } diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index d6068b9616..19727684a3 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -28,6 +28,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.tenant.TenantService; +import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.service.install.sql.SqlDbHelper; import java.nio.charset.Charset; @@ -96,6 +97,9 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @Autowired private DeviceProfileService deviceProfileService; + @Autowired + private ApiUsageStateService apiUsageStateService; + @Override public void upgradeDatabase(String fromVersion) throws Exception { @@ -352,6 +356,22 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService } catch (Exception e) { } + try { + conn.createStatement().execute("CREATE TABLE IF NOT EXISTS api_usage_state (" + + " id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY," + + " created_time bigint NOT NULL," + + " tenant_id uuid," + + " entity_type varchar(32)," + + " entity_id uuid," + + " transport varchar(32)," + + " db_storage varchar(32)," + + " re_exec varchar(32)," + + " js_exec varchar(32)," + + " CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)\n" + + ");"); + } catch (Exception e) { + } + schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.1.1", "schema_update_before.sql"); loadSql(schemaUpdateFile, conn); @@ -367,6 +387,10 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService do { pageData = tenantService.findTenants(pageLink); for (Tenant tenant : pageData.getData()) { + try { + apiUsageStateService.createDefaultApiUsageState(tenant.getId()); + } catch (Exception e) { + } List deviceTypes = deviceService.findDeviceTypesByTenantId(tenant.getId()).get(); try { deviceProfileService.createDefaultDeviceProfile(tenant.getId()); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java similarity index 83% rename from application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java rename to common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java index f2dfab54d4..e12cb5fb14 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/ApiFeature.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiFeature.java @@ -13,12 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.apiusage; +package org.thingsboard.server.common.data; import lombok.Getter; public enum ApiFeature { - TRANSPORT("transportApiState"), DB("dbApiState"), RE("ruleEngineApiState"), JS("jsExecutionApiState"); + TRANSPORT("transportApiState"), + DB("dbApiState"), + RE("ruleEngineApiState"), + JS("jsExecutionApiState"); @Getter private final String apiStateKey; @@ -26,4 +29,5 @@ public enum ApiFeature { ApiFeature(String apiStateKey) { this.apiStateKey = apiStateKey; } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java index b1ff230e71..dc8556a755 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageRecordKey.java @@ -19,20 +19,42 @@ import lombok.Getter; public enum ApiUsageRecordKey { - TRANSPORT_MSG_COUNT("transportMsgCount", "transportMsgLimit"), - TRANSPORT_DP_COUNT("transportDataPointsCount", "transportDataPointsLimit"), - STORAGE_DP_COUNT("storageDataPointsCount", "storageDataPointsLimit"), - RE_EXEC_COUNT("ruleEngineExecutionCount", "ruleEngineExecutionLimit"), - JS_EXEC_COUNT("jsExecutionCount", "jsExecutionLimit"); + TRANSPORT_MSG_COUNT(ApiFeature.TRANSPORT, "transportMsgCount", "transportMsgLimit"), + TRANSPORT_DP_COUNT(ApiFeature.TRANSPORT, "transportDataPointsCount", "transportDataPointsLimit"), + STORAGE_DP_COUNT(ApiFeature.DB, "storageDataPointsCount", "storageDataPointsLimit"), + RE_EXEC_COUNT(ApiFeature.RE, "ruleEngineExecutionCount", "ruleEngineExecutionLimit"), + JS_EXEC_COUNT(ApiFeature.JS, "jsExecutionCount", "jsExecutionLimit"); + private static final ApiUsageRecordKey[] JS_RECORD_KEYS = {JS_EXEC_COUNT}; + private static final ApiUsageRecordKey[] RE_RECORD_KEYS = {RE_EXEC_COUNT}; + private static final ApiUsageRecordKey[] DB_RECORD_KEYS = {STORAGE_DP_COUNT}; + private static final ApiUsageRecordKey[] TRANSPORT_RECORD_KEYS = {TRANSPORT_MSG_COUNT, TRANSPORT_DP_COUNT}; + @Getter + private final ApiFeature apiFeature; @Getter private final String apiCountKey; @Getter private final String apiLimitKey; - ApiUsageRecordKey(String apiCountKey, String apiLimitKey) { + ApiUsageRecordKey(ApiFeature apiFeature, String apiCountKey, String apiLimitKey) { + this.apiFeature = apiFeature; this.apiCountKey = apiCountKey; this.apiLimitKey = apiLimitKey; } + public static ApiUsageRecordKey[] getKeys(ApiFeature feature) { + switch (feature) { + case TRANSPORT: + return TRANSPORT_RECORD_KEYS; + case DB: + return DB_RECORD_KEYS; + case RE: + return RE_RECORD_KEYS; + case JS: + return JS_RECORD_KEYS; + default: + return new ApiUsageRecordKey[]{}; + } + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java index 5144861790..52797baa3b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageState.java @@ -37,16 +37,16 @@ public class ApiUsageState extends BaseData implements HasTenan private EntityId entityId; @Getter @Setter - private boolean transportEnabled = true; + private ApiUsageStateValue transportState; @Getter @Setter - private boolean dbStorageEnabled = true; + private ApiUsageStateValue dbStorageState; @Getter @Setter - private boolean reExecEnabled = true; + private ApiUsageStateValue reExecState; @Getter @Setter - private boolean jsExecEnabled = true; + private ApiUsageStateValue jsExecState; public ApiUsageState() { super(); @@ -60,9 +60,25 @@ public class ApiUsageState extends BaseData implements HasTenan super(ur); this.tenantId = ur.getTenantId(); this.entityId = ur.getEntityId(); - this.transportEnabled = ur.isTransportEnabled(); - this.dbStorageEnabled = ur.isDbStorageEnabled(); - this.reExecEnabled = ur.isReExecEnabled(); - this.jsExecEnabled = ur.isJsExecEnabled(); + this.transportState = ur.getTransportState(); + this.dbStorageState = ur.getDbStorageState(); + this.reExecState = ur.getReExecState(); + this.jsExecState = ur.getJsExecState(); + } + + public boolean isTransportEnabled() { + return !ApiUsageStateValue.DISABLED.equals(transportState); + } + + public boolean isReExecEnabled() { + return !ApiUsageStateValue.DISABLED.equals(reExecState); + } + + public boolean isDbStorageEnabled() { + return !ApiUsageStateValue.DISABLED.equals(dbStorageState); + } + + public boolean isJsExecEnabled() { + return !ApiUsageStateValue.DISABLED.equals(jsExecState); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java new file mode 100644 index 0000000000..ae14ff80bf --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/ApiUsageStateValue.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2020 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +public enum ApiUsageStateValue { + + ENABLED, WARNING, DISABLED; + + + public static ApiUsageStateValue toMoreRestricted(ApiUsageStateValue a, ApiUsageStateValue b) { + return a.ordinal() > b.ordinal() ? a : b; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java index 1804731c04..250f9c75a5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/DefaultTenantProfileConfiguration.java @@ -39,6 +39,8 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura private long maxDPStorageDays; private int maxRuleNodeExecutionsPerMessage; + private double warnThreshold; + @Override public long getProfileThreshold(ApiUsageRecordKey key) { switch (key) { @@ -56,6 +58,10 @@ public class DefaultTenantProfileConfiguration implements TenantProfileConfigura return 0L; } + @Override + public long getWarnThreshold(ApiUsageRecordKey key) { + return (long) (getProfileThreshold(key) * (warnThreshold > 0.0 ? warnThreshold : 0.8)); + } @Override public TenantProfileType getType() { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java index c4c6d7563a..ea4a0e1fbe 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/tenant/profile/TenantProfileConfiguration.java @@ -37,6 +37,9 @@ public interface TenantProfileConfiguration { @JsonIgnore long getProfileThreshold(ApiUsageRecordKey key); + @JsonIgnore + long getWarnThreshold(ApiUsageRecordKey key); + @JsonIgnore int getMaxRuleNodeExecsPerMessage(); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java index d406139dbb..d78c6d1bad 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueCoreSettings.java @@ -26,7 +26,7 @@ public class TbQueueCoreSettings { @Value("${queue.core.topic}") private String topic; - @Value("${queue.core.usage-stats-topic}") + @Value("${queue.core.usage-stats-topic:tb_usage_stats}") private String usageStatsTopic; @Value("${queue.core.partitions}") diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java index 8e1e62b90f..e863de1edc 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java @@ -446,10 +446,10 @@ public class ModelConstants { public static final String API_USAGE_STATE_TENANT_ID_COLUMN = TENANT_ID_PROPERTY; public static final String API_USAGE_STATE_ENTITY_TYPE_COLUMN = ENTITY_TYPE_COLUMN; public static final String API_USAGE_STATE_ENTITY_ID_COLUMN = ENTITY_ID_COLUMN; - public static final String API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN = "transport_enabled"; - public static final String API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN = "db_storage_enabled"; - public static final String API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN = "re_exec_enabled"; - public static final String API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN = "js_exec_enabled"; + public static final String API_USAGE_STATE_TRANSPORT_COLUMN = "transport"; + public static final String API_USAGE_STATE_DB_STORAGE_COLUMN = "db_storage"; + public static final String API_USAGE_STATE_RE_EXEC_COLUMN = "re_exec"; + public static final String API_USAGE_STATE_JS_EXEC_COLUMN = "js_exec"; /** * Cassandra attributes and timeseries constants. diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java index 262252517a..bc4aed6398 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/ApiUsageStateEntity.java @@ -17,10 +17,9 @@ package org.thingsboard.server.dao.model.sql; import lombok.Data; import lombok.EqualsAndHashCode; -import lombok.Getter; -import lombok.Setter; import org.hibernate.annotations.TypeDef; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.ApiUsageStateId; @@ -31,6 +30,8 @@ import org.thingsboard.server.dao.util.mapping.JsonStringType; import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.EnumType; +import javax.persistence.Enumerated; import javax.persistence.Table; import java.util.UUID; @@ -46,21 +47,22 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements @Column(name = ModelConstants.API_USAGE_STATE_TENANT_ID_COLUMN) private UUID tenantId; - @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_TYPE_COLUMN) private String entityType; - @Column(name = ModelConstants.API_USAGE_STATE_ENTITY_ID_COLUMN) private UUID entityId; - - @Column(name = ModelConstants.API_USAGE_STATE_TRANSPORT_ENABLED_COLUMN) - private boolean transportEnabled = true; - @Column(name = ModelConstants.API_USAGE_STATE_DB_STORAGE_ENABLED_COLUMN) - private boolean dbStorageEnabled = true; - @Column(name = ModelConstants.API_USAGE_STATE_RE_EXEC_ENABLED_COLUMN) - private boolean reExecEnabled = true; - @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_ENABLED_COLUMN) - private boolean jsExecEnabled = true; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_TRANSPORT_COLUMN) + private ApiUsageStateValue transportState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_DB_STORAGE_COLUMN) + private ApiUsageStateValue dbStorageState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_RE_EXEC_COLUMN) + private ApiUsageStateValue reExecState = ApiUsageStateValue.ENABLED; + @Enumerated(EnumType.STRING) + @Column(name = ModelConstants.API_USAGE_STATE_JS_EXEC_COLUMN) + private ApiUsageStateValue jsExecState = ApiUsageStateValue.ENABLED; public ApiUsageStateEntity() { } @@ -77,10 +79,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements this.entityType = ur.getEntityId().getEntityType().name(); this.entityId = ur.getEntityId().getId(); } - this.transportEnabled = ur.isTransportEnabled(); - this.dbStorageEnabled = ur.isDbStorageEnabled(); - this.reExecEnabled = ur.isReExecEnabled(); - this.jsExecEnabled = ur.isJsExecEnabled(); + this.transportState = ur.getTransportState(); + this.dbStorageState = ur.getDbStorageState(); + this.reExecState = ur.getReExecState(); + this.jsExecState = ur.getJsExecState(); } @Override @@ -93,10 +95,10 @@ public class ApiUsageStateEntity extends BaseSqlEntity implements if (entityId != null) { ur.setEntityId(EntityIdFactory.getByTypeAndUuid(entityType, entityId)); } - ur.setTransportEnabled(transportEnabled); - ur.setDbStorageEnabled(dbStorageEnabled); - ur.setReExecEnabled(reExecEnabled); - ur.setJsExecEnabled(jsExecEnabled); + ur.setTransportState(transportState); + ur.setDbStorageState(dbStorageState); + ur.setReExecState(reExecState); + ur.setJsExecState(jsExecState); return ur; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index e1b494e2e8..ed02bf7726 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; @@ -71,6 +72,10 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A ApiUsageState apiUsageState = new ApiUsageState(); apiUsageState.setTenantId(tenantId); apiUsageState.setEntityId(tenantId); + apiUsageState.setTransportState(ApiUsageStateValue.ENABLED); + apiUsageState.setReExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setJsExecState(ApiUsageStateValue.ENABLED); + apiUsageState.setDbStorageState(ApiUsageStateValue.ENABLED); apiUsageStateValidator.validate(apiUsageState, ApiUsageState::getTenantId); ApiUsageState saved = apiUsageStateDao.save(apiUsageState.getTenantId(), apiUsageState); diff --git a/dao/src/main/resources/sql/schema-entities-hsql.sql b/dao/src/main/resources/sql/schema-entities-hsql.sql index b7c028d5d7..0e8f8a7791 100644 --- a/dao/src/main/resources/sql/schema-entities-hsql.sql +++ b/dao/src/main/resources/sql/schema-entities-hsql.sql @@ -412,9 +412,9 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, - transport_enabled boolean, - db_storage_enabled boolean, - re_exec_enabled boolean, - js_exec_enabled boolean, + transport varchar(32), + db_storage varchar(32), + re_exec varchar(32), + js_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 6fffea8fe9..db0362a877 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -438,10 +438,10 @@ CREATE TABLE IF NOT EXISTS api_usage_state ( tenant_id uuid, entity_type varchar(32), entity_id uuid, - transport_enabled boolean, - db_storage_enabled boolean, - re_exec_enabled boolean, - js_exec_enabled boolean, + transport varchar(32), + db_storage varchar(32), + re_exec varchar(32), + js_exec varchar(32), CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id) ); diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java index a33e190a03..024d3dd5c3 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseApiUsageStateServiceTest.java @@ -19,6 +19,7 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.ApiUsageStateValue; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; @@ -53,7 +54,7 @@ public abstract class BaseApiUsageStateServiceTest extends AbstractServiceTest { ApiUsageState apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); Assert.assertNotNull(apiUsageState); Assert.assertTrue(apiUsageState.isTransportEnabled()); - apiUsageState.setTransportEnabled(false); + apiUsageState.setTransportState(ApiUsageStateValue.DISABLED); apiUsageState = apiUsageStateService.update(apiUsageState); Assert.assertNotNull(apiUsageState); apiUsageState = apiUsageStateService.findTenantApiUsageState(tenantId); diff --git a/transport/coap/src/main/resources/tb-coap-transport.yml b/transport/coap/src/main/resources/tb-coap-transport.yml index 229196d325..3a1d0e7414 100644 --- a/transport/coap/src/main/resources/tb-coap-transport.yml +++ b/transport/coap/src/main/resources/tb-coap-transport.yml @@ -145,6 +145,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" diff --git a/transport/http/src/main/resources/tb-http-transport.yml b/transport/http/src/main/resources/tb-http-transport.yml index 6aaa42ca18..ef152bb47d 100644 --- a/transport/http/src/main/resources/tb-http-transport.yml +++ b/transport/http/src/main/resources/tb-http-transport.yml @@ -138,6 +138,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" diff --git a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml index f9341dfb86..fc8d970638 100644 --- a/transport/mqtt/src/main/resources/tb-mqtt-transport.yml +++ b/transport/mqtt/src/main/resources/tb-mqtt-transport.yml @@ -167,6 +167,7 @@ queue: poll-interval: "${TB_QUEUE_CORE_POLL_INTERVAL_MS:25}" partitions: "${TB_QUEUE_CORE_PARTITIONS:10}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" + usage-stats-topic: "${TB_QUEUE_US_TOPIC:tb_usage_stats}" stats: enabled: "${TB_QUEUE_CORE_STATS_ENABLED:false}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" From b1c4332a0376e1da9d02ef2223b77dac64375c58 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 10 Nov 2020 17:08:16 +0200 Subject: [PATCH 082/102] UI: Improvement logo container size --- ui-ngx/src/app/modules/home/home.component.scss | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ui-ngx/src/app/modules/home/home.component.scss b/ui-ngx/src/app/modules/home/home.component.scss index f77e85fd87..6cb0adea27 100644 --- a/ui-ngx/src/app/modules/home/home.component.scss +++ b/ui-ngx/src/app/modules/home/home.component.scss @@ -37,6 +37,7 @@ .tb-nav-header-toolbar { min-height: 64px; height: inherit; + padding: 0; z-index: 2; flex-shrink: 0; white-space: nowrap; @@ -45,7 +46,9 @@ height: 64px; .tb-logo-title { width: auto; + max-width: 100%; height: 36px; + max-height: 100%; margin: auto; } } From c3623be872fac607b13f33860b1637f67c7ea0c7 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Tue, 10 Nov 2020 18:37:55 +0200 Subject: [PATCH 083/102] Fix for black box tests --- .../server/service/apiusage/DefaultTbApiUsageStateService.java | 1 - 1 file changed, 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index c7fda26dac..b016cced95 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -71,7 +71,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @Slf4j -@TbCoreComponent @Service public class DefaultTbApiUsageStateService implements TbApiUsageStateService { From a535b320f13f7010328b45fb59893c0d7a22e04f Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 09:18:48 +0200 Subject: [PATCH 084/102] Change ordering of the startup sequence --- .../server/actors/service/DefaultActorService.java | 2 ++ .../apiusage/DefaultTbApiUsageStateService.java | 10 ++++++++-- .../service/queue/DefaultTbCoreConsumerService.java | 2 ++ .../queue/processing/AbstractConsumerService.java | 2 ++ .../service/transport/TbCoreTransportApiService.java | 2 ++ .../server/queue/discovery/DummyDiscoveryService.java | 2 ++ .../server/queue/discovery/ZkDiscoveryService.java | 2 ++ 7 files changed, 20 insertions(+), 2 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java index 2d3775c9f5..7be4dcb8be 100644 --- a/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java +++ b/application/src/main/java/org/thingsboard/server/actors/service/DefaultActorService.java @@ -20,6 +20,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.actors.ActorSystemContext; @@ -113,6 +114,7 @@ public class DefaultActorService implements ActorService { } @EventListener(ApplicationReadyEvent.class) + @Order(value = 2) public void onApplicationEvent(ApplicationReadyEvent applicationReadyEvent) { log.info("Received application ready event. Sending application init message to actor system"); appActor.tellWithHighPriority(new AppInitMsg()); diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index b016cced95..0dcef2c88d 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -20,7 +20,10 @@ import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.annotation.Lazy; +import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; import org.springframework.data.util.Pair; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.ApiFeature; @@ -129,8 +132,8 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { public void init() { if (enabled) { log.info("Starting api usage service."); - initStatesFromDataBase(); scheduler.scheduleAtFixedRate(this::checkStartOfNextCycle, nextCycleCheckInterval, nextCycleCheckInterval, TimeUnit.MILLISECONDS); + log.info("Started api usage service."); } } @@ -340,12 +343,15 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { private void initStatesFromDataBase() { try { + log.info("Initializing tenant states."); PageDataIterable tenantIterator = new PageDataIterable<>(tenantService::findTenants, 1024); for (Tenant tenant : tenantIterator) { if (!myTenantStates.containsKey(tenant.getId()) && partitionService.resolve(ServiceType.TB_CORE, tenant.getId(), tenant.getId()).isMyPartition()) { + log.debug("[{}] Initializing tenant state.", tenant.getId()); updateLock.lock(); try { updateTenantState(getOrFetchState(tenant.getId()), tenantProfileCache.get(tenant.getTenantProfileId())); + log.debug("[{}] Initialized tenant state.", tenant.getId()); } catch (Exception e) { log.warn("[{}] Failed to initialize tenant API state", tenant.getId(), e); } finally { @@ -353,7 +359,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } } - log.info("Api usage service started."); + log.info("Initialized tenant states."); } catch (Exception e) { log.warn("Unknown failure", e); } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index aaafa52c35..ac7aea9f44 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -19,6 +19,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.event.ApplicationReadyEvent; import org.springframework.context.event.EventListener; +import org.springframework.core.annotation.Order; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; @@ -140,6 +141,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService Date: Mon, 9 Nov 2020 17:49:05 +0200 Subject: [PATCH 085/102] added validation with maxDevices and maxAssets to deviceService and assetService --- .../server/actors/ActorSystemContext.java | 2 +- .../thingsboard/server/actors/app/AppActor.java | 3 +-- .../server/controller/BaseController.java | 3 +-- .../apiusage/DefaultTbApiUsageStateService.java | 2 +- .../queue/DefaultTbCoreConsumerService.java | 2 +- .../DefaultTbRuleEngineConsumerService.java | 2 +- .../queue/DefaultTenantRoutingInfoService.java | 3 +-- .../processing/AbstractConsumerService.java | 2 +- .../transport/DefaultTransportApiService.java | 2 +- .../server/dao/tenant}/TbTenantProfileCache.java | 2 +- .../thingsboard/server/dao/asset/AssetDao.java | 2 ++ .../server/dao/asset/BaseAssetService.java | 16 ++++++++++++++++ .../thingsboard/server/dao/device/DeviceDao.java | 2 ++ .../server/dao/device/DeviceServiceImpl.java | 16 ++++++++++++++++ .../server/dao/sql/asset/AssetRepository.java | 1 + .../server/dao/sql/asset/JpaAssetDao.java | 6 ++++++ .../server/dao/sql/device/DeviceRepository.java | 2 ++ .../server/dao/sql/device/JpaDeviceDao.java | 5 +++++ .../dao/tenant}/DefaultTbTenantProfileCache.java | 9 ++------- 19 files changed, 62 insertions(+), 20 deletions(-) rename {application/src/main/java/org/thingsboard/server/service/profile => common/dao-api/src/main/java/org/thingsboard/server/dao/tenant}/TbTenantProfileCache.java (95%) rename {application/src/main/java/org/thingsboard/server/service/profile => dao/src/main/java/org/thingsboard/server/dao/tenant}/DefaultTbTenantProfileCache.java (90%) diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index 5a0e291ac2..52dbb38c9c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -73,7 +73,7 @@ import org.thingsboard.server.service.executors.ExternalCallExecutorService; import org.thingsboard.server.service.executors.SharedEventLoopGroupService; import org.thingsboard.server.service.mail.MailExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index fea5558540..ba8654854c 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java @@ -30,7 +30,6 @@ import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.data.page.PageDataIterable; import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; import org.thingsboard.server.common.msg.MsgType; @@ -41,7 +40,7 @@ import org.thingsboard.server.common.msg.queue.QueueToRuleEngineMsg; import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.dao.tenant.TenantService; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.transport.msg.TransportToDeviceActorMsgWrapper; import java.util.HashSet; diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index b8fe956217..869cbea127 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -35,7 +35,6 @@ import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; -import org.thingsboard.server.common.data.id.*; import org.thingsboard.server.common.data.id.AlarmId; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -94,7 +93,7 @@ import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.permission.AccessControlService; diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 0dcef2c88d..79f05fa897 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -55,7 +55,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.scheduler.SchedulerComponent; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.telemetry.InternalTelemetryService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index ac7aea9f44..f56e08b10f 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -55,7 +55,7 @@ import org.thingsboard.server.queue.provider.TbCoreQueueFactory; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java index ffb431ca37..aa3be47ef9 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java @@ -45,7 +45,7 @@ import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; import org.thingsboard.server.queue.util.TbRuleEngineComponent; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingDecision; import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingResult; diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java index 4d5219fbfe..2bcf7e7e88 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTenantRoutingInfoService.java @@ -21,11 +21,10 @@ import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.queue.discovery.TenantRoutingInfo; import org.thingsboard.server.queue.discovery.TenantRoutingInfoService; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; @Slf4j @Service diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index fd95f66346..0dfc9c5742 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -38,7 +38,7 @@ import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.common.transport.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbPackCallback; import org.thingsboard.server.service.queue.TbPackProcessingContext; diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index ab566fb147..3e9e413ceb 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -71,7 +71,7 @@ import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.profile.TbDeviceProfileCache; -import org.thingsboard.server.service.profile.TbTenantProfileCache; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.service.queue.TbClusterService; import org.thingsboard.server.service.state.DeviceStateService; diff --git a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java rename to common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java index de69b5f51c..735e23e73a 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/TbTenantProfileCache.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/tenant/TbTenantProfileCache.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.profile; +package org.thingsboard.server.dao.tenant; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.TenantId; diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 00154ec602..1366df3ac2 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -166,4 +166,6 @@ public interface AssetDao extends Dao { */ ListenableFuture> findTenantAssetTypesAsync(UUID tenantId); + Long countAssetsByTenantId(TenantId tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java index f0683ae5ae..923baa1df4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java @@ -26,6 +26,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.Customer; @@ -44,12 +45,14 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import java.util.ArrayList; @@ -90,6 +93,10 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @Autowired private CacheManager cacheManager; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public AssetInfo findAssetInfoById(TenantId tenantId, AssetId assetId) { log.trace("Executing findAssetInfoById [{}]", assetId); @@ -320,6 +327,15 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ @Override protected void validateCreate(TenantId tenantId, Asset asset) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxAssets = profileConfiguration.getMaxAssets(); + if (maxAssets > 0) { + long currentAssetsCount = assetDao.countAssetsByTenantId(tenantId); + if (maxAssets >= currentAssetsCount) { + throw new DataValidationException("Can't create assets more then " + maxAssets); + } + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java index 54ca245ee8..2f321ff499 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java @@ -203,6 +203,8 @@ public interface DeviceDao extends Dao { */ ListenableFuture findDeviceByTenantIdAndIdAsync(TenantId tenantId, UUID id); + Long countDevicesByTenantId(TenantId tenantId); + Long countDevicesByDeviceProfileId(TenantId tenantId, UUID deviceProfileId); /** diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index b0290e8eb1..9d3e161eb4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -27,6 +27,7 @@ import org.springframework.cache.Cache; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.CacheEvict; import org.springframework.cache.annotation.Cacheable; +import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; @@ -57,6 +58,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.dao.customer.CustomerDao; import org.thingsboard.server.dao.device.provision.ProvisionFailedException; import org.thingsboard.server.dao.device.provision.ProvisionRequest; @@ -67,6 +69,7 @@ import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.DataValidator; import org.thingsboard.server.dao.service.PaginatedRemover; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantDao; import org.thingsboard.server.dao.util.mapping.JacksonUtil; @@ -120,6 +123,10 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Autowired private EventService eventService; + @Autowired + @Lazy + private TbTenantProfileCache tenantProfileCache; + @Override public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { log.trace("Executing findDeviceInfoById [{}]", deviceId); @@ -520,6 +527,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe @Override protected void validateCreate(TenantId tenantId, Device device) { + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration)tenantProfileCache.get(tenantId).getProfileData().getConfiguration(); + long maxDevices = profileConfiguration.getMaxDevices(); + if (maxDevices > 0) { + long currentDevicesCount = deviceDao.countDevicesByTenantId(tenantId); + if (maxDevices >= currentDevicesCount) { + throw new DataValidationException("Can't create devices more then " + maxDevices); + } + } } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index 5e9d942720..a6e016c418 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -122,4 +122,5 @@ public interface AssetRepository extends PagingAndSortingRepository findTenantAssetTypes(@Param("tenantId") UUID tenantId); + Long countByTenantId(UUID tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index 1e67802873..698183ea0a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -176,4 +176,10 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im } return list; } + + @Override + public Long countAssetsByTenantId(TenantId tenantId) { + return assetRepository.countByTenantId(tenantId.getId()); + + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java index b6ead2cdc3..9f3cc5b5a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/device/DeviceRepository.java @@ -168,4 +168,6 @@ public interface DeviceRepository extends PagingAndSortingRepository return deviceRepository.countByDeviceProfileId(deviceProfileId); } + @Override + public Long countDevicesByTenantId(TenantId tenantId) { + return deviceRepository.countByTenantId(tenantId.getId()); + } + private List convertTenantDeviceTypesToDto(UUID tenantId, List types) { List list = Collections.emptyList(); if (types != null && !types.isEmpty()) { diff --git a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java similarity index 90% rename from application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java rename to dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java index 42c09c104c..adf05e0f74 100644 --- a/application/src/main/java/org/thingsboard/server/service/profile/DefaultTbTenantProfileCache.java +++ b/dao/src/main/java/org/thingsboard/server/dao/tenant/DefaultTbTenantProfileCache.java @@ -13,20 +13,15 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.profile; +package org.thingsboard.server.dao.tenant; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; -import org.thingsboard.server.dao.device.DeviceProfileService; -import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; From 92ba5907b0e8fb335e6562d636a3bcb627099f92 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Tue, 3 Nov 2020 16:51:41 +0200 Subject: [PATCH 086/102] Added validation for deviceName/attribute key contains only spaces situation --- .../thingsboard/server/controller/TelemetryController.java | 5 +++++ .../org/thingsboard/server/dao/device/DeviceServiceImpl.java | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index af4eb0f08f..e8c4756a6f 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -393,6 +393,11 @@ public class TelemetryController extends BaseController { if (attributes.isEmpty()) { return getImmediateDeferredResult("No attributes data found in request body!", HttpStatus.BAD_REQUEST); } + for (AttributeKvEntry attributeKvEntry: attributes) { + if (attributeKvEntry.getKey().isEmpty() || attributeKvEntry.getKey().trim().length() == 0) { + return getImmediateDeferredResult("Key cannot be empty or contains only spaces", HttpStatus.BAD_REQUEST); + } + } SecurityUser user = getCurrentUser(); return accessValidator.validateEntityAndCallback(getCurrentUser(), Operation.WRITE_ATTRIBUTES, entityIdSrc, (result, tenantId, entityId) -> { tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java index cb6d74cf10..8d29b9c447 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java @@ -388,7 +388,7 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe if (StringUtils.isEmpty(device.getType())) { throw new DataValidationException("Device type should be specified!"); } - if (StringUtils.isEmpty(device.getName())) { + if (StringUtils.isEmpty(device.getName()) || device.getName().trim().length() == 0) { throw new DataValidationException("Device name should be specified!"); } if (device.getTenantId() == null) { From 1daf5a44b5683a35b065f568e28498e414f95111 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Tue, 3 Nov 2020 17:33:57 +0200 Subject: [PATCH 087/102] alarm state improvements (added details to alarm from device profile details pattern) --- .../rule/engine/profile/AlarmState.java | 53 ++++++++++++++++--- 1 file changed, 47 insertions(+), 6 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 6c771f0c86..3d29da6bd7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -16,6 +16,7 @@ package org.thingsboard.rule.engine.profile; import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.thingsboard.rule.engine.action.TbAlarmResult; @@ -29,6 +30,7 @@ import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.KeyFilter; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.ServiceQueue; @@ -74,7 +76,7 @@ class AlarmState { public boolean createOrClearAlarms(TbContext ctx, T data, SnapshotUpdate update, BiFunction evalFunction) { boolean stateUpdate = false; - AlarmSeverity resultSeverity = null; + AlarmRuleState resultState = null; log.debug("[{}] processing update: {}", alarmDefinition.getId(), data); for (AlarmRuleState state : createRulesSortedBySeverityDesc) { if (!validateUpdate(update, state)) { @@ -84,15 +86,15 @@ class AlarmState { AlarmEvalResult evalResult = evalFunction.apply(state, data); stateUpdate |= state.checkUpdate(); if (AlarmEvalResult.TRUE.equals(evalResult)) { - resultSeverity = state.getSeverity(); + resultState = state; break; } else if (AlarmEvalResult.FALSE.equals(evalResult)) { state.clear(); stateUpdate |= state.checkUpdate(); } } - if (resultSeverity != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultSeverity); + if (resultState != null) { + TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); if (result != null) { pushMsg(ctx, result); } @@ -187,7 +189,8 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmSeverity severity) { + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { + AlarmSeverity severity = ruleState.getSeverity(); if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). // Maybe we should fetch alarm every time? @@ -213,7 +216,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.createObjectNode()); + currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -226,6 +229,44 @@ class AlarmState { } } + private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { + ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); + + if (alarmDetails != null) { + for (KeyFilter keyFilter : ruleState.getAlarmRule().getCondition().getCondition()) { + EntityKeyValue entityKeyValue = dataSnapshot.getValue(keyFilter.getKey()); + alarmDetails = alarmDetails.replaceAll(String.format("\\$\\{%s}", keyFilter.getKey().getKey()), getValueAsString(entityKeyValue)); + } + + details.put("data", alarmDetails); + } + + return details; + } + + private static String getValueAsString(EntityKeyValue entityKeyValue) { + Object result = null; + switch (entityKeyValue.getDataType()) { + case STRING: + result = entityKeyValue.getStrValue(); + break; + case JSON: + result = entityKeyValue.getJsonValue(); + break; + case LONG: + result = String.valueOf(entityKeyValue.getLngValue()); + break; + case DOUBLE: + result = String.valueOf(entityKeyValue.getDblValue()); + break; + case BOOLEAN: + result = String.valueOf(entityKeyValue.getBoolValue()); + break; + } + return String.valueOf(result); + } + public boolean processAlarmClear(TbContext ctx, Alarm alarmNf) { boolean updated = false; if (currentAlarm != null && currentAlarm.getId().equals(alarmNf.getId())) { From 55b8ae92ea298a5eb2bddfe7c6b0d83a67a063de Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Nov 2020 13:40:38 +0200 Subject: [PATCH 088/102] refactored --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 3d29da6bd7..8fe7bcb777 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -255,13 +255,13 @@ class AlarmState { result = entityKeyValue.getJsonValue(); break; case LONG: - result = String.valueOf(entityKeyValue.getLngValue()); + result = entityKeyValue.getLngValue(); break; case DOUBLE: - result = String.valueOf(entityKeyValue.getDblValue()); + result = entityKeyValue.getDblValue(); break; case BOOLEAN: - result = String.valueOf(entityKeyValue.getBoolValue()); + result = entityKeyValue.getBoolValue(); break; } return String.valueOf(result); From a2eea121bedf483e5a0a0db3e16fbb1891e96bcd Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 4 Nov 2020 18:14:18 +0200 Subject: [PATCH 089/102] AlarmState improvements --- .../org/thingsboard/rule/engine/profile/AlarmState.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java index 8fe7bcb777..c59dbb75ca 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java @@ -55,6 +55,7 @@ class AlarmState { private volatile boolean initialFetchDone; private volatile TbMsgMetaData lastMsgMetaData; private volatile String lastMsgQueueName; + private volatile DataSnapshot dataSnapshot; AlarmState(ProfileState deviceProfile, EntityId originator, DeviceProfileAlarm alarmDefinition, PersistedAlarmState alarmState) { this.deviceProfile = deviceProfile; @@ -94,7 +95,7 @@ class AlarmState { } } if (resultState != null) { - TbAlarmResult result = calculateAlarmResult(ctx, resultState, data); + TbAlarmResult result = calculateAlarmResult(ctx, resultState); if (result != null) { pushMsg(ctx, result); } @@ -189,7 +190,7 @@ class AlarmState { } } - private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState, T data) { + private TbAlarmResult calculateAlarmResult(TbContext ctx, AlarmRuleState ruleState) { AlarmSeverity severity = ruleState.getSeverity(); if (currentAlarm != null) { // TODO: In some extremely rare cases, we might miss the event of alarm clear (If one use in-mem queue and restarted the server) or (if one manipulated the rule chain). @@ -216,7 +217,7 @@ class AlarmState { currentAlarm.setSeverity(severity); currentAlarm.setStartTs(System.currentTimeMillis()); currentAlarm.setEndTs(currentAlarm.getStartTs()); - currentAlarm.setDetails(createDetails(ruleState, (DataSnapshot) data)); + currentAlarm.setDetails(createDetails(ruleState)); currentAlarm.setOriginator(originator); currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setPropagate(alarmDefinition.isPropagate()); @@ -229,7 +230,7 @@ class AlarmState { } } - private JsonNode createDetails(AlarmRuleState ruleState, DataSnapshot dataSnapshot) { + private JsonNode createDetails(AlarmRuleState ruleState) { ObjectNode details = JacksonUtil.OBJECT_MAPPER.createObjectNode(); String alarmDetails = ruleState.getAlarmRule().getAlarmDetails(); From 580c8aef51a355254ba14eff88974fee83e12f13 Mon Sep 17 00:00:00 2001 From: zbeacon Date: Fri, 30 Oct 2020 14:28:30 +0200 Subject: [PATCH 090/102] Added handler for too long payload exception in MQTT transport --- .../server/transport/mqtt/MqttTransportHandler.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 5d24f812eb..abb8f629f1 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -115,7 +115,13 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement log.trace("[{}] Processing msg: {}", sessionId, msg); try { if (msg instanceof MqttMessage) { - processMqttMsg(ctx, (MqttMessage) msg); + MqttMessage message = (MqttMessage) msg; + if (message.decoderResult().isSuccess()) { + processMqttMsg(ctx, message); + } else { + log.error("[{}] Message processing failed: {}", sessionId, message.decoderResult().cause().getMessage()); + ctx.close(); + } } else { ctx.close(); } From f6e9959ca5e215a61c5b8029c203dd166e1d5c9e Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 12:22:00 +0200 Subject: [PATCH 091/102] Minor improvements --- .../DefaultSystemDataLoaderService.java | 3 --- .../usagestats/DefaultTbApiUsageClient.java | 4 +++- .../transport/mqtt/MqttTransportHandler.java | 6 +++-- .../DefaultTransportRateLimitService.java | 24 ++++++++++++++----- 4 files changed, 25 insertions(+), 12 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index fd757ed6dd..6c7678f023 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -134,7 +134,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { TenantProfile isolatedTbCoreProfile = new TenantProfile(); isolatedTbCoreProfile.setDefault(false); isolatedTbCoreProfile.setName("Isolated TB Core"); - isolatedTbCoreProfile.setProfileData(new TenantProfileData()); isolatedTbCoreProfile.setDescription("Isolated TB Core tenant profile"); isolatedTbCoreProfile.setIsolatedTbCore(true); isolatedTbCoreProfile.setIsolatedTbRuleEngine(false); @@ -148,7 +147,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { TenantProfile isolatedTbRuleEngineProfile = new TenantProfile(); isolatedTbRuleEngineProfile.setDefault(false); isolatedTbRuleEngineProfile.setName("Isolated TB Rule Engine"); - isolatedTbRuleEngineProfile.setProfileData(new TenantProfileData()); isolatedTbRuleEngineProfile.setDescription("Isolated TB Rule Engine tenant profile"); isolatedTbRuleEngineProfile.setIsolatedTbCore(false); isolatedTbRuleEngineProfile.setIsolatedTbRuleEngine(true); @@ -163,7 +161,6 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { TenantProfile isolatedTbCoreAndTbRuleEngineProfile = new TenantProfile(); isolatedTbCoreAndTbRuleEngineProfile.setDefault(false); isolatedTbCoreAndTbRuleEngineProfile.setName("Isolated TB Core and TB Rule Engine"); - isolatedTbCoreAndTbRuleEngineProfile.setProfileData(new TenantProfileData()); isolatedTbCoreAndTbRuleEngineProfile.setDescription("Isolated TB Core and TB Rule Engine tenant profile"); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbCore(true); isolatedTbCoreAndTbRuleEngineProfile.setIsolatedTbRuleEngine(true); diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java index 0702322838..5c5b593d96 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java @@ -94,7 +94,9 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, tenantId).newByTopic(msgProducer.getDefaultTopic()); msgProducer.send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), builder.build()), null); })); - log.info("Report statistics for: {} tenants", report.size()); + if (!report.isEmpty()) { + log.info("Report statistics for: {} tenants", report.size()); + } } catch (Exception e) { log.warn("Failed to report statistics: ", e); } diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index 14712a7cb3..024e42f9ac 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -464,8 +464,10 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement String userName = msg.payload().userName(); log.info("[{}] Processing connect msg for client with user name: {}!", sessionId, userName); TransportProtos.ValidateBasicMqttCredRequestMsg.Builder request = TransportProtos.ValidateBasicMqttCredRequestMsg.newBuilder() - .setClientId(msg.payload().clientIdentifier()) - .setUserName(userName); + .setClientId(msg.payload().clientIdentifier()); + if (userName != null) { + request.setUserName(userName); + } String password = msg.payload().password(); if (password != null) { request.setPassword(password); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java index b6f160ad1e..6dc1db0fc2 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/limits/DefaultTransportRateLimitService.java @@ -21,6 +21,7 @@ import org.springframework.util.StringUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.TenantProfile; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; @@ -77,6 +78,7 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi @Override public void update(TenantProfileUpdateResult update) { + log.info("Received tenant profile update: {}", update.getProfile()); EntityTransportRateLimits tenantRateLimitPrototype = createRateLimits(update.getProfile(), true); EntityTransportRateLimits deviceRateLimitPrototype = createRateLimits(update.getProfile(), false); for (TenantId tenantId : update.getAffectedTenants()) { @@ -114,16 +116,26 @@ public class DefaultTransportRateLimitService implements TransportRateLimitServi tenantAllowed.put(tenantId, allowed); } - private void mergeLimits(T deviceId, EntityTransportRateLimits newRateLimits, - Function getFunction, - BiConsumer putFunction) { - EntityTransportRateLimits oldRateLimits = getFunction.apply(deviceId); + private void mergeLimits(T entityId, EntityTransportRateLimits newRateLimits, + Function getFunction, + BiConsumer putFunction) { + EntityTransportRateLimits oldRateLimits = getFunction.apply(entityId); if (oldRateLimits == null) { - putFunction.accept(deviceId, newRateLimits); + if (EntityType.TENANT.equals(entityId.getEntityType())) { + log.info("[{}] New rate limits: {}", entityId, newRateLimits); + } else { + log.debug("[{}] New rate limits: {}", entityId, newRateLimits); + } + putFunction.accept(entityId, newRateLimits); } else { EntityTransportRateLimits updated = merge(oldRateLimits, newRateLimits); if (updated != null) { - putFunction.accept(deviceId, updated); + if (EntityType.TENANT.equals(entityId.getEntityType())) { + log.info("[{}] Updated rate limits: {}", entityId, updated); + } else { + log.debug("[{}] Updated rate limits: {}", entityId, updated); + } + putFunction.accept(entityId, updated); } } } From f157d07c1f4e1550155159a383bc934964ba6f05 Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 12:45:14 +0200 Subject: [PATCH 092/102] Improvements --- .../apiusage/DefaultTbApiUsageStateService.java | 11 +++++++---- .../server/service/queue/DefaultTbClusterService.java | 1 + application/src/main/resources/thingsboard.yml | 1 - 3 files changed, 8 insertions(+), 5 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index 0dcef2c88d..d7b96c78be 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -221,6 +221,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantProfileUpdate(TenantProfileId tenantProfileId) { + log.info("[{}] On Tenant Profile Update", tenantProfileId); TenantProfile tenantProfile = tenantProfileCache.get(tenantProfileId); updateLock.lock(); try { @@ -236,6 +237,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantUpdate(TenantId tenantId) { + log.info("[{}] On Tenant Update", tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId); updateLock.lock(); try { @@ -248,16 +250,17 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } } - private void updateTenantState(TenantApiUsageState state, TenantProfile tenantProfile) { + private void updateTenantState(TenantApiUsageState state, TenantProfile profile) { + log.info("[{}] On Tenant Update.", state.getTenantId()); TenantProfileData oldProfileData = state.getTenantProfileData(); - state.setTenantProfileId(tenantProfile.getId()); - state.setTenantProfileData(tenantProfile.getProfileData()); + state.setTenantProfileId(profile.getId()); + state.setTenantProfileData(profile.getProfileData()); Map result = state.checkStateUpdatedDueToThresholds(); if (!result.isEmpty()) { persistAndNotify(state, result); } updateProfileThresholds(state.getTenantId(), state.getApiUsageState().getId(), - oldProfileData.getConfiguration(), tenantProfile.getProfileData().getConfiguration()); + oldProfileData.getConfiguration(), profile.getProfileData().getConfiguration()); } private void updateProfileThresholds(TenantId tenantId, ApiUsageStateId id, diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 18f064b745..edd53c6727 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -274,6 +274,7 @@ public class DefaultTbClusterService implements TbClusterService { TbQueueProducer> toRuleEngineProducer = producerProvider.getRuleEngineNotificationsMsgProducer(); Set tbRuleEngineServices = new HashSet<>(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)); if (msg.getEntityId().getEntityType().equals(EntityType.TENANT) + || msg.getEntityId().getEntityType().equals(EntityType.TENANT_PROFILE) || msg.getEntityId().getEntityType().equals(EntityType.DEVICE_PROFILE) || msg.getEntityId().getEntityType().equals(EntityType.API_USAGE_STATE)) { TbQueueProducer> toCoreNfProducer = producerProvider.getTbCoreNotificationsMsgProducer(); diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index f0f4b9dd54..2766793acc 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -766,7 +766,6 @@ metrics: # Metrics percentiles returned by actuator for timer metrics. List of double values (divided by ,). percentiles: "${METRICS_TIMER_PERCENTILES:0.5}" - management: endpoints: web: From ebf10b55c8cc1f3287ffb23baab3a255160a2f03 Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 11 Nov 2020 12:59:22 +0200 Subject: [PATCH 093/102] api usage state alias improvements --- .../dao/sql/query/DefaultEntityQueryRepository.java | 2 +- .../server/dao/sql/query/EntityKeyMapping.java | 3 ++- .../dao/usagerecord/ApiUsageStateServiceImpl.java | 13 +++++++++++++ ui-ngx/src/app/shared/models/entity-type.models.ts | 9 ++++++++- ui-ngx/src/assets/locale/locale.constant-en_US.json | 3 ++- 5 files changed, 26 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java index 380e166aa5..db61e6ab05 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultEntityQueryRepository.java @@ -210,7 +210,7 @@ public class DefaultEntityQueryRepository implements EntityQueryRepository { entityTableMap.put(EntityType.CUSTOMER, "customer"); entityTableMap.put(EntityType.USER, "tb_user"); entityTableMap.put(EntityType.TENANT, "tenant"); - entityTableMap.put(EntityType.API_USAGE_STATE, "(select aus.id, aus.created_time, aus.tenant_id, '' as name, '' as additional_info from api_usage_state as aus)"); + entityTableMap.put(EntityType.API_USAGE_STATE, "api_usage_state"); } public static EntityType[] RELATION_QUERY_ENTITY_TYPES = new EntityType[]{ diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 525483f0a0..8d316aebcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -79,7 +79,7 @@ public class EntityKeyMapping { public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); - + public static final List apiUsageStateEntityFields = Collections.singletonList(CREATED_TIME); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); @@ -99,6 +99,7 @@ public class EntityKeyMapping { allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); + allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, new HashSet<>(apiUsageStateEntityFields)); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index ed02bf7726..b525a71eaa 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.usagerecord; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; +import org.thingsboard.server.common.data.ApiFeature; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.ApiUsageStateValue; @@ -27,6 +28,7 @@ import org.thingsboard.server.common.data.id.ApiUsageStateId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; import org.thingsboard.server.dao.entity.AbstractEntityService; @@ -84,6 +86,17 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); List profileThresholds = new ArrayList<>(); + List apiUsageStates = new ArrayList<>(); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.DB.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); + tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); } diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index c6a94ad8b5..e53c5fb10e 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -47,7 +47,8 @@ export enum EntityType { RULE_NODE = 'RULE_NODE', ENTITY_VIEW = 'ENTITY_VIEW', WIDGETS_BUNDLE = 'WIDGETS_BUNDLE', - WIDGET_TYPE = 'WIDGET_TYPE' + WIDGET_TYPE = 'WIDGET_TYPE', + API_USAGE_STATE = 'API_USAGE_STATE' } export enum AliasEntityType { @@ -238,6 +239,12 @@ export const entityTypeTranslations = new Map Date: Wed, 11 Nov 2020 13:03:22 +0200 Subject: [PATCH 094/102] refactored --- .../thingsboard/server/dao/sql/query/EntityKeyMapping.java | 5 +++-- .../server/dao/usagerecord/ApiUsageStateServiceImpl.java | 3 ++- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java index 8d316aebcd..7332de4b49 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/EntityKeyMapping.java @@ -79,7 +79,8 @@ public class EntityKeyMapping { public static final List dashboardEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, TITLE); public static final List labeledEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, ADDITIONAL_INFO); public static final List contactBasedEntityFields = Arrays.asList(CREATED_TIME, ENTITY_TYPE, EMAIL, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO); - public static final List apiUsageStateEntityFields = Collections.singletonList(CREATED_TIME); + + public static final Set apiUsageStateEntityFields = Collections.singleton(CREATED_TIME); public static final Set commonEntityFieldsSet = new HashSet<>(commonEntityFields); public static final Set relationQueryEntityFieldsSet = new HashSet<>(Arrays.asList(CREATED_TIME, ENTITY_TYPE, NAME, TYPE, LABEL, FIRST_NAME, LAST_NAME, EMAIL, REGION, TITLE, COUNTRY, STATE, CITY, ADDRESS, ADDRESS_2, ZIP, PHONE, ADDITIONAL_INFO)); @@ -99,7 +100,7 @@ public class EntityKeyMapping { allowedEntityFieldMap.put(EntityType.RULE_NODE, new HashSet<>(commonEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGET_TYPE, new HashSet<>(widgetEntityFields)); allowedEntityFieldMap.put(EntityType.WIDGETS_BUNDLE, new HashSet<>(widgetEntityFields)); - allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, new HashSet<>(apiUsageStateEntityFields)); + allowedEntityFieldMap.put(EntityType.API_USAGE_STATE, apiUsageStateEntityFields); entityFieldColumnMap.put(CREATED_TIME, ModelConstants.CREATED_TIME_PROPERTY); entityFieldColumnMap.put(ENTITY_TYPE, ModelConstants.ENTITY_TYPE_PROPERTY); diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index b525a71eaa..12b5b3c01a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -85,7 +85,6 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A Tenant tenant = tenantDao.findById(tenantId, tenantId.getId()); TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); - List profileThresholds = new ArrayList<>(); List apiUsageStates = new ArrayList<>(); apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); @@ -97,6 +96,8 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); + List profileThresholds = new ArrayList<>(); + for (ApiUsageRecordKey key : ApiUsageRecordKey.values()) { profileThresholds.add(new BasicTsKvEntry(saved.getCreatedTime(), new LongDataEntry(key.getApiLimitKey(), configuration.getProfileThreshold(key)))); } From 91aa144ae53f7598868e6b3f5c73985b6ecb32f9 Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Wed, 11 Nov 2020 15:13:59 +0200 Subject: [PATCH 095/102] Fix widget margin --- ui-ngx/src/app/core/services/dashboard-utils.service.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index 0b21a3f9be..a825b51f72 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -422,6 +422,12 @@ export class DashboardUtilsService { widgetLayout.row = row; widgetLayout.col = 0; } + + widgetLayout.sizeX = Math.floor(widgetLayout.sizeX); + widgetLayout.sizeY = Math.floor(widgetLayout.sizeY); + widgetLayout.row = Math.floor(widgetLayout.row); + widgetLayout.col = Math.floor(widgetLayout.col); + layout.widgets[widget.id] = widgetLayout; } From 5de0059eb765631e7ac4862fe4c19bb4de75b98c Mon Sep 17 00:00:00 2001 From: YevhenBondarenko Date: Wed, 11 Nov 2020 16:25:25 +0200 Subject: [PATCH 096/102] apiUsageState improvements --- .../server/dao/usagerecord/ApiUsageStateServiceImpl.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java index 12b5b3c01a..7836154c62 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/usagerecord/ApiUsageStateServiceImpl.java @@ -86,13 +86,13 @@ public class ApiUsageStateServiceImpl extends AbstractEntityService implements A TenantProfile tenantProfile = tenantProfileDao.findById(tenantId, tenant.getTenantProfileId().getId()); TenantProfileConfiguration configuration = tenantProfile.getProfileData().getConfiguration(); List apiUsageStates = new ArrayList<>(); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.TRANSPORT.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.DB.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.RE.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); - apiUsageStates.add(new BasicTsKvEntry(apiUsageState.getCreatedTime(), + apiUsageStates.add(new BasicTsKvEntry(saved.getCreatedTime(), new StringDataEntry(ApiFeature.JS.getApiStateKey(), ApiUsageStateValue.ENABLED.name()))); tsService.save(tenantId, saved.getId(), apiUsageStates, 0L); From b02cbec0ee6b6bd2979b7f8b6e825642effa904d Mon Sep 17 00:00:00 2001 From: Andrii Shvaika Date: Wed, 11 Nov 2020 16:41:07 +0200 Subject: [PATCH 097/102] Minor logging improvements --- .../service/queue/processing/AbstractConsumerService.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java index 0dfc9c5742..2d9ca3f042 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java @@ -153,6 +153,8 @@ public abstract class AbstractConsumerService Date: Wed, 11 Nov 2020 18:02:10 +0200 Subject: [PATCH 098/102] Improvements --- .../DefaultTbApiUsageStateService.java | 3 +-- .../transport/DefaultTransportApiService.java | 25 +++++++++++-------- docker/README.md | 2 +- 3 files changed, 16 insertions(+), 14 deletions(-) diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index f5d65b8397..c7206712c3 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -237,7 +237,7 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { @Override public void onTenantUpdate(TenantId tenantId) { - log.info("[{}] On Tenant Update", tenantId); + log.info("[{}] On Tenant Update.", tenantId); TenantProfile tenantProfile = tenantProfileCache.get(tenantId); updateLock.lock(); try { @@ -251,7 +251,6 @@ public class DefaultTbApiUsageStateService implements TbApiUsageStateService { } private void updateTenantState(TenantApiUsageState state, TenantProfile profile) { - log.info("[{}] On Tenant Update.", state.getTenantId()); TenantProfileData oldProfileData = state.getTenantProfileData(); state.setTenantProfileId(profile.getId()); state.setTenantProfileData(profile.getProfileData()); diff --git a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java index 3e9e413ceb..c7cbea8200 100644 --- a/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java +++ b/application/src/main/java/org/thingsboard/server/service/transport/DefaultTransportApiService.java @@ -164,22 +164,25 @@ public class DefaultTransportApiService implements TransportApiService { } private ListenableFuture validateCredentials(TransportProtos.ValidateBasicMqttCredRequestMsg mqtt) { - DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); - if (credentials != null) { - if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { - return getDeviceInfo(credentials.getDeviceId(), credentials); - } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { - if (!checkMqttCredentials(mqtt, credentials)) { - credentials = null; + DeviceCredentials credentials = null; + if (mqtt.getUserName() != null) { + credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(mqtt.getUserName()); + if (credentials != null) { + if (credentials.getCredentialsType() == DeviceCredentialsType.ACCESS_TOKEN) { + return getDeviceInfo(credentials.getDeviceId(), credentials); + } else if (credentials.getCredentialsType() == DeviceCredentialsType.MQTT_BASIC) { + if (!checkMqttCredentials(mqtt, credentials)) { + credentials = null; + } } } - } - if (credentials == null) { - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); if (credentials == null) { - credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash("|", mqtt.getClientId(), mqtt.getUserName())); } } + if (credentials == null) { + credentials = checkMqttCredentials(mqtt, EncryptionUtil.getSha3Hash(mqtt.getClientId())); + } if (credentials != null) { return getDeviceInfo(credentials.getDeviceId(), credentials); } else { diff --git a/docker/README.md b/docker/README.md index a1051c039e..9664edc48e 100644 --- a/docker/README.md +++ b/docker/README.md @@ -58,7 +58,7 @@ In case of any issues you can examine service logs for errors. For example to see ThingsBoard node logs execute the following command: ` -$ docker-compose logs -f tb-core1 tb-rule-engine1 +$ docker-compose logs -f tb-core1 tb-core2 tb-rule-engine1 tb-rule-engine2 tb-mqtt-transport1 tb-mqtt-transport2 ` Or use `docker-compose ps` to see the state of all the containers. From 2ccaa095cb5e255410641bcb11ee3e73b7d4e39e Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Thu, 24 Sep 2020 14:47:29 +0300 Subject: [PATCH 099/102] Fix marker move --- .../home/components/widget/lib/maps/leaflet-map.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 5d6f39850e..14d296436d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -410,10 +410,16 @@ export default abstract class LeafletMap { } convertToCustomFormat(position: L.LatLng): object { - return { - [this.options.latKeyName]: position.lat % 90, - [this.options.lngKeyName]: position.lng % 180 - }; + if(position.lng > 180){ + position.lng = 180; + }else if(position.lng < -180){ + position.lng = -180; + }; + + return { + [this.options.latKeyName]: position.lat, + [this.options.lngKeyName]: position.lng + }; } convertToPolygonFormat(points: Array): Array { From a06c8354973e361c760fd90b293a7fad41d18a6a Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Mon, 28 Sep 2020 14:48:47 +0300 Subject: [PATCH 100/102] Fix marker move and marker point --- .../components/widget/lib/maps/leaflet-map.ts | 6 +++-- .../widget/lib/maps/providers/image-map.ts | 25 ++++++++++++++++--- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 14d296436d..742451ec5e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -131,10 +131,13 @@ export default abstract class LeafletMap { tooltipAnchor: [16, -28], shadowSize: [41, 41] }); + const customLatLng = this.convertToCustomFormat(mousePositionOnMap); + mousePositionOnMap.lat = customLatLng[this.options.latKeyName]; + mousePositionOnMap.lng = customLatLng[this.options.lngKeyName]; + const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map); this.addMarkers.push(newMarker); const datasourcesList = document.createElement('div'); - const customLatLng = this.convertToCustomFormat(mousePositionOnMap); const header = document.createElement('p'); header.appendChild(document.createTextNode('Select entity:')); header.setAttribute('style', 'font-size: 14px; margin: 8px 0'); @@ -415,7 +418,6 @@ export default abstract class LeafletMap { }else if(position.lng < -180){ position.lng = -180; }; - return { [this.options.latKeyName]: position.lat, [this.options.lngKeyName]: position.lng diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index 1b20ddad01..2c967660e0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -258,10 +258,29 @@ export class ImageMap extends LeafletMap { } convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object { - const point = this.latLngToPoint(position); + let point = this.latLngToPoint(position); + const customX = calculateNewPointCoordinate(point.x, width); + const customY = calculateNewPointCoordinate(point.y, height); + + if(customX === 0){ + point.x = 0; + } else if(customX === 1){ + point.x = width; + } + + if(customY === 0){ + point.y = 0; + } else if(customY === 1){ + point.y = height; + } + + const customLatLng = this.pointToLatLng(point.x,point.y) + return { - [this.options.xPosKeyName]: calculateNewPointCoordinate(point.x, width), - [this.options.yPosKeyName]: calculateNewPointCoordinate(point.y, height) + [this.options.xPosKeyName]: customX, + [this.options.yPosKeyName]: customY, + [this.options.latKeyName]:customLatLng.lat, + [this.options.lngKeyName]:customLatLng.lng }; } From 60de89015acf58d7efc9903d9713661c04f8afed Mon Sep 17 00:00:00 2001 From: Kalutka Zhenya Date: Mon, 28 Sep 2020 15:01:20 +0300 Subject: [PATCH 101/102] Refactoring --- .../modules/home/components/widget/lib/maps/leaflet-map.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index 742451ec5e..f29ab38f03 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -132,8 +132,8 @@ export default abstract class LeafletMap { shadowSize: [41, 41] }); const customLatLng = this.convertToCustomFormat(mousePositionOnMap); - mousePositionOnMap.lat = customLatLng[this.options.latKeyName]; - mousePositionOnMap.lng = customLatLng[this.options.lngKeyName]; + mousePositionOnMap.lat = customLatLng[this.options.latKeyName]; + mousePositionOnMap.lng = customLatLng[this.options.lngKeyName]; const newMarker = L.marker(mousePositionOnMap, { icon }).addTo(this.map); this.addMarkers.push(newMarker); From 724b6c5247c97e661b104985fbb5956c88fae0dc Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Wed, 11 Nov 2020 18:03:33 +0200 Subject: [PATCH 102/102] Refactoring --- .../components/widget/lib/maps/leaflet-map.ts | 6 +++--- .../home/components/widget/lib/maps/markers.ts | 2 +- .../widget/lib/maps/providers/image-map.ts | 17 ++++++++--------- 3 files changed, 12 insertions(+), 13 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts index f29ab38f03..0f1f08799c 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts @@ -413,11 +413,11 @@ export default abstract class LeafletMap { } convertToCustomFormat(position: L.LatLng): object { - if(position.lng > 180){ + if (position.lng > 180) { position.lng = 180; - }else if(position.lng < -180){ + } else if (position.lng < -180) { position.lng = -180; - }; + } return { [this.options.latKeyName]: position.lat, [this.options.lngKeyName]: position.lng diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts index 4796956fa6..5f9e5e8362 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts @@ -94,7 +94,7 @@ export class Marker { } updateMarkerPosition(position: L.LatLng) { - if (!this.location.equals(position)) { + if (!this.leafletMarker.getLatLng().equals(position)) { this.location = position; this.leafletMarker.setLatLng(position); } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts index 2c967660e0..825ace1227 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts @@ -258,29 +258,28 @@ export class ImageMap extends LeafletMap { } convertToCustomFormat(position: L.LatLng, width = this.width, height = this.height): object { - let point = this.latLngToPoint(position); + const point = this.latLngToPoint(position); const customX = calculateNewPointCoordinate(point.x, width); const customY = calculateNewPointCoordinate(point.y, height); - if(customX === 0){ + if (customX === 0) { point.x = 0; - } else if(customX === 1){ + } else if (customX === 1) { point.x = width; } - if(customY === 0){ + if (customY === 0) { point.y = 0; - } else if(customY === 1){ + } else if (customY === 1) { point.y = height; } - - const customLatLng = this.pointToLatLng(point.x,point.y) + const customLatLng = this.pointToLatLng(point.x, point.y); return { [this.options.xPosKeyName]: customX, [this.options.yPosKeyName]: customY, - [this.options.latKeyName]:customLatLng.lat, - [this.options.lngKeyName]:customLatLng.lng + [this.options.latKeyName]: customLatLng.lat, + [this.options.lngKeyName]: customLatLng.lng }; }