Browse Source

Merge branch 'develop/3.0' of https://github.com/thingsboard/thingsboard into map/3.0

pull/2738/head
Adsumus 6 years ago
parent
commit
746e63ab63
  1. 4
      application/pom.xml
  2. 4
      application/src/main/data/json/demo/rule_chains/root_rule_chain.json
  3. 4
      application/src/main/data/json/demo/rule_chains/thermostat_alarms.json
  4. 29
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  5. 12
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java
  6. 24
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  7. 13
      application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java
  8. 2
      application/src/main/java/org/thingsboard/server/controller/AuthController.java
  9. 13
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java
  10. 18
      application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java
  11. 1
      application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java
  12. 5
      application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java
  13. 10
      application/src/main/java/org/thingsboard/server/service/queue/processing/BurstTbRuleEngineSubmitStrategy.java
  14. 8
      application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java
  15. 1
      application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByTenantIdTbRuleEngineSubmitStrategy.java
  16. 6
      application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialTbRuleEngineSubmitStrategy.java
  17. 8
      application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java
  18. 7
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java
  19. 2
      application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java
  20. 8
      application/src/main/resources/thingsboard.yml
  21. 12
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  22. 12
      common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java
  23. 1
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusAdmin.java
  24. 118
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java
  25. 7
      common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusProducerTemplate.java
  26. 53
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractParallelTbQueueConsumerTemplate.java
  27. 152
      common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java
  28. 107
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  29. 32
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubAdmin.java
  30. 105
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java
  31. 6
      common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubProducerTemplate.java
  32. 2
      common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqAdmin.java
  33. 107
      common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqConsumerTemplate.java
  34. 14
      common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqProducerTemplate.java
  35. 139
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsConsumerTemplate.java
  36. 5
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java
  37. 1
      common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java
  38. 23
      common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
  39. 13
      common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java
  40. 13
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportServiceCallback.java
  41. 4
      dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java
  42. 6
      docker/docker-compose.hybrid.yml
  43. 2
      docker/tb-js-executor.env
  44. 12
      msa/js-executor/config/custom-environment-variables.yml
  45. 12
      msa/js-executor/config/default.yml
  46. 1320
      msa/js-executor/package-lock.json
  47. 1
      msa/js-executor/package.json
  48. 38
      msa/js-executor/queue/awsSqsTemplate.js
  49. 2
      msa/js-executor/queue/kafkaTemplate.js
  50. 29
      msa/js-executor/queue/pubSubTemplate.js
  51. 26
      msa/js-executor/queue/rabbitmqTemplate.js
  52. 3
      msa/js-executor/queue/serviceBusTemplate.js
  53. 2
      msa/js-executor/server.js
  54. 38
      pom.xml
  55. 5
      rule-engine/rule-engine-api/pom.xml
  56. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java
  57. 580
      ui-ngx/package-lock.json
  58. 38
      ui-ngx/package.json
  59. 4
      ui-ngx/src/app/core/api/datasource-subcription.ts
  60. 4
      ui-ngx/src/app/core/api/datasource.service.ts
  61. 58
      ui-ngx/src/app/core/services/utils.service.ts
  62. 223
      ui-ngx/src/app/core/utils.ts
  63. 4
      ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts
  64. 4
      ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts
  65. 4
      ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts
  66. 23
      ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts
  67. 6
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts
  68. 12
      ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts
  69. 175
      ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts
  70. 7
      ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts
  71. 7
      ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts
  72. 4
      ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts
  73. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts
  74. 2
      ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts
  75. 28
      ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts
  76. 4
      ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts
  77. 3
      ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts
  78. 2
      ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html
  79. 27
      ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts
  80. 1
      ui-ngx/src/app/shared/pipe/public-api.ts
  81. 25
      ui-ngx/src/app/shared/pipe/template.pipe.ts
  82. 3
      ui-ngx/src/app/shared/shared.module.ts
  83. 6
      ui/src/app/locale/locale.constant-de_DE.json
  84. 4
      ui/src/app/locale/locale.constant-el_GR.json
  85. 4
      ui/src/app/locale/locale.constant-es_ES.json
  86. 6
      ui/src/app/locale/locale.constant-fr_FR.json
  87. 6
      ui/src/app/locale/locale.constant-it_IT.json
  88. 4
      ui/src/app/locale/locale.constant-ja_JA.json
  89. 4
      ui/src/app/locale/locale.constant-ko_KR.json
  90. 6
      ui/src/app/locale/locale.constant-ro_RO.json
  91. 6
      ui/src/app/locale/locale.constant-tr_TR.json

4
application/pom.xml

@ -193,8 +193,8 @@
<artifactId>logback-classic</artifactId> <artifactId>logback-classic</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.mail</groupId> <groupId>com.sun.mail</groupId>
<artifactId>mail</artifactId> <artifactId>javax.mail</artifactId>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.apache.curator</groupId> <groupId>org.apache.curator</groupId>

4
application/src/main/data/json/demo/rule_chains/root_rule_chain.json

@ -17,7 +17,7 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbJsFilterNode", "type": "org.thingsboard.rule.engine.filter.TbJsFilterNode",
"name": "Is Thermostat?", "name": "Is Thermostat?",
"debugMode": true, "debugMode": false,
"configuration": { "configuration": {
"jsScript": "return msg.id.entityType === \"DEVICE\" && msg.type === \"thermostat\";" "jsScript": "return msg.id.entityType === \"DEVICE\" && msg.type === \"thermostat\";"
} }
@ -113,7 +113,7 @@
}, },
"type": "org.thingsboard.rule.engine.action.TbCreateRelationNode", "type": "org.thingsboard.rule.engine.action.TbCreateRelationNode",
"name": "Relate to Asset", "name": "Relate to Asset",
"debugMode": true, "debugMode": false,
"configuration": { "configuration": {
"direction": "FROM", "direction": "FROM",
"relationType": "ToAlarmPropagationAsset", "relationType": "ToAlarmPropagationAsset",

4
application/src/main/data/json/demo/rule_chains/thermostat_alarms.json

@ -81,7 +81,7 @@
}, },
"type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode", "type": "org.thingsboard.rule.engine.filter.TbJsSwitchNode",
"name": "Check Alarms", "name": "Check Alarms",
"debugMode": true, "debugMode": false,
"configuration": { "configuration": {
"jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;" "jsScript": "var relations = [];\nif(metadata[\"ss_alarmTemperature\"] === \"true\"){\n if(msg.temperature > metadata[\"ss_thresholdTemperature\"]){\n relations.push(\"NewTempAlarm\");\n } else {\n relations.push(\"ClearTempAlarm\");\n }\n}\nif(metadata[\"ss_alarmHumidity\"] === \"true\"){\n if(msg.humidity < metadata[\"ss_thresholdHumidity\"]){\n relations.push(\"NewHumidityAlarm\");\n } else {\n relations.push(\"ClearHumidityAlarm\");\n }\n}\n\nreturn relations;"
} }
@ -93,7 +93,7 @@
}, },
"type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode", "type": "org.thingsboard.rule.engine.metadata.TbGetAttributesNode",
"name": "Fetch Configuration", "name": "Fetch Configuration",
"debugMode": true, "debugMode": false,
"configuration": { "configuration": {
"clientAttributeNames": [], "clientAttributeNames": [],
"sharedAttributeNames": [], "sharedAttributeNames": [],

29
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -163,7 +163,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ? String dispatcherName = tenantId.getId().equals(EntityId.NULL_UUID) ?
DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME; DefaultActorService.SYSTEM_RULE_DISPATCHER_NAME : DefaultActorService.TENANT_RULE_DISPATCHER_NAME;
return context.actorOf( return context.actorOf(
Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getId())) Props.create(new RuleNodeActor.ActorCreator(systemContext, tenantId, entityId, ruleNode.getName(), ruleNode.getId()))
.withDispatcher(dispatcherName), ruleNode.getId().toString()); .withDispatcher(dispatcherName), ruleNode.getId().toString());
} }
@ -200,7 +200,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg); log.trace("[{}][{}] Processing message [{}]: {}", entityId, firstId, msg.getId(), msg);
if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) { if (envelope.getRelationTypes() == null || envelope.getRelationTypes().isEmpty()) {
try { try {
checkActive(); checkActive(envelope.getTbMsg());
RuleNodeId targetId = msg.getRuleNodeId(); RuleNodeId targetId = msg.getRuleNodeId();
RuleNodeCtx targetCtx; RuleNodeCtx targetCtx;
if (targetId == null) { if (targetId == null) {
@ -216,6 +216,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId); log.trace("[{}][{}] Rule node does not exist. Probably old message", entityId, targetId);
msg.getCallback().onSuccess(); msg.getCallback().onSuccess();
} }
} catch (RuleNodeException rne) {
envelope.getTbMsg().getCallback().onFailure(rne);
} catch (Exception e) { } catch (Exception e) {
envelope.getTbMsg().getCallback().onFailure(new RuleEngineException(e.getMessage())); envelope.getTbMsg().getCallback().onFailure(new RuleEngineException(e.getMessage()));
} }
@ -225,11 +227,15 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
} }
void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) { void onRuleChainToRuleChainMsg(RuleChainToRuleChainMsg envelope) {
checkActive(); try {
if (firstNode != null) { checkActive(envelope.getMsg());
pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType()); if (firstNode != null) {
} else { pushMsgToNode(firstNode, envelope.getMsg(), envelope.getFromRelationType());
envelope.getMsg().getCallback().onSuccess(); } else {
envelope.getMsg().getCallback().onSuccess();
}
} catch (RuleNodeException e) {
log.debug("Rule Chain is not active. Current state [{}] for processor [{}][{}] tenant [{}]", state, entityId.getEntityType(), entityId, tenantId);
} }
} }
@ -239,7 +245,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private void onTellNext(TbMsg msg, RuleNodeId originatorNodeId, Set<String> relationTypes, String failureMessage) { private void onTellNext(TbMsg msg, RuleNodeId originatorNodeId, Set<String> relationTypes, String failureMessage) {
try { try {
checkActive(); checkActive(msg);
EntityId entityId = msg.getOriginator(); EntityId entityId = msg.getOriginator();
TopicPartitionInfo tpi = systemContext.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId); TopicPartitionInfo tpi = systemContext.resolve(ServiceType.TB_RULE_ENGINE, tenantId, entityId);
List<RuleNodeRelation> relations = nodeRoutes.get(originatorNodeId).stream() List<RuleNodeRelation> relations = nodeRoutes.get(originatorNodeId).stream()
@ -272,6 +278,8 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
putToQueue(tpi, msg, callbackWrapper, target); putToQueue(tpi, msg, callbackWrapper, target);
} }
} }
} catch (RuleNodeException rne) {
msg.getCallback().onFailure(rne);
} catch (Exception e) { } catch (Exception e) {
msg.getCallback().onFailure(new RuleEngineException("onTellNext - " + e.getMessage())); msg.getCallback().onFailure(new RuleEngineException("onTellNext - " + e.getMessage()));
} }
@ -333,4 +341,9 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
} }
} }
@Override
protected RuleNodeException getInactiveException() {
RuleNode firstRuleNode = firstNode != null ? firstNode.getSelf() : null;
return new RuleNodeException("Rule Chain is not active! Failed to initialize.", ruleChainName, firstRuleNode);
}
} }

12
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActor.java

@ -27,12 +27,14 @@ import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessageProcessor> { public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessageProcessor> {
private final String ruleChainName;
private final RuleChainId ruleChainId; private final RuleChainId ruleChainId;
private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { private RuleNodeActor(ActorSystemContext systemContext, TenantId tenantId, RuleChainId ruleChainId, String ruleChainName, RuleNodeId ruleNodeId) {
super(systemContext, tenantId, ruleNodeId); super(systemContext, tenantId, ruleNodeId);
this.ruleChainName = ruleChainName;
this.ruleChainId = ruleChainId; this.ruleChainId = ruleChainId;
setProcessor(new RuleNodeActorMessageProcessor(tenantId, ruleChainId, ruleNodeId, systemContext, setProcessor(new RuleNodeActorMessageProcessor(tenantId, this.ruleChainName, ruleNodeId, systemContext,
context().parent(), context().self())); context().parent(), context().self()));
} }
@ -96,19 +98,21 @@ public class RuleNodeActor extends ComponentActor<RuleNodeId, RuleNodeActorMessa
private final TenantId tenantId; private final TenantId tenantId;
private final RuleChainId ruleChainId; private final RuleChainId ruleChainId;
private final String ruleChainName;
private final RuleNodeId ruleNodeId; private final RuleNodeId ruleNodeId;
public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public ActorCreator(ActorSystemContext context, TenantId tenantId, RuleChainId ruleChainId, String ruleChainName, RuleNodeId ruleNodeId) {
super(context); super(context);
this.tenantId = tenantId; this.tenantId = tenantId;
this.ruleChainId = ruleChainId; this.ruleChainId = ruleChainId;
this.ruleChainName = ruleChainName;
this.ruleNodeId = ruleNodeId; this.ruleNodeId = ruleNodeId;
} }
@Override @Override
public RuleNodeActor create() throws Exception { public RuleNodeActor create() throws Exception {
return new RuleNodeActor(context, tenantId, ruleChainId, ruleNodeId); return new RuleNodeActor(context, tenantId, ruleChainId, ruleChainName, ruleNodeId);
} }
} }

24
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java

@ -17,37 +17,33 @@ package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorContext; import akka.actor.ActorContext;
import akka.actor.ActorRef; import akka.actor.ActorRef;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor; import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.common.msg.queue.RuleNodeException;
/** /**
* @author Andrew Shvayka * @author Andrew Shvayka
*/ */
public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> { public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNodeId> {
private final ActorRef parent; private final String ruleChainName;
private final ActorRef self; private final ActorRef self;
private final RuleChainService service;
private RuleNode ruleNode; private RuleNode ruleNode;
private TbNode tbNode; private TbNode tbNode;
private DefaultTbContext defaultCtx; private DefaultTbContext defaultCtx;
RuleNodeActorMessageProcessor(TenantId tenantId, RuleChainId ruleChainId, RuleNodeId ruleNodeId, ActorSystemContext systemContext RuleNodeActorMessageProcessor(TenantId tenantId, String ruleChainName, RuleNodeId ruleNodeId, ActorSystemContext systemContext
, ActorRef parent, ActorRef self) { , ActorRef parent, ActorRef self) {
super(systemContext, tenantId, ruleNodeId); super(systemContext, tenantId, ruleNodeId);
this.parent = parent; this.ruleChainName = ruleChainName;
this.self = self; this.self = self;
this.service = systemContext.getRuleChainService();
this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId); this.ruleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode)); this.defaultCtx = new DefaultTbContext(systemContext, new RuleNodeCtx(tenantId, parent, self, ruleNode));
} }
@ -63,8 +59,8 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
@Override @Override
public void onUpdate(ActorContext context) throws Exception { public void onUpdate(ActorContext context) throws Exception {
RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId); RuleNode newRuleNode = systemContext.getRuleChainService().findRuleNodeById(tenantId, entityId);
boolean restartRequired = !(ruleNode.getType().equals(newRuleNode.getType()) boolean restartRequired = state != ComponentLifecycleState.ACTIVE ||
&& ruleNode.getConfiguration().equals(newRuleNode.getConfiguration())); !(ruleNode.getType().equals(newRuleNode.getType()) && ruleNode.getConfiguration().equals(newRuleNode.getConfiguration()));
this.ruleNode = newRuleNode; this.ruleNode = newRuleNode;
this.defaultCtx.updateSelf(newRuleNode); this.defaultCtx.updateSelf(newRuleNode);
if (restartRequired) { if (restartRequired) {
@ -91,7 +87,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
} }
public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception { public void onRuleToSelfMsg(RuleNodeToSelfMsg msg) throws Exception {
checkActive(); checkActive(msg.getMsg());
if (ruleNode.isDebugMode()) { if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self"); systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), "Self");
} }
@ -103,7 +99,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
} }
void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception { void onRuleChainToRuleNodeMsg(RuleChainToRuleNodeMsg msg) throws Exception {
checkActive(); checkActive(msg.getMsg());
if (ruleNode.isDebugMode()) { if (ruleNode.isDebugMode()) {
systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType()); systemContext.persistDebugInput(tenantId, entityId, msg.getMsg(), msg.getFromRelationType());
} }
@ -129,4 +125,8 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
return tbNode; return tbNode;
} }
@Override
protected RuleNodeException getInactiveException() {
return new RuleNodeException("Rule Node is not active! Failed to initialize.", ruleChainName, ruleNode);
}
} }

13
application/src/main/java/org/thingsboard/server/actors/shared/ComponentMsgProcessor.java

@ -22,7 +22,10 @@ import org.thingsboard.server.actors.stats.StatsPersistTick;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleState; import org.thingsboard.server.common.data.plugin.ComponentLifecycleState;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg;
import org.thingsboard.server.common.msg.queue.RuleEngineException;
import org.thingsboard.server.common.msg.queue.RuleNodeException;
@Slf4j @Slf4j
public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor { public abstract class ComponentMsgProcessor<T extends EntityId> extends AbstractContextAwareMsgProcessor {
@ -74,11 +77,17 @@ public abstract class ComponentMsgProcessor<T extends EntityId> extends Abstract
schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency); schedulePeriodicMsgWithDelay(context, new StatsPersistTick(), statsPersistFrequency, statsPersistFrequency);
} }
protected void checkActive() { protected void checkActive(TbMsg tbMsg) throws RuleNodeException {
if (state != ComponentLifecycleState.ACTIVE) { if (state != ComponentLifecycleState.ACTIVE) {
log.debug("Component is not active. Current state [{}] for processor [{}][{}] tenant [{}]", state, entityId.getEntityType(), entityId, tenantId); log.debug("Component is not active. Current state [{}] for processor [{}][{}] tenant [{}]", state, entityId.getEntityType(), entityId, tenantId);
throw new IllegalStateException("Rule chain is not active! " + entityId + " - " + tenantId); RuleNodeException ruleNodeException = getInactiveException();
if (tbMsg != null) {
tbMsg.getCallback().onFailure(ruleNodeException);
}
throw ruleNodeException;
} }
} }
abstract protected RuleNodeException getInactiveException();
} }

2
application/src/main/java/org/thingsboard/server/controller/AuthController.java

@ -338,7 +338,7 @@ public class AuthController extends BaseController {
@RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST) @RequestMapping(value = "/noauth/oauth2Clients", method = RequestMethod.POST)
@ResponseBody @ResponseBody
public List<OAuth2ClientInfo> getOath2Clients() throws ThingsboardException { public List<OAuth2ClientInfo> getOAuth2Clients() throws ThingsboardException {
try { try {
return oauth2Service.getOAuth2Clients(); return oauth2Service.getOAuth2Clients();
} catch (Exception e) { } catch (Exception e) {

13
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java

@ -23,6 +23,7 @@ import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RpcError; import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId; 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.TbActorMsg;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
@ -45,6 +46,7 @@ import org.thingsboard.server.service.encoding.DataDecodingEncodingService;
import org.thingsboard.server.service.queue.processing.AbstractConsumerService; import org.thingsboard.server.service.queue.processing.AbstractConsumerService;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse; import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.ToDeviceRpcRequestActorMsg;
import org.thingsboard.server.service.state.DeviceStateService; import org.thingsboard.server.service.state.DeviceStateService;
import org.thingsboard.server.service.subscription.SubscriptionManagerService; import org.thingsboard.server.service.subscription.SubscriptionManagerService;
import org.thingsboard.server.service.subscription.TbLocalSubscriptionService; import org.thingsboard.server.service.subscription.TbLocalSubscriptionService;
@ -100,7 +102,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} }
@PreDestroy @PreDestroy
public void destroy(){ public void destroy() {
super.destroy(); super.destroy();
} }
@ -143,8 +145,13 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService<ToCore
} else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) { } else if (toCoreMsg.getToDeviceActorNotificationMsg() != null && !toCoreMsg.getToDeviceActorNotificationMsg().isEmpty()) {
Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray()); Optional<TbActorMsg> actorMsg = encodingService.decode(toCoreMsg.getToDeviceActorNotificationMsg().toByteArray());
if (actorMsg.isPresent()) { if (actorMsg.isPresent()) {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get()); TbActorMsg tbActorMsg = actorMsg.get();
actorContext.tell(actorMsg.get(), ActorRef.noSender()); if (tbActorMsg.getMsgType().equals(MsgType.DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG)) {
tbCoreDeviceRpcService.forwardRpcRequestToDeviceActor((ToDeviceRpcRequestActorMsg) tbActorMsg);
} else {
log.trace("[{}] Forwarding message to App Actor {}", id, actorMsg.get());
actorContext.tell(actorMsg.get(), ActorRef.noSender());
}
} }
callback.onSuccess(); callback.onSuccess();
} }

18
application/src/main/java/org/thingsboard/server/service/queue/DefaultTbRuleEngineConsumerService.java

@ -21,6 +21,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.Scheduled; import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbActorMsg;
@ -31,6 +32,7 @@ import org.thingsboard.server.common.msg.queue.ServiceQueue;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg;
import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineNotificationMsg;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
@ -48,6 +50,9 @@ import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStr
import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory; import org.thingsboard.server.service.queue.processing.TbRuleEngineProcessingStrategyFactory;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategy;
import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory; import org.thingsboard.server.service.queue.processing.TbRuleEngineSubmitStrategyFactory;
import org.thingsboard.server.service.rpc.FromDeviceRpcResponse;
import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService;
import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService;
import org.thingsboard.server.service.stats.RuleEngineStatisticsService; import org.thingsboard.server.service.stats.RuleEngineStatisticsService;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
@ -81,6 +86,7 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory; private final TbRuleEngineQueueFactory tbRuleEngineQueueFactory;
private final TbQueueRuleEngineSettings ruleEngineSettings; private final TbQueueRuleEngineSettings ruleEngineSettings;
private final RuleEngineStatisticsService statisticsService; private final RuleEngineStatisticsService statisticsService;
private final TbRuleEngineDeviceRpcService tbDeviceRpcService;
private final ConcurrentMap<String, TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>>> consumers = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TbQueueConsumer<TbProtoQueueMsg<ToRuleEngineMsg>>> consumers = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TbRuleEngineQueueConfiguration> consumerConfigurations = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TbRuleEngineQueueConfiguration> consumerConfigurations = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TbRuleEngineConsumerStats> consumerStats = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TbRuleEngineConsumerStats> consumerStats = new ConcurrentHashMap<>();
@ -90,13 +96,15 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
TbRuleEngineSubmitStrategyFactory submitStrategyFactory, TbRuleEngineSubmitStrategyFactory submitStrategyFactory,
TbQueueRuleEngineSettings ruleEngineSettings, TbQueueRuleEngineSettings ruleEngineSettings,
TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService, TbRuleEngineQueueFactory tbRuleEngineQueueFactory, RuleEngineStatisticsService statisticsService,
ActorSystemContext actorContext, DataDecodingEncodingService encodingService) { ActorSystemContext actorContext, DataDecodingEncodingService encodingService,
TbRuleEngineDeviceRpcService tbDeviceRpcService) {
super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer()); super(actorContext, encodingService, tbRuleEngineQueueFactory.createToRuleEngineNotificationsMsgConsumer());
this.statisticsService = statisticsService; this.statisticsService = statisticsService;
this.ruleEngineSettings = ruleEngineSettings; this.ruleEngineSettings = ruleEngineSettings;
this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory; this.tbRuleEngineQueueFactory = tbRuleEngineQueueFactory;
this.submitStrategyFactory = submitStrategyFactory; this.submitStrategyFactory = submitStrategyFactory;
this.processingStrategyFactory = processingStrategyFactory; this.processingStrategyFactory = processingStrategyFactory;
this.tbDeviceRpcService = tbDeviceRpcService;
} }
@PostConstruct @PostConstruct
@ -227,7 +235,15 @@ public class DefaultTbRuleEngineConsumerService extends AbstractConsumerService<
actorContext.tell(actorMsg.get(), ActorRef.noSender()); actorContext.tell(actorMsg.get(), ActorRef.noSender());
} }
callback.onSuccess(); callback.onSuccess();
} else if (nfMsg.hasFromDeviceRpcResponse()) {
TransportProtos.FromDeviceRPCResponseProto proto = nfMsg.getFromDeviceRpcResponse();
RpcError error = proto.getError() > 0 ? RpcError.values()[proto.getError()] : null;
FromDeviceRpcResponse response = new FromDeviceRpcResponse(new UUID(proto.getRequestIdMSB(), proto.getRequestIdLSB())
, proto.getResponse(), error);
tbDeviceRpcService.processRpcResponseFromDevice(response);
callback.onSuccess();
} else { } else {
log.trace("Received notification with missing handler");
callback.onSuccess(); callback.onSuccess();
} }
} }

1
application/src/main/java/org/thingsboard/server/service/queue/processing/AbstractConsumerService.java

@ -23,7 +23,6 @@ import org.thingsboard.common.util.ThingsBoardThreadFactory;
import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.ServiceType;
import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TbCallback;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.TbQueueConsumer; import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import org.thingsboard.server.queue.discovery.PartitionChangeEvent; import org.thingsboard.server.queue.discovery.PartitionChangeEvent;

5
application/src/main/java/org/thingsboard/server/service/queue/processing/BatchTbRuleEngineSubmitStrategy.java

@ -23,7 +23,6 @@ import java.util.LinkedHashMap;
import java.util.Map; import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
@ -77,8 +76,8 @@ public class BatchTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS
} }
} }
int submitSize = pendingPack.size(); int submitSize = pendingPack.size();
if (log.isInfoEnabled() && submitSize > 0) { if (log.isDebugEnabled() && submitSize > 0) {
log.info("[{}] submitting [{}] messages to rule engine", queueName, submitSize); log.debug("[{}] submitting [{}] messages to rule engine", queueName, submitSize);
} }
pendingPack.forEach(msgConsumer); pendingPack.forEach(msgConsumer);
} }

10
application/src/main/java/org/thingsboard/server/service/queue/processing/BurstTbRuleEngineSubmitStrategy.java

@ -19,14 +19,8 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ExecutorService;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j @Slf4j
public class BurstTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { public class BurstTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy {
@ -37,8 +31,8 @@ public class BurstTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitS
@Override @Override
public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) { public void submitAttempt(BiConsumer<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> msgConsumer) {
if (log.isInfoEnabled()) { if (log.isDebugEnabled()) {
log.info("[{}] submitting [{}] messages to rule engine", queueName, orderedMsgList.size()); log.debug("[{}] submitting [{}] messages to rule engine", queueName, orderedMsgList.size());
} }
orderedMsgList.forEach(pair -> msgConsumer.accept(pair.uuid, pair.msg)); orderedMsgList.forEach(pair -> msgConsumer.accept(pair.uuid, pair.msg));
} }

8
application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByEntityIdTbRuleEngineSubmitStrategy.java

@ -15,26 +15,18 @@
*/ */
package org.thingsboard.server.service.queue.processing; package org.thingsboard.server.service.queue.processing;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.gen.MsgProtos;
import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import java.util.ArrayList;
import java.util.LinkedList; import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Queue; import java.util.Queue;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.BiConsumer; import java.util.function.BiConsumer;
import java.util.stream.Collectors;
@Slf4j @Slf4j
public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy { public abstract class SequentialByEntityIdTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSubmitStrategy {

1
application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialByTenantIdTbRuleEngineSubmitStrategy.java

@ -30,6 +30,5 @@ public class SequentialByTenantIdTbRuleEngineSubmitStrategy extends SequentialBy
@Override @Override
protected EntityId getEntityId(TransportProtos.ToRuleEngineMsg msg) { protected EntityId getEntityId(TransportProtos.ToRuleEngineMsg msg) {
return new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB())); return new TenantId(new UUID(msg.getTenantIdMSB(), msg.getTenantIdLSB()));
} }
} }

6
application/src/main/java/org/thingsboard/server/service/queue/processing/SequentialTbRuleEngineSubmitStrategy.java

@ -19,8 +19,6 @@ import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicInteger;
@ -63,8 +61,8 @@ public class SequentialTbRuleEngineSubmitStrategy extends AbstractTbRuleEngineSu
if (idx < listSize) { if (idx < listSize) {
IdMsgPair pair = orderedMsgList.get(idx); IdMsgPair pair = orderedMsgList.get(idx);
expectedMsgId = pair.uuid; expectedMsgId = pair.uuid;
if (log.isInfoEnabled()) { if (log.isDebugEnabled()) {
log.info("[{}] submitting [{}] message to rule engine", queueName, pair.msg); log.debug("[{}] submitting [{}] message to rule engine", queueName, pair.msg);
} }
msgConsumer.accept(pair.uuid, pair.msg); msgConsumer.accept(pair.uuid, pair.msg);
} }

8
application/src/main/java/org/thingsboard/server/service/queue/processing/TbRuleEngineProcessingStrategyFactory.java

@ -82,10 +82,10 @@ public class TbRuleEngineProcessingStrategyFactory {
retryCount++; retryCount++;
double failedCount = result.getFailedMap().size() + result.getPendingMap().size(); double failedCount = result.getFailedMap().size() + result.getPendingMap().size();
if (maxRetries > 0 && retryCount > maxRetries) { if (maxRetries > 0 && retryCount > maxRetries) {
log.info("[{}] Skip reprocess of the rule engine pack due to max retries", queueName); log.debug("[{}] Skip reprocess of the rule engine pack due to max retries", queueName);
return new TbRuleEngineProcessingDecision(true, null); return new TbRuleEngineProcessingDecision(true, null);
} else if (maxAllowedFailurePercentage > 0 && (failedCount / initialTotalCount) > maxAllowedFailurePercentage) { } else if (maxAllowedFailurePercentage > 0 && (failedCount / initialTotalCount) > maxAllowedFailurePercentage) {
log.info("[{}] Skip reprocess of the rule engine pack due to max allowed failure percentage", queueName); log.debug("[{}] Skip reprocess of the rule engine pack due to max allowed failure percentage", queueName);
return new TbRuleEngineProcessingDecision(true, null); return new TbRuleEngineProcessingDecision(true, null);
} else { } else {
ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> toReprocess = new ConcurrentHashMap<>(initialTotalCount); ConcurrentMap<UUID, TbProtoQueueMsg<TransportProtos.ToRuleEngineMsg>> toReprocess = new ConcurrentHashMap<>(initialTotalCount);
@ -98,7 +98,7 @@ public class TbRuleEngineProcessingStrategyFactory {
if (retrySuccessful) { if (retrySuccessful) {
result.getSuccessMap().forEach(toReprocess::put); result.getSuccessMap().forEach(toReprocess::put);
} }
log.info("[{}] Going to reprocess {} messages", queueName, toReprocess.size()); log.debug("[{}] Going to reprocess {} messages", queueName, toReprocess.size());
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
toReprocess.forEach((id, msg) -> log.trace("Going to reprocess [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); toReprocess.forEach((id, msg) -> log.trace("Going to reprocess [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));
} }
@ -126,7 +126,7 @@ public class TbRuleEngineProcessingStrategyFactory {
@Override @Override
public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) { public TbRuleEngineProcessingDecision analyze(TbRuleEngineProcessingResult result) {
if (!result.isSuccess()) { if (!result.isSuccess()) {
log.info("[{}] Reprocessing skipped for {} failed and {} timeout messages", queueName, result.getFailedMap().size(), result.getPendingMap().size()); log.debug("[{}] Reprocessing skipped for {} failed and {} timeout messages", queueName, result.getFailedMap().size(), result.getPendingMap().size());
} }
if (log.isTraceEnabled()) { if (log.isTraceEnabled()) {
result.getFailedMap().forEach((id, msg) -> log.trace("Failed messages [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY))); result.getFailedMap().forEach((id, msg) -> log.trace("Failed messages [{}]: {}", id, TbMsg.fromBytes(msg.getValue().getTbMsg().toByteArray(), TbMsgCallback.EMPTY)));

7
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/BasicOAuth2ClientMapper.java

@ -51,9 +51,9 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen
String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey()); String firstName = getStringAttributeByKey(attributes, config.getBasic().getFirstNameAttributeKey());
oauth2User.setFirstName(firstName); oauth2User.setFirstName(firstName);
} }
if (!StringUtils.isEmpty(config.getBasic().getCustomerNameStrategyPattern())) { if (!StringUtils.isEmpty(config.getBasic().getCustomerNamePattern())) {
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
String customerName = sub.replace(config.getBasic().getCustomerNameStrategyPattern()); String customerName = sub.replace(config.getBasic().getCustomerNamePattern());
oauth2User.setCustomerName(customerName); oauth2User.setCustomerName(customerName);
} }
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation());
@ -68,7 +68,7 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen
return email.substring(email .indexOf("@") + 1); return email.substring(email .indexOf("@") + 1);
case CUSTOM_TENANT_STRATEGY: case CUSTOM_TENANT_STRATEGY:
StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX); StrSubstitutor sub = new StrSubstitutor(attributes, START_PLACEHOLDER_PREFIX, END_PLACEHOLDER_PREFIX);
return sub.replace(config.getBasic().getTenantNameStrategyPattern()); return sub.replace(config.getBasic().getTenantNamePattern());
default: default:
throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!"); throw new RuntimeException("Tenant Name Strategy with type " + config.getBasic().getTenantNameStrategy() + " is not supported!");
} }
@ -78,7 +78,6 @@ public class BasicOAuth2ClientMapper extends AbstractOAuth2ClientMapper implemen
String result = null; String result = null;
try { try {
result = (String) attributes.get(key); result = (String) attributes.get(key);
} catch (Exception e) { } catch (Exception e) {
log.warn("Can't convert attribute to String by key " + key); log.warn("Can't convert attribute to String by key " + key);
} }

2
application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/CustomOAuth2ClientMapper.java

@ -41,7 +41,7 @@ public class CustomOAuth2ClientMapper extends AbstractOAuth2ClientMapper impleme
return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation()); return getOrCreateSecurityUserFromOAuth2User(oauth2User, config.getBasic().isAllowUserCreation());
} }
public OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) { private synchronized OAuth2User getOAuth2User(OAuth2AuthenticationToken token, OAuth2ClientMapperConfig.CustomOAuth2ClientMapperConfig custom) {
if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) { if (!StringUtils.isEmpty(custom.getUsername()) && !StringUtils.isEmpty(custom.getPassword())) {
restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword()); restTemplateBuilder = restTemplateBuilder.basicAuthentication(custom.getUsername(), custom.getPassword());
} }

8
application/src/main/resources/thingsboard.yml

@ -126,8 +126,8 @@ security:
firstNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_FIRST_NAME_ATTRIBUTE_KEY:}" firstNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_FIRST_NAME_ATTRIBUTE_KEY:}"
lastNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_LAST_NAME_ATTRIBUTE_KEY:}" lastNameAttributeKey: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_LAST_NAME_ATTRIBUTE_KEY:}"
tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}" # domain, email or custom tenantNameStrategy: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY:domain}" # domain, email or custom
tenantNameStrategyPattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_STRATEGY_PATTERN:}" tenantNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_TENANT_NAME_PATTERN:}" # %{attribute_key} as placeholder for attributes value by key
customerNameStrategyPattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_STRATEGY_PATTERN:}" customerNamePattern: "${SECURITY_OAUTH2_DEFAULT_MAPPER_BASIC_CUSTOMER_NAME_PATTERN:}" # %{attribute_key} as placeholder for attributes value by key
custom: custom:
url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}" url: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_URL:}"
username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}" username: "${SECURITY_OAUTH2_DEFAULT_MAPPER_CUSTOM_USERNAME:}"
@ -637,7 +637,7 @@ queue:
pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_CORE_PACK_PROCESSING_TIMEOUT_MS:60000}"
stats: stats:
enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}" enabled: "${TB_QUEUE_CORE_STATS_ENABLED:true}"
print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:10000}" print-interval-ms: "${TB_QUEUE_CORE_STATS_PRINT_INTERVAL_MS:60000}"
js: js:
# JS Eval request topic # JS Eval request topic
request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}" request_topic: "${REMOTE_JS_EVAL_REQUEST_TOPIC:js_eval.requests}"
@ -657,7 +657,7 @@ queue:
pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}" pack-processing-timeout: "${TB_QUEUE_RULE_ENGINE_PACK_PROCESSING_TIMEOUT_MS:60000}"
stats: stats:
enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}" enabled: "${TB_QUEUE_RULE_ENGINE_STATS_ENABLED:true}"
print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:10000}" print-interval-ms: "${TB_QUEUE_RULE_ENGINE_STATS_PRINT_INTERVAL_MS:60000}"
queues: queues:
- name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}" - name: "${TB_QUEUE_RE_MAIN_QUEUE_NAME:Main}"
topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}" topic: "${TB_QUEUE_RE_MAIN_TOPIC:tb_rule_engine.main}"

12
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.gen.MsgProtos; import org.thingsboard.server.common.msg.gen.MsgProtos;
import org.thingsboard.server.common.msg.queue.TbMsgCallback; import org.thingsboard.server.common.msg.queue.TbMsgCallback;
import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.util.UUID; import java.util.UUID;
@ -47,7 +48,7 @@ public final class TbMsg implements Serializable {
private final RuleChainId ruleChainId; private final RuleChainId ruleChainId;
private final RuleNodeId ruleNodeId; private final RuleNodeId ruleNodeId;
//This field is not serialized because we use queues and there is no need to do it //This field is not serialized because we use queues and there is no need to do it
private final TbMsgCallback callback; transient private final TbMsgCallback callback;
public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) { public static TbMsg newMsg(String type, EntityId originator, TbMsgMetaData metaData, String data) {
return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, TbMsgCallback.EMPTY); return new TbMsg(UUID.randomUUID(), type, originator, metaData.copy(), TbMsgDataType.JSON, data, null, null, TbMsgCallback.EMPTY);
@ -156,4 +157,13 @@ public final class TbMsg implements Serializable {
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) { public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(this.id, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, callback); return new TbMsg(this.id, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, callback);
} }
public TbMsgCallback getCallback() {
//May be null in case of deserialization;
if (callback != null) {
return callback;
} else {
return TbMsgCallback.EMPTY;
}
}
} }

12
common/message/src/main/java/org/thingsboard/server/common/msg/queue/RuleNodeException.java

@ -36,9 +36,15 @@ public class RuleNodeException extends RuleEngineException {
public RuleNodeException(String message, String ruleChainName, RuleNode ruleNode) { public RuleNodeException(String message, String ruleChainName, RuleNode ruleNode) {
super(message); super(message);
this.ruleChainName = ruleChainName; this.ruleChainName = ruleChainName;
this.ruleNodeName = ruleNode.getName(); if (ruleNode != null) {
this.ruleChainId = ruleNode.getRuleChainId(); this.ruleNodeName = ruleNode.getName();
this.ruleNodeId = ruleNode.getId(); this.ruleChainId = ruleNode.getRuleChainId();
this.ruleNodeId = ruleNode.getId();
} else {
ruleNodeName = "Unknown";
ruleChainId = new RuleChainId(RuleChainId.NULL_UUID);
ruleNodeId = new RuleNodeId(RuleNodeId.NULL_UUID);
}
} }
public String toJsonString() { public String toJsonString() {

1
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusAdmin.java

@ -67,6 +67,7 @@ public class TbServiceBusAdmin implements TbQueueAdmin {
try { try {
QueueDescription queueDescription = new QueueDescription(topic); QueueDescription queueDescription = new QueueDescription(topic);
queueDescription.setRequiresDuplicateDetection(false);
setQueueConfigs(queueDescription); setQueueConfigs(queueDescription);
client.createQueue(queueDescription); client.createQueue(queueDescription);

118
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusConsumerTemplate.java

@ -31,9 +31,9 @@ import org.apache.qpid.proton.amqp.transport.SenderSettleMode;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.TbQueueMsgDecoder; import org.thingsboard.server.queue.TbQueueMsgDecoder;
import org.thingsboard.server.queue.common.AbstractTbQueueConsumerTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg; import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.time.Duration; import java.time.Duration;
@ -50,102 +50,72 @@ import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@Slf4j @Slf4j
public class TbServiceBusConsumerTemplate<T extends TbQueueMsg> implements TbQueueConsumer<T> { public class TbServiceBusConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQueueConsumerTemplate<MessageWithDeliveryTag, T> {
private final TbQueueAdmin admin; private final TbQueueAdmin admin;
private final String topic;
private final TbQueueMsgDecoder<T> decoder; private final TbQueueMsgDecoder<T> decoder;
private final TbServiceBusSettings serviceBusSettings; private final TbServiceBusSettings serviceBusSettings;
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private Set<CoreMessageReceiver> receivers; private Set<CoreMessageReceiver> receivers;
private volatile Set<TopicPartitionInfo> partitions; private final Map<CoreMessageReceiver, Collection<MessageWithDeliveryTag>> pendingMessages = new ConcurrentHashMap<>();
private volatile boolean subscribed;
private volatile boolean stopped = false;
private Map<CoreMessageReceiver, Collection<MessageWithDeliveryTag>> pendingMessages = new ConcurrentHashMap<>();
private volatile int messagesPerQueue; private volatile int messagesPerQueue;
public TbServiceBusConsumerTemplate(TbQueueAdmin admin, TbServiceBusSettings serviceBusSettings, String topic, TbQueueMsgDecoder<T> decoder) { public TbServiceBusConsumerTemplate(TbQueueAdmin admin, TbServiceBusSettings serviceBusSettings, String topic, TbQueueMsgDecoder<T> decoder) {
super(topic);
this.admin = admin; this.admin = admin;
this.decoder = decoder; this.decoder = decoder;
this.topic = topic;
this.serviceBusSettings = serviceBusSettings; this.serviceBusSettings = serviceBusSettings;
} }
@Override @Override
public String getTopic() { protected List<MessageWithDeliveryTag> doPoll(long durationInMillis) {
return topic; List<CompletableFuture<Collection<MessageWithDeliveryTag>>> messageFutures =
receivers.stream()
.map(receiver -> receiver
.receiveAsync(messagesPerQueue, Duration.ofMillis(durationInMillis))
.whenComplete((messages, err) -> {
if (!CollectionUtils.isEmpty(messages)) {
pendingMessages.put(receiver, messages);
} else if (err != null) {
log.error("Failed to receive messages.", err);
}
}))
.collect(Collectors.toList());
try {
return fromList(messageFutures)
.get()
.stream()
.flatMap(messages -> CollectionUtils.isEmpty(messages) ? Stream.empty() : messages.stream())
.collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
if (stopped) {
log.info("[{}] Service Bus consumer is stopped.", getTopic());
} else {
log.error("Failed to receive messages", e);
}
return Collections.emptyList();
}
} }
@Override @Override
public void subscribe() { protected void doSubscribe(List<String> topicNames) {
partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true)); createReceivers();
subscribed = false; messagesPerQueue = receivers.size() / partitions.size();
} }
@Override @Override
public void subscribe(Set<TopicPartitionInfo> partitions) { protected void doCommit() {
this.partitions = partitions; pendingMessages.forEach((receiver, msgs) ->
subscribed = false; msgs.forEach(msg -> receiver.completeMessageAsync(msg.getDeliveryTag(), TransactionContext.NULL_TXN)));
pendingMessages.clear();
} }
@Override @Override
public void unsubscribe() { protected void doUnsubscribe() {
stopped = true;
receivers.forEach(CoreMessageReceiver::closeAsync); receivers.forEach(CoreMessageReceiver::closeAsync);
} }
@Override
public List<T> poll(long durationInMillis) {
if (!subscribed && partitions == null) {
try {
Thread.sleep(durationInMillis);
} catch (InterruptedException e) {
log.debug("Failed to await subscription", e);
}
} else {
if (!subscribed) {
createReceivers();
messagesPerQueue = receivers.size() / partitions.size();
subscribed = true;
}
List<CompletableFuture<Collection<MessageWithDeliveryTag>>> messageFutures =
receivers.stream()
.map(receiver -> receiver
.receiveAsync(messagesPerQueue, Duration.ofMillis(durationInMillis))
.whenComplete((messages, err) -> {
if (!CollectionUtils.isEmpty(messages)) {
pendingMessages.put(receiver, messages);
} else if (err != null) {
log.error("Failed to receive messages.", err);
}
}))
.collect(Collectors.toList());
try {
return fromList(messageFutures)
.get()
.stream()
.flatMap(messages -> CollectionUtils.isEmpty(messages) ? Stream.empty() : messages.stream())
.map(message -> {
try {
return decode(message);
} catch (InvalidProtocolBufferException e) {
log.error("Failed to parse message.", e);
throw new RuntimeException("Failed to parse message.", e);
}
}).collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
if (stopped) {
log.info("[{}] Service Bus consumer is stopped.", topic);
} else {
log.error("Failed to receive messages", e);
}
}
}
return Collections.emptyList();
}
private void createReceivers() { private void createReceivers() {
List<CompletableFuture<CoreMessageReceiver>> receiverFutures = partitions.stream() List<CompletableFuture<CoreMessageReceiver>> receiverFutures = partitions.stream()
.map(TopicPartitionInfo::getFullTopicName) .map(TopicPartitionInfo::getFullTopicName)
@ -167,7 +137,7 @@ public class TbServiceBusConsumerTemplate<T extends TbQueueMsg> implements TbQue
receivers = new HashSet<>(fromList(receiverFutures).get()); receivers = new HashSet<>(fromList(receiverFutures).get());
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
if (stopped) { if (stopped) {
log.info("[{}] Service Bus consumer is stopped.", topic); log.info("[{}] Service Bus consumer is stopped.", getTopic());
} else { } else {
log.error("Failed to create receivers", e); log.error("Failed to create receivers", e);
} }
@ -196,13 +166,7 @@ public class TbServiceBusConsumerTemplate<T extends TbQueueMsg> implements TbQue
} }
@Override @Override
public void commit() { protected T decode(MessageWithDeliveryTag data) throws InvalidProtocolBufferException {
pendingMessages.forEach((receiver, msgs) ->
msgs.forEach(msg -> receiver.completeMessageAsync(msg.getDeliveryTag(), TransactionContext.NULL_TXN)));
pendingMessages.clear();
}
private T decode(MessageWithDeliveryTag data) throws InvalidProtocolBufferException {
DefaultTbQueueMsg msg = gson.fromJson(new String(((Data) data.getMessage().getBody()).getValue().getArray()), DefaultTbQueueMsg.class); DefaultTbQueueMsg msg = gson.fromJson(new String(((Data) data.getMessage().getBody()).getValue().getArray()), DefaultTbQueueMsg.class);
return decoder.decode(msg); return decoder.decode(msg);
} }

7
common/queue/src/main/java/org/thingsboard/server/queue/azure/servicebus/TbServiceBusProducerTemplate.java

@ -33,6 +33,7 @@ import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService; import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -42,14 +43,14 @@ public class TbServiceBusProducerTemplate<T extends TbQueueMsg> implements TbQue
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private final TbQueueAdmin admin; private final TbQueueAdmin admin;
private final TbServiceBusSettings serviceBusSettings; private final TbServiceBusSettings serviceBusSettings;
private final Map<String, QueueClient> clients = new HashMap<>(); private final Map<String, QueueClient> clients = new ConcurrentHashMap<>();
private ExecutorService executorService; private final ExecutorService executorService;
public TbServiceBusProducerTemplate(TbQueueAdmin admin, TbServiceBusSettings serviceBusSettings, String defaultTopic) { public TbServiceBusProducerTemplate(TbQueueAdmin admin, TbServiceBusSettings serviceBusSettings, String defaultTopic) {
this.admin = admin; this.admin = admin;
this.defaultTopic = defaultTopic; this.defaultTopic = defaultTopic;
this.serviceBusSettings = serviceBusSettings; this.serviceBusSettings = serviceBusSettings;
executorService = Executors.newSingleThreadExecutor(); executorService = Executors.newCachedThreadPool();
} }
@Override @Override

53
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractParallelTbQueueConsumerTemplate.java

@ -0,0 +1,53 @@
/**
* 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 com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.queue.TbQueueMsg;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
@Slf4j
public abstract class AbstractParallelTbQueueConsumerTemplate<R, T extends TbQueueMsg> extends AbstractTbQueueConsumerTemplate<R, T> {
protected ListeningExecutorService consumerExecutor;
public AbstractParallelTbQueueConsumerTemplate(String topic) {
super(topic);
}
protected void initNewExecutor(int threadPoolSize) {
if (consumerExecutor != null) {
consumerExecutor.shutdown();
try {
consumerExecutor.awaitTermination(1, TimeUnit.MINUTES);
} catch (InterruptedException e) {
log.trace("Interrupted while waiting for consumer executor to stop");
}
}
consumerExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(threadPoolSize));
}
protected void shutdownExecutor() {
if (consumerExecutor != null) {
consumerExecutor.shutdownNow();
}
}
}

152
common/queue/src/main/java/org/thingsboard/server/queue/common/AbstractTbQueueConsumerTemplate.java

@ -0,0 +1,152 @@
/**
* 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 lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
@Slf4j
public abstract class AbstractTbQueueConsumerTemplate<R, T extends TbQueueMsg> implements TbQueueConsumer<T> {
private volatile boolean subscribed;
protected volatile boolean stopped = false;
protected volatile Set<TopicPartitionInfo> partitions;
protected final Lock consumerLock = new ReentrantLock();
@Getter
private final String topic;
public AbstractTbQueueConsumerTemplate(String topic) {
this.topic = topic;
}
@Override
public void subscribe() {
consumerLock.lock();
try {
partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true));
subscribed = false;
} finally {
consumerLock.unlock();
}
}
@Override
public void subscribe(Set<TopicPartitionInfo> partitions) {
consumerLock.lock();
try {
this.partitions = partitions;
subscribed = false;
} finally {
consumerLock.unlock();
}
}
@Override
public List<T> poll(long durationInMillis) {
if (!subscribed && partitions == null) {
try {
Thread.sleep(durationInMillis);
} catch (InterruptedException e) {
log.debug("Failed to await subscription", e);
}
} else {
long pollStartTs = System.currentTimeMillis();
consumerLock.lock();
try {
if (!subscribed) {
List<String> topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
doSubscribe(topicNames);
subscribed = true;
}
List<R> records = doPoll(durationInMillis);
if (!records.isEmpty()) {
List<T> result = new ArrayList<>(records.size());
records.forEach(record -> {
try {
if (record != null) {
result.add(decode(record));
}
} catch (IOException e) {
log.error("Failed decode record: [{}]", record);
throw new RuntimeException("Failed to decode record: ", e);
}
});
return result;
} else {
long pollDuration = System.currentTimeMillis() - pollStartTs;
if (pollDuration < durationInMillis) {
try {
Thread.sleep(durationInMillis - pollDuration);
} catch (InterruptedException e) {
if (!stopped) {
log.error("Failed to wait.", e);
}
}
}
}
} finally {
consumerLock.unlock();
}
}
return Collections.emptyList();
}
@Override
public void commit() {
consumerLock.lock();
try {
doCommit();
} finally {
consumerLock.unlock();
}
}
@Override
public void unsubscribe() {
stopped = true;
consumerLock.lock();
try {
doUnsubscribe();
} finally {
consumerLock.unlock();
}
}
abstract protected List<R> doPoll(long durationInMillis);
abstract protected T decode(R record) throws IOException;
abstract protected void doSubscribe(List<String> topicNames);
abstract protected void doCommit();
abstract protected void doUnsubscribe();
}

107
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -16,16 +16,14 @@
package org.thingsboard.server.queue.kafka; package org.thingsboard.server.queue.kafka;
import lombok.Builder; import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.consumer.ConsumerConfig; import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.consumer.ConsumerRecord; import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.clients.consumer.ConsumerRecords; import org.apache.kafka.clients.consumer.ConsumerRecords;
import org.apache.kafka.clients.consumer.KafkaConsumer; import org.apache.kafka.clients.consumer.KafkaConsumer;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.common.AbstractTbQueueConsumerTemplate;
import java.io.IOException; import java.io.IOException;
import java.time.Duration; import java.time.Duration;
@ -33,26 +31,16 @@ import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;
/** /**
* Created by ashvayka on 24.09.18. * Created by ashvayka on 24.09.18.
*/ */
@Slf4j @Slf4j
public class TbKafkaConsumerTemplate<T extends TbQueueMsg> implements TbQueueConsumer<T> { public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQueueConsumerTemplate<ConsumerRecord<String, byte[]>, T> {
private final TbQueueAdmin admin; private final TbQueueAdmin admin;
private final KafkaConsumer<String, byte[]> consumer; private final KafkaConsumer<String, byte[]> consumer;
private final TbKafkaDecoder<T> decoder; private final TbKafkaDecoder<T> decoder;
private volatile boolean subscribed;
private volatile Set<TopicPartitionInfo> partitions;
private final Lock consumerLock;
@Getter
private final String topic;
@Builder @Builder
private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder<T> decoder, private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder<T> decoder,
@ -60,6 +48,7 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> implements TbQueueCon
boolean autoCommit, int autoCommitIntervalMs, boolean autoCommit, int autoCommitIntervalMs,
int maxPollRecords, int maxPollRecords,
TbQueueAdmin admin) { TbQueueAdmin admin) {
super(topic);
Properties props = settings.toProps(); Properties props = settings.toProps();
props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId); props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId);
if (groupId != null) { if (groupId != null) {
@ -75,94 +64,42 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> implements TbQueueCon
this.admin = admin; this.admin = admin;
this.consumer = new KafkaConsumer<>(props); this.consumer = new KafkaConsumer<>(props);
this.decoder = decoder; this.decoder = decoder;
this.topic = topic;
this.consumerLock = new ReentrantLock();
} }
@Override @Override
public void subscribe() { protected void doSubscribe(List<String> topicNames) {
consumerLock.lock(); topicNames.forEach(admin::createTopicIfNotExists);
try { consumer.subscribe(topicNames);
partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true));
subscribed = false;
} finally {
consumerLock.unlock();
}
} }
@Override @Override
public void subscribe(Set<TopicPartitionInfo> partitions) { protected List<ConsumerRecord<String, byte[]>> doPoll(long durationInMillis) {
consumerLock.lock(); ConsumerRecords<String, byte[]> records = consumer.poll(Duration.ofMillis(durationInMillis));
try { if (records.isEmpty()) {
this.partitions = partitions; return Collections.emptyList();
subscribed = false; } else {
} finally { List<ConsumerRecord<String, byte[]>> recordList = new ArrayList<>(256);
consumerLock.unlock(); records.forEach(recordList::add);
return recordList;
} }
} }
@Override @Override
public List<T> poll(long durationInMillis) { public T decode(ConsumerRecord<String, byte[]> record) throws IOException {
if (!subscribed && partitions == null) { return decoder.decode(new KafkaTbQueueMsg(record));
try {
Thread.sleep(durationInMillis);
} catch (InterruptedException e) {
log.debug("Failed to await subscription", e);
}
} else {
consumerLock.lock();
try {
if (!subscribed) {
List<String> topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
topicNames.forEach(admin::createTopicIfNotExists);
consumer.subscribe(topicNames);
subscribed = true;
}
ConsumerRecords<String, byte[]> records = consumer.poll(Duration.ofMillis(durationInMillis));
if (records.count() > 0) {
List<T> result = new ArrayList<>();
records.forEach(record -> {
try {
result.add(decode(record));
} catch (IOException e) {
log.error("Failed decode record: [{}]", record);
}
});
return result;
}
} finally {
consumerLock.unlock();
}
}
return Collections.emptyList();
} }
@Override @Override
public void commit() { protected void doCommit() {
consumerLock.lock(); consumer.commitAsync();
try {
consumer.commitAsync();
} finally {
consumerLock.unlock();
}
} }
@Override @Override
public void unsubscribe() { protected void doUnsubscribe() {
consumerLock.lock(); if (consumer != null) {
try { consumer.unsubscribe();
if (consumer != null) { consumer.close();
consumer.unsubscribe();
consumer.close();
}
} finally {
consumerLock.unlock();
} }
} }
public T decode(ConsumerRecord<String, byte[]> record) throws IOException {
return decoder.decode(new KafkaTbQueueMsg(record));
}
} }

32
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubAdmin.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.queue.pubsub; package org.thingsboard.server.queue.pubsub;
import com.google.api.gax.rpc.AlreadyExistsException;
import com.google.cloud.pubsub.v1.SubscriptionAdminClient; import com.google.cloud.pubsub.v1.SubscriptionAdminClient;
import com.google.cloud.pubsub.v1.SubscriptionAdminSettings; import com.google.cloud.pubsub.v1.SubscriptionAdminSettings;
import com.google.cloud.pubsub.v1.TopicAdminClient; import com.google.cloud.pubsub.v1.TopicAdminClient;
@ -24,9 +25,9 @@ import com.google.pubsub.v1.ListSubscriptionsRequest;
import com.google.pubsub.v1.ListTopicsRequest; import com.google.pubsub.v1.ListTopicsRequest;
import com.google.pubsub.v1.ProjectName; import com.google.pubsub.v1.ProjectName;
import com.google.pubsub.v1.ProjectSubscriptionName; import com.google.pubsub.v1.ProjectSubscriptionName;
import com.google.pubsub.v1.ProjectTopicName;
import com.google.pubsub.v1.Subscription; import com.google.pubsub.v1.Subscription;
import com.google.pubsub.v1.Topic; import com.google.pubsub.v1.Topic;
import com.google.pubsub.v1.TopicName;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
@ -103,7 +104,10 @@ public class TbPubSubAdmin implements TbQueueAdmin {
@Override @Override
public void createTopicIfNotExists(String partition) { public void createTopicIfNotExists(String partition) {
ProjectTopicName topicName = ProjectTopicName.of(pubSubSettings.getProjectId(), partition); TopicName topicName = TopicName.newBuilder()
.setTopic(partition)
.setProject(pubSubSettings.getProjectId())
.build();
if (topicSet.contains(topicName.toString())) { if (topicSet.contains(topicName.toString())) {
createSubscriptionIfNotExists(partition, topicName); createSubscriptionIfNotExists(partition, topicName);
@ -121,13 +125,18 @@ public class TbPubSubAdmin implements TbQueueAdmin {
} }
} }
topicAdminClient.createTopic(topicName); try {
topicSet.add(topicName.toString()); topicAdminClient.createTopic(topicName);
log.info("Created new topic: [{}]", topicName.toString()); log.info("Created new topic: [{}]", topicName.toString());
} catch (AlreadyExistsException e) {
log.info("[{}] Topic already exist.", topicName.toString());
} finally {
topicSet.add(topicName.toString());
}
createSubscriptionIfNotExists(partition, topicName); createSubscriptionIfNotExists(partition, topicName);
} }
private void createSubscriptionIfNotExists(String partition, ProjectTopicName topicName) { private void createSubscriptionIfNotExists(String partition, TopicName topicName) {
ProjectSubscriptionName subscriptionName = ProjectSubscriptionName subscriptionName =
ProjectSubscriptionName.of(pubSubSettings.getProjectId(), partition); ProjectSubscriptionName.of(pubSubSettings.getProjectId(), partition);
@ -153,9 +162,14 @@ public class TbPubSubAdmin implements TbQueueAdmin {
setAckDeadline(subscriptionBuilder); setAckDeadline(subscriptionBuilder);
setMessageRetention(subscriptionBuilder); setMessageRetention(subscriptionBuilder);
subscriptionAdminClient.createSubscription(subscriptionBuilder.build()); try {
subscriptionSet.add(subscriptionName.toString()); subscriptionAdminClient.createSubscription(subscriptionBuilder.build());
log.info("Created new subscription: [{}]", subscriptionName.toString()); log.info("Created new subscription: [{}]", subscriptionName.toString());
} catch (AlreadyExistsException e) {
log.info("[{}] Subscription already exist.", subscriptionName.toString());
} finally {
subscriptionSet.add(subscriptionName.toString());
}
} }
private void setAckDeadline(Subscription.Builder builder) { private void setAckDeadline(Subscription.Builder builder) {

105
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubConsumerTemplate.java

@ -30,27 +30,25 @@ import com.google.pubsub.v1.PullResponse;
import com.google.pubsub.v1.ReceivedMessage; import com.google.pubsub.v1.ReceivedMessage;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.TbQueueMsgDecoder; import org.thingsboard.server.queue.TbQueueMsgDecoder;
import org.thingsboard.server.queue.common.AbstractParallelTbQueueConsumerTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg; import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
public class TbPubSubConsumerTemplate<T extends TbQueueMsg> implements TbQueueConsumer<T> { public class TbPubSubConsumerTemplate<T extends TbQueueMsg> extends AbstractParallelTbQueueConsumerTemplate<PubsubMessage, T> {
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private final TbQueueAdmin admin; private final TbQueueAdmin admin;
@ -58,23 +56,18 @@ public class TbPubSubConsumerTemplate<T extends TbQueueMsg> implements TbQueueCo
private final TbQueueMsgDecoder<T> decoder; private final TbQueueMsgDecoder<T> decoder;
private final TbPubSubSettings pubSubSettings; private final TbPubSubSettings pubSubSettings;
private volatile boolean subscribed;
private volatile Set<TopicPartitionInfo> partitions;
private volatile Set<String> subscriptionNames; private volatile Set<String> subscriptionNames;
private final List<AcknowledgeRequest> acknowledgeRequests = new CopyOnWriteArrayList<>(); private final List<AcknowledgeRequest> acknowledgeRequests = new CopyOnWriteArrayList<>();
private ExecutorService consumerExecutor;
private final SubscriberStub subscriber; private final SubscriberStub subscriber;
private volatile boolean stopped;
private volatile int messagesPerTopic; private volatile int messagesPerTopic;
public TbPubSubConsumerTemplate(TbQueueAdmin admin, TbPubSubSettings pubSubSettings, String topic, TbQueueMsgDecoder<T> decoder) { public TbPubSubConsumerTemplate(TbQueueAdmin admin, TbPubSubSettings pubSubSettings, String topic, TbQueueMsgDecoder<T> decoder) {
super(topic);
this.admin = admin; this.admin = admin;
this.pubSubSettings = pubSubSettings; this.pubSubSettings = pubSubSettings;
this.topic = topic; this.topic = topic;
this.decoder = decoder; this.decoder = decoder;
try { try {
SubscriberStubSettings subscriberStubSettings = SubscriberStubSettings subscriberStubSettings =
SubscriberStubSettings.newBuilder() SubscriberStubSettings.newBuilder()
@ -84,89 +77,50 @@ public class TbPubSubConsumerTemplate<T extends TbQueueMsg> implements TbQueueCo
.setMaxInboundMessageSize(pubSubSettings.getMaxMsgSize()) .setMaxInboundMessageSize(pubSubSettings.getMaxMsgSize())
.build()) .build())
.build(); .build();
this.subscriber = GrpcSubscriberStub.create(subscriberStubSettings); this.subscriber = GrpcSubscriberStub.create(subscriberStubSettings);
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to create subscriber.", e); log.error("Failed to create subscriber.", e);
throw new RuntimeException("Failed to create subscriber.", e); throw new RuntimeException("Failed to create subscriber.", e);
} }
stopped = false;
} }
@Override @Override
public String getTopic() { protected List<PubsubMessage> doPoll(long durationInMillis) {
return topic; try {
List<ReceivedMessage> messages = receiveMessages();
if (!messages.isEmpty()) {
return messages.stream().map(ReceivedMessage::getMessage).collect(Collectors.toList());
}
} catch (ExecutionException | InterruptedException e) {
if (stopped) {
log.info("[{}] Pub/Sub consumer is stopped.", topic);
} else {
log.error("Failed to receive messages", e);
}
}
return Collections.emptyList();
} }
@Override @Override
public void subscribe() { protected void doSubscribe(List<String> topicNames) {
partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true)); subscriptionNames = new LinkedHashSet<>(topicNames);
subscribed = false; subscriptionNames.forEach(admin::createTopicIfNotExists);
initNewExecutor(subscriptionNames.size() + 1);
messagesPerTopic = pubSubSettings.getMaxMessages() / subscriptionNames.size();
} }
@Override @Override
public void subscribe(Set<TopicPartitionInfo> partitions) { protected void doCommit() {
this.partitions = partitions; acknowledgeRequests.forEach(subscriber.acknowledgeCallable()::futureCall);
subscribed = false; acknowledgeRequests.clear();
} }
@Override @Override
public void unsubscribe() { protected void doUnsubscribe() {
stopped = true;
if (consumerExecutor != null) {
consumerExecutor.shutdownNow();
}
if (subscriber != null) { if (subscriber != null) {
subscriber.close(); subscriber.close();
} }
} shutdownExecutor();
@Override
public List<T> poll(long durationInMillis) {
if (!subscribed && partitions == null) {
try {
Thread.sleep(durationInMillis);
} catch (InterruptedException e) {
log.debug("Failed to await subscription", e);
}
} else {
if (!subscribed) {
subscriptionNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toSet());
subscriptionNames.forEach(admin::createTopicIfNotExists);
consumerExecutor = Executors.newFixedThreadPool(subscriptionNames.size());
messagesPerTopic = pubSubSettings.getMaxMessages() / subscriptionNames.size();
subscribed = true;
}
List<ReceivedMessage> messages;
try {
messages = receiveMessages();
if (!messages.isEmpty()) {
List<T> result = new ArrayList<>();
messages.forEach(msg -> {
try {
result.add(decode(msg.getMessage()));
} catch (InvalidProtocolBufferException e) {
log.error("Failed decode record: [{}]", msg);
}
});
return result;
}
} catch (ExecutionException | InterruptedException e) {
if (stopped) {
log.info("[{}] Pub/Sub consumer is stopped.", topic);
} else {
log.error("Failed to receive messages", e);
}
}
}
return Collections.emptyList();
}
@Override
public void commit() {
acknowledgeRequests.forEach(subscriber.acknowledgeCallable()::futureCall);
acknowledgeRequests.clear();
} }
private List<ReceivedMessage> receiveMessages() throws ExecutionException, InterruptedException { private List<ReceivedMessage> receiveMessages() throws ExecutionException, InterruptedException {
@ -175,7 +129,7 @@ public class TbPubSubConsumerTemplate<T extends TbQueueMsg> implements TbQueueCo
PullRequest pullRequest = PullRequest pullRequest =
PullRequest.newBuilder() PullRequest.newBuilder()
.setMaxMessages(messagesPerTopic) .setMaxMessages(messagesPerTopic)
.setReturnImmediately(false) // return immediately if messages are not available // .setReturnImmediately(false) // return immediately if messages are not available
.setSubscription(subscriptionName) .setSubscription(subscriptionName)
.build(); .build();
@ -211,6 +165,7 @@ public class TbPubSubConsumerTemplate<T extends TbQueueMsg> implements TbQueueCo
return transform.get(); return transform.get();
} }
@Override
public T decode(PubsubMessage message) throws InvalidProtocolBufferException { public T decode(PubsubMessage message) throws InvalidProtocolBufferException {
DefaultTbQueueMsg msg = gson.fromJson(message.getData().toStringUtf8(), DefaultTbQueueMsg.class); DefaultTbQueueMsg msg = gson.fromJson(message.getData().toStringUtf8(), DefaultTbQueueMsg.class);
return decoder.decode(msg); return decoder.decode(msg);

6
common/queue/src/main/java/org/thingsboard/server/queue/pubsub/TbPubSubProducerTemplate.java

@ -49,7 +49,7 @@ public class TbPubSubProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
private final Map<String, Publisher> publisherMap = new ConcurrentHashMap<>(); private final Map<String, Publisher> publisherMap = new ConcurrentHashMap<>();
private ExecutorService pubExecutor = Executors.newCachedThreadPool(); private final ExecutorService pubExecutor = Executors.newCachedThreadPool();
public TbPubSubProducerTemplate(TbQueueAdmin admin, TbPubSubSettings pubSubSettings, String defaultTopic) { public TbPubSubProducerTemplate(TbQueueAdmin admin, TbPubSubSettings pubSubSettings, String defaultTopic) {
this.defaultTopic = defaultTopic; this.defaultTopic = defaultTopic;
@ -124,8 +124,8 @@ public class TbPubSubProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
publisherMap.put(topic, publisher); publisherMap.put(topic, publisher);
return publisher; return publisher;
} catch (IOException e) { } catch (IOException e) {
log.error("Failed to create topic [{}].", topic, e); log.error("Failed to create Publisher for the topic [{}].", topic, e);
throw new RuntimeException("Failed to create topic.", e); throw new RuntimeException("Failed to create Publisher for the topic.", e);
} }
} }

2
common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqAdmin.java

@ -27,13 +27,11 @@ import java.util.concurrent.TimeoutException;
@Slf4j @Slf4j
public class TbRabbitMqAdmin implements TbQueueAdmin { public class TbRabbitMqAdmin implements TbQueueAdmin {
private final TbRabbitMqSettings rabbitMqSettings;
private final Channel channel; private final Channel channel;
private final Connection connection; private final Connection connection;
private final Map<String, Object> arguments; private final Map<String, Object> arguments;
public TbRabbitMqAdmin(TbRabbitMqSettings rabbitMqSettings, Map<String, Object> arguments) { public TbRabbitMqAdmin(TbRabbitMqSettings rabbitMqSettings, Map<String, Object> arguments) {
this.rabbitMqSettings = rabbitMqSettings;
this.arguments = arguments; this.arguments = arguments;
try { try {

107
common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqConsumerTemplate.java

@ -23,9 +23,9 @@ import com.rabbitmq.client.GetResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.TbQueueMsgDecoder; import org.thingsboard.server.queue.TbQueueMsgDecoder;
import org.thingsboard.server.queue.common.AbstractTbQueueConsumerTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg; import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.io.IOException; import java.io.IOException;
@ -37,33 +37,26 @@ import java.util.concurrent.TimeoutException;
import java.util.stream.Collectors; import java.util.stream.Collectors;
@Slf4j @Slf4j
public class TbRabbitMqConsumerTemplate<T extends TbQueueMsg> implements TbQueueConsumer<T> { public class TbRabbitMqConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQueueConsumerTemplate<GetResponse, T> {
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private final TbQueueAdmin admin; private final TbQueueAdmin admin;
private final String topic;
private final TbQueueMsgDecoder<T> decoder; private final TbQueueMsgDecoder<T> decoder;
private final TbRabbitMqSettings rabbitMqSettings;
private final Channel channel; private final Channel channel;
private final Connection connection; private final Connection connection;
private volatile Set<TopicPartitionInfo> partitions;
private volatile boolean subscribed;
private volatile Set<String> queues; private volatile Set<String> queues;
private volatile boolean stopped;
public TbRabbitMqConsumerTemplate(TbQueueAdmin admin, TbRabbitMqSettings rabbitMqSettings, String topic, TbQueueMsgDecoder<T> decoder) { public TbRabbitMqConsumerTemplate(TbQueueAdmin admin, TbRabbitMqSettings rabbitMqSettings, String topic, TbQueueMsgDecoder<T> decoder) {
super(topic);
this.admin = admin; this.admin = admin;
this.decoder = decoder; this.decoder = decoder;
this.topic = topic;
this.rabbitMqSettings = rabbitMqSettings;
try { try {
connection = rabbitMqSettings.getConnectionFactory().newConnection(); connection = rabbitMqSettings.getConnectionFactory().newConnection();
} catch (IOException | TimeoutException e) { } catch (IOException | TimeoutException e) {
log.error("Failed to create connection.", e); log.error("Failed to create connection.", e);
throw new RuntimeException("Failed to create connection.", e); throw new RuntimeException("Failed to create connection.", e);
} }
try { try {
channel = connection.createChannel(); channel = connection.createChannel();
} catch (IOException e) { } catch (IOException e) {
@ -74,25 +67,42 @@ public class TbRabbitMqConsumerTemplate<T extends TbQueueMsg> implements TbQueue
} }
@Override @Override
public String getTopic() { protected List<GetResponse> doPoll(long durationInMillis) {
return topic; List<GetResponse> result = queues.stream()
.map(queue -> {
try {
return channel.basicGet(queue, false);
} catch (IOException e) {
log.error("Failed to get messages from queue: [{}]", queue);
throw new RuntimeException("Failed to get messages from queue.", e);
}
}).filter(Objects::nonNull).collect(Collectors.toList());
if (result.size() > 0) {
return result;
} else {
return Collections.emptyList();
}
} }
@Override @Override
public void subscribe() { protected void doSubscribe(List<String> topicNames) {
partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true)); queues = partitions.stream()
subscribed = false; .map(TopicPartitionInfo::getFullTopicName)
.collect(Collectors.toSet());
queues.forEach(admin::createTopicIfNotExists);
} }
@Override @Override
public void subscribe(Set<TopicPartitionInfo> partitions) { protected void doCommit() {
this.partitions = partitions; try {
subscribed = false; channel.basicAck(0, true);
} catch (IOException e) {
log.error("Failed to ack messages.", e);
}
} }
@Override @Override
public void unsubscribe() { protected void doUnsubscribe() {
stopped = true;
if (channel != null) { if (channel != null) {
try { try {
channel.close(); channel.close();
@ -109,63 +119,6 @@ public class TbRabbitMqConsumerTemplate<T extends TbQueueMsg> implements TbQueue
} }
} }
@Override
public List<T> poll(long durationInMillis) {
if (!subscribed && partitions == null) {
try {
Thread.sleep(durationInMillis);
} catch (InterruptedException e) {
log.debug("Failed to await subscription", e);
}
} else {
if (!subscribed) {
queues = partitions.stream()
.map(TopicPartitionInfo::getFullTopicName)
.collect(Collectors.toSet());
queues.forEach(admin::createTopicIfNotExists);
subscribed = true;
}
List<T> result = queues.stream()
.map(queue -> {
try {
return channel.basicGet(queue, false);
} catch (IOException e) {
log.error("Failed to get messages from queue: [{}]", queue);
throw new RuntimeException("Failed to get messages from queue.", e);
}
}).filter(Objects::nonNull).map(message -> {
try {
return decode(message);
} catch (InvalidProtocolBufferException e) {
log.error("Failed to decode message: [{}].", message);
throw new RuntimeException("Failed to decode message.", e);
}
}).collect(Collectors.toList());
if (result.size() > 0) {
return result;
}
}
try {
Thread.sleep(durationInMillis);
} catch (InterruptedException e) {
if (!stopped) {
log.error("Failed to wait.", e);
}
}
return Collections.emptyList();
}
@Override
public void commit() {
try {
channel.basicAck(0, true);
} catch (IOException e) {
log.error("Failed to ack messages.", e);
}
}
public T decode(GetResponse message) throws InvalidProtocolBufferException { public T decode(GetResponse message) throws InvalidProtocolBufferException {
DefaultTbQueueMsg msg = gson.fromJson(new String(message.getBody()), DefaultTbQueueMsg.class); DefaultTbQueueMsg msg = gson.fromJson(new String(message.getBody()), DefaultTbQueueMsg.class);
return decoder.decode(msg); return decoder.decode(msg);

14
common/queue/src/main/java/org/thingsboard/server/queue/rabbitmq/TbRabbitMqProducerTemplate.java

@ -30,6 +30,8 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg; import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.io.IOException; import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
@ -39,10 +41,12 @@ public class TbRabbitMqProducerTemplate<T extends TbQueueMsg> implements TbQueue
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private final TbQueueAdmin admin; private final TbQueueAdmin admin;
private final TbRabbitMqSettings rabbitMqSettings; private final TbRabbitMqSettings rabbitMqSettings;
private ListeningExecutorService producerExecutor; private final ListeningExecutorService producerExecutor;
private final Channel channel; private final Channel channel;
private final Connection connection; private final Connection connection;
private final Set<TopicPartitionInfo> topics = ConcurrentHashMap.newKeySet();
public TbRabbitMqProducerTemplate(TbQueueAdmin admin, TbRabbitMqSettings rabbitMqSettings, String defaultTopic) { public TbRabbitMqProducerTemplate(TbQueueAdmin admin, TbRabbitMqSettings rabbitMqSettings, String defaultTopic) {
this.admin = admin; this.admin = admin;
this.defaultTopic = defaultTopic; this.defaultTopic = defaultTopic;
@ -75,6 +79,7 @@ public class TbRabbitMqProducerTemplate<T extends TbQueueMsg> implements TbQueue
@Override @Override
public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) { public void send(TopicPartitionInfo tpi, T msg, TbQueueCallback callback) {
createTopicIfNotExist(tpi);
AMQP.BasicProperties properties = new AMQP.BasicProperties(); AMQP.BasicProperties properties = new AMQP.BasicProperties();
try { try {
channel.basicPublish(rabbitMqSettings.getExchangeName(), tpi.getFullTopicName(), properties, gson.toJson(new DefaultTbQueueMsg(msg)).getBytes()); channel.basicPublish(rabbitMqSettings.getExchangeName(), tpi.getFullTopicName(), properties, gson.toJson(new DefaultTbQueueMsg(msg)).getBytes());
@ -110,4 +115,11 @@ public class TbRabbitMqProducerTemplate<T extends TbQueueMsg> implements TbQueue
} }
} }
private void createTopicIfNotExist(TopicPartitionInfo tpi) {
if (topics.contains(tpi)) {
return;
}
admin.createTopicIfNotExists(tpi.getFullTopicName());
topics.add(tpi);
}
} }

139
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsConsumerTemplate.java

@ -25,21 +25,17 @@ import com.amazonaws.services.sqs.model.Message;
import com.amazonaws.services.sqs.model.ReceiveMessageRequest; import com.amazonaws.services.sqs.model.ReceiveMessageRequest;
import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.InvalidProtocolBufferException;
import lombok.Data; import lombok.Data;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.thingsboard.server.common.msg.queue.TopicPartitionInfo;
import org.thingsboard.server.queue.TbQueueAdmin; import org.thingsboard.server.queue.TbQueueAdmin;
import org.thingsboard.server.queue.TbQueueConsumer;
import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.TbQueueMsg;
import org.thingsboard.server.queue.TbQueueMsgDecoder; import org.thingsboard.server.queue.TbQueueMsgDecoder;
import org.thingsboard.server.queue.common.AbstractParallelTbQueueConsumerTemplate;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg; import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
@ -47,34 +43,28 @@ import java.util.Objects;
import java.util.Set; import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@Slf4j @Slf4j
public class TbAwsSqsConsumerTemplate<T extends TbQueueMsg> implements TbQueueConsumer<T> { public class TbAwsSqsConsumerTemplate<T extends TbQueueMsg> extends AbstractParallelTbQueueConsumerTemplate<Message, T> {
private static final int MAX_NUM_MSGS = 10; private static final int MAX_NUM_MSGS = 10;
private final Gson gson = new Gson(); private final Gson gson = new Gson();
private final TbQueueAdmin admin; private final TbQueueAdmin admin;
private final AmazonSQS sqsClient; private final AmazonSQS sqsClient;
private final String topic;
private final TbQueueMsgDecoder<T> decoder; private final TbQueueMsgDecoder<T> decoder;
private final TbAwsSqsSettings sqsSettings; private final TbAwsSqsSettings sqsSettings;
private final List<AwsSqsMsgWrapper> pendingMessages = new CopyOnWriteArrayList<>(); private final List<AwsSqsMsgWrapper> pendingMessages = new CopyOnWriteArrayList<>();
private volatile Set<String> queueUrls; private volatile Set<String> queueUrls;
private volatile Set<TopicPartitionInfo> partitions;
private ListeningExecutorService consumerExecutor;
private volatile boolean subscribed;
private volatile boolean stopped = false;
public TbAwsSqsConsumerTemplate(TbQueueAdmin admin, TbAwsSqsSettings sqsSettings, String topic, TbQueueMsgDecoder<T> decoder) { public TbAwsSqsConsumerTemplate(TbQueueAdmin admin, TbAwsSqsSettings sqsSettings, String topic, TbQueueMsgDecoder<T> decoder) {
super(topic);
this.admin = admin; this.admin = admin;
this.decoder = decoder; this.decoder = decoder;
this.topic = topic;
this.sqsSettings = sqsSettings; this.sqsSettings = sqsSettings;
AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey()); AWSCredentials awsCredentials = new BasicAWSCredentials(sqsSettings.getAccessKeyId(), sqsSettings.getSecretAccessKey());
@ -87,81 +77,60 @@ public class TbAwsSqsConsumerTemplate<T extends TbQueueMsg> implements TbQueueCo
} }
@Override @Override
public String getTopic() { protected void doSubscribe(List<String> topicNames) {
return topic; queueUrls = topicNames.stream().map(this::getQueueUrl).collect(Collectors.toSet());
initNewExecutor(queueUrls.size() * sqsSettings.getThreadsPerTopic() + 1);
} }
@Override @Override
public void subscribe() { protected List<Message> doPoll(long durationInMillis) {
partitions = Collections.singleton(new TopicPartitionInfo(topic, null, null, true)); int duration = (int) TimeUnit.MILLISECONDS.toSeconds(durationInMillis);
subscribed = false; List<ListenableFuture<List<Message>>> futureList = queueUrls
.stream()
.map(url -> poll(url, duration))
.collect(Collectors.toList());
ListenableFuture<List<List<Message>>> futureResult = Futures.allAsList(futureList);
try {
return futureResult.get().stream()
.flatMap(List::stream)
.filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
if (stopped) {
log.info("[{}] Aws SQS consumer is stopped.", getTopic());
} else {
log.error("Failed to pool messages.", e);
}
return Collections.emptyList();
}
} }
@Override @Override
public void subscribe(Set<TopicPartitionInfo> partitions) { public T decode(Message message) throws InvalidProtocolBufferException {
this.partitions = partitions; DefaultTbQueueMsg msg = gson.fromJson(message.getBody(), DefaultTbQueueMsg.class);
subscribed = false; return decoder.decode(msg);
} }
@Override @Override
public void unsubscribe() { protected void doCommit() {
stopped = true; pendingMessages.forEach(msg ->
consumerExecutor.submit(() -> {
if (sqsClient != null) { List<DeleteMessageBatchRequestEntry> entries = msg.getMessages()
sqsClient.shutdown(); .stream()
} .map(message -> new DeleteMessageBatchRequestEntry(message.getMessageId(), message.getReceiptHandle()))
if (consumerExecutor != null) { .collect(Collectors.toList());
consumerExecutor.shutdownNow(); sqsClient.deleteMessageBatch(msg.getUrl(), entries);
} }));
pendingMessages.clear();
} }
@Override @Override
public List<T> poll(long durationInMillis) { protected void doUnsubscribe() {
if (!subscribed && partitions == null) { stopped = true;
try { if (sqsClient != null) {
Thread.sleep(durationInMillis); sqsClient.shutdown();
} catch (InterruptedException e) {
log.debug("Failed to await subscription", e);
}
} else {
if (!subscribed) {
List<String> topicNames = partitions.stream().map(TopicPartitionInfo::getFullTopicName).collect(Collectors.toList());
queueUrls = topicNames.stream().map(this::getQueueUrl).collect(Collectors.toSet());
consumerExecutor = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(queueUrls.size() * sqsSettings.getThreadsPerTopic() + 1));
subscribed = true;
}
if (!pendingMessages.isEmpty()) {
log.warn("Present {} non committed messages.", pendingMessages.size());
return Collections.emptyList();
}
List<ListenableFuture<List<Message>>> futureList = queueUrls
.stream()
.map(url -> poll(url, (int) TimeUnit.MILLISECONDS.toSeconds(durationInMillis)))
.collect(Collectors.toList());
ListenableFuture<List<List<Message>>> futureResult = Futures.allAsList(futureList);
try {
return futureResult.get().stream()
.flatMap(List::stream)
.map(msg -> {
try {
return decode(msg);
} catch (IOException e) {
log.error("Failed to decode message: [{}]", msg);
return null;
}
}).filter(Objects::nonNull)
.collect(Collectors.toList());
} catch (InterruptedException | ExecutionException e) {
if (stopped) {
log.info("[{}] Aws SQS consumer is stopped.", topic);
} else {
log.error("Failed to pool messages.", e);
}
}
} }
return Collections.emptyList(); shutdownExecutor();
} }
private ListenableFuture<List<Message>> poll(String url, int waitTimeSeconds) { private ListenableFuture<List<Message>> poll(String url, int waitTimeSeconds) {
@ -172,7 +141,6 @@ public class TbAwsSqsConsumerTemplate<T extends TbQueueMsg> implements TbQueueCo
ReceiveMessageRequest request = new ReceiveMessageRequest(); ReceiveMessageRequest request = new ReceiveMessageRequest();
request request
.withWaitTimeSeconds(waitTimeSeconds) .withWaitTimeSeconds(waitTimeSeconds)
.withMessageAttributeNames("headers")
.withQueueUrl(url) .withQueueUrl(url)
.withMaxNumberOfMessages(MAX_NUM_MSGS); .withMaxNumberOfMessages(MAX_NUM_MSGS);
return sqsClient.receiveMessage(request).getMessages(); return sqsClient.receiveMessage(request).getMessages();
@ -194,25 +162,6 @@ public class TbAwsSqsConsumerTemplate<T extends TbQueueMsg> implements TbQueueCo
}, consumerExecutor); }, consumerExecutor);
} }
@Override
public void commit() {
pendingMessages.forEach(msg ->
consumerExecutor.submit(() -> {
List<DeleteMessageBatchRequestEntry> entries = msg.getMessages()
.stream()
.map(message -> new DeleteMessageBatchRequestEntry(message.getMessageId(), message.getReceiptHandle()))
.collect(Collectors.toList());
sqsClient.deleteMessageBatch(msg.getUrl(), entries);
}));
pendingMessages.clear();
}
public T decode(Message message) throws InvalidProtocolBufferException {
DefaultTbQueueMsg msg = gson.fromJson(message.getBody(), DefaultTbQueueMsg.class);
return decoder.decode(msg);
}
@Data @Data
private static class AwsSqsMsgWrapper { private static class AwsSqsMsgWrapper {
private final String url; private final String url;

5
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsProducerTemplate.java

@ -37,6 +37,7 @@ import org.thingsboard.server.queue.TbQueueProducer;
import org.thingsboard.server.queue.common.DefaultTbQueueMsg; import org.thingsboard.server.queue.common.DefaultTbQueueMsg;
import java.util.Map; import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
@ -80,7 +81,9 @@ public class TbAwsSqsProducerTemplate<T extends TbQueueMsg> implements TbQueuePr
sendMsgRequest.withQueueUrl(getQueueUrl(tpi.getFullTopicName())); sendMsgRequest.withQueueUrl(getQueueUrl(tpi.getFullTopicName()));
sendMsgRequest.withMessageBody(gson.toJson(new DefaultTbQueueMsg(msg))); sendMsgRequest.withMessageBody(gson.toJson(new DefaultTbQueueMsg(msg)));
sendMsgRequest.withMessageGroupId(msg.getKey().toString()); sendMsgRequest.withMessageGroupId(tpi.getTopic());
sendMsgRequest.withMessageDeduplicationId(UUID.randomUUID().toString());
ListenableFuture<SendMessageResult> future = producerExecutor.submit(() -> sqsClient.sendMessage(sendMsgRequest)); ListenableFuture<SendMessageResult> future = producerExecutor.submit(() -> sqsClient.sendMessage(sendMsgRequest));
Futures.addCallback(future, new FutureCallback<SendMessageResult>() { Futures.addCallback(future, new FutureCallback<SendMessageResult>() {

1
common/queue/src/main/java/org/thingsboard/server/queue/sqs/TbAwsSqsQueueAttributes.java

@ -55,7 +55,6 @@ public class TbAwsSqsQueueAttributes {
@PostConstruct @PostConstruct
private void init() { private void init() {
defaultAttributes.put(QueueAttributeName.FifoQueue.toString(), "true"); defaultAttributes.put(QueueAttributeName.FifoQueue.toString(), "true");
defaultAttributes.put(QueueAttributeName.ContentBasedDeduplication.toString(), "true");
coreAttributes = getConfigs(coreProperties); coreAttributes = getConfigs(coreProperties);
ruleEngineAttributes = getConfigs(ruleEngineProperties); ruleEngineAttributes = getConfigs(ruleEngineProperties);

23
common/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java

@ -37,6 +37,7 @@ import org.thingsboard.server.gen.transport.TransportProtos;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.util.List; import java.util.List;
import java.util.Optional; import java.util.Optional;
import java.util.Set;
import java.util.UUID; import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentMap;
@ -55,6 +56,8 @@ public class CoapTransportResource extends CoapResource {
private final Field observerField; private final Field observerField;
private final long timeout; private final long timeout;
private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>(); private final ConcurrentMap<String, TransportProtos.SessionInfoProto> tokenToSessionIdMap = new ConcurrentHashMap<>();
private final Set<UUID> rpcSubscriptions = ConcurrentHashMap.newKeySet();
private final Set<UUID> attributeSubscriptions = ConcurrentHashMap.newKeySet();
public CoapTransportResource(CoapTransportContext context, String name) { public CoapTransportResource(CoapTransportContext context, String name) {
super(name); super(name);
@ -149,11 +152,13 @@ public class CoapTransportResource extends CoapResource {
transportService.process(sessionInfo, transportService.process(sessionInfo,
transportContext.getAdaptor().convertToPostAttributes(sessionId, request), transportContext.getAdaptor().convertToPostAttributes(sessionId, request),
new CoapOkCallback(exchange)); new CoapOkCallback(exchange));
reportActivity(sessionId, sessionInfo);
break; break;
case POST_TELEMETRY_REQUEST: case POST_TELEMETRY_REQUEST:
transportService.process(sessionInfo, transportService.process(sessionInfo,
transportContext.getAdaptor().convertToPostTelemetry(sessionId, request), transportContext.getAdaptor().convertToPostTelemetry(sessionId, request),
new CoapOkCallback(exchange)); new CoapOkCallback(exchange));
reportActivity(sessionId, sessionInfo);
break; break;
case CLAIM_REQUEST: case CLAIM_REQUEST:
transportService.process(sessionInfo, transportService.process(sessionInfo,
@ -161,6 +166,7 @@ public class CoapTransportResource extends CoapResource {
new CoapOkCallback(exchange)); new CoapOkCallback(exchange));
break; break;
case SUBSCRIBE_ATTRIBUTES_REQUEST: case SUBSCRIBE_ATTRIBUTES_REQUEST:
attributeSubscriptions.add(sessionId);
advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced),
registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); registerAsyncCoapSession(exchange, request, sessionInfo, sessionId)));
transportService.process(sessionInfo, transportService.process(sessionInfo,
@ -168,6 +174,7 @@ public class CoapTransportResource extends CoapResource {
new CoapNoOpCallback(exchange)); new CoapNoOpCallback(exchange));
break; break;
case UNSUBSCRIBE_ATTRIBUTES_REQUEST: case UNSUBSCRIBE_ATTRIBUTES_REQUEST:
attributeSubscriptions.remove(sessionId);
TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(request); TransportProtos.SessionInfoProto attrSession = lookupAsyncSessionInfo(request);
if (attrSession != null) { if (attrSession != null) {
transportService.process(attrSession, transportService.process(attrSession,
@ -177,6 +184,7 @@ public class CoapTransportResource extends CoapResource {
} }
break; break;
case SUBSCRIBE_RPC_COMMANDS_REQUEST: case SUBSCRIBE_RPC_COMMANDS_REQUEST:
rpcSubscriptions.add(sessionId);
advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced), advanced.setObserver(new CoapExchangeObserverProxy((ExchangeObserver) observerField.get(advanced),
registerAsyncCoapSession(exchange, request, sessionInfo, sessionId))); registerAsyncCoapSession(exchange, request, sessionInfo, sessionId)));
transportService.process(sessionInfo, transportService.process(sessionInfo,
@ -184,13 +192,13 @@ public class CoapTransportResource extends CoapResource {
new CoapNoOpCallback(exchange)); new CoapNoOpCallback(exchange));
break; break;
case UNSUBSCRIBE_RPC_COMMANDS_REQUEST: case UNSUBSCRIBE_RPC_COMMANDS_REQUEST:
rpcSubscriptions.remove(sessionId);
TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(request); TransportProtos.SessionInfoProto rpcSession = lookupAsyncSessionInfo(request);
if (rpcSession != null) { if (rpcSession != null) {
transportService.process(rpcSession, transportService.process(rpcSession,
TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(), TransportProtos.SubscribeToRPCMsg.newBuilder().setUnsubscribe(true).build(),
new CoapOkCallback(exchange)); new CoapOkCallback(exchange));
transportService.process(sessionInfo, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); closeAndDeregister(sessionInfo);
transportService.deregisterSession(rpcSession);
} }
break; break;
case TO_DEVICE_RPC_RESPONSE: case TO_DEVICE_RPC_RESPONSE:
@ -221,6 +229,14 @@ public class CoapTransportResource extends CoapResource {
})); }));
} }
private void reportActivity(UUID sessionId, TransportProtos.SessionInfoProto sessionInfo) {
transportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder()
.setAttributeSubscription(attributeSubscriptions.contains(sessionId))
.setRpcSubscription(rpcSubscriptions.contains(sessionId))
.setLastActivityTime(System.currentTimeMillis())
.build(), TransportServiceCallback.EMPTY);
}
private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) { private TransportProtos.SessionInfoProto lookupAsyncSessionInfo(Request request) {
String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString(); String token = request.getSource().getHostAddress() + ":" + request.getSourcePort() + ":" + request.getTokenString();
return tokenToSessionIdMap.remove(token); return tokenToSessionIdMap.remove(token);
@ -438,6 +454,9 @@ public class CoapTransportResource extends CoapResource {
private void closeAndDeregister(TransportProtos.SessionInfoProto session) { private void closeAndDeregister(TransportProtos.SessionInfoProto session) {
transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null); transportService.process(session, getSessionEventMsg(TransportProtos.SessionEvent.CLOSED), null);
transportService.deregisterSession(session); transportService.deregisterSession(session);
UUID sessionId = new UUID(session.getSessionIdMSB(), session.getSessionIdLSB());
rpcSubscriptions.remove(sessionId);
attributeSubscriptions.remove(sessionId);
} }
} }

13
common/transport/http/src/main/java/org/thingsboard/server/transport/http/DeviceApiController.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.transport.TransportContext;
import org.thingsboard.server.common.transport.TransportService; import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.TransportServiceCallback; import org.thingsboard.server.common.transport.TransportServiceCallback;
import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.adaptor.JsonConverter;
import org.thingsboard.server.gen.transport.TransportProtos;
import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg; import org.thingsboard.server.gen.transport.TransportProtos.AttributeUpdateNotificationMsg;
import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto; import org.thingsboard.server.gen.transport.TransportProtos.DeviceInfoProto;
import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg; import org.thingsboard.server.gen.transport.TransportProtos.GetAttributeRequestMsg;
@ -102,6 +103,7 @@ public class DeviceApiController {
TransportService transportService = transportContext.getTransportService(); TransportService transportService = transportContext.getTransportService();
transportService.process(sessionInfo, JsonConverter.convertToAttributesProto(new JsonParser().parse(json)), transportService.process(sessionInfo, JsonConverter.convertToAttributesProto(new JsonParser().parse(json)),
new HttpOkCallback(responseWriter)); new HttpOkCallback(responseWriter));
reportActivity(sessionInfo);
})); }));
return responseWriter; return responseWriter;
} }
@ -115,6 +117,7 @@ public class DeviceApiController {
TransportService transportService = transportContext.getTransportService(); TransportService transportService = transportContext.getTransportService();
transportService.process(sessionInfo, JsonConverter.convertToTelemetryProto(new JsonParser().parse(json)), transportService.process(sessionInfo, JsonConverter.convertToTelemetryProto(new JsonParser().parse(json)),
new HttpOkCallback(responseWriter)); new HttpOkCallback(responseWriter));
reportActivity(sessionInfo);
})); }));
return responseWriter; return responseWriter;
} }
@ -274,7 +277,6 @@ public class DeviceApiController {
} }
} }
private static class HttpSessionListener implements SessionMsgListener { private static class HttpSessionListener implements SessionMsgListener {
private final DeferredResult<ResponseEntity> responseWriter; private final DeferredResult<ResponseEntity> responseWriter;
@ -308,4 +310,13 @@ public class DeviceApiController {
responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK)); responseWriter.setResult(new ResponseEntity<>(JsonConverter.toJson(msg).toString(), HttpStatus.OK));
} }
} }
private void reportActivity(SessionInfoProto sessionInfo) {
transportContext.getTransportService().process(sessionInfo, TransportProtos.SubscriptionInfoProto.newBuilder()
.setAttributeSubscription(false)
.setRpcSubscription(false)
.setLastActivityTime(System.currentTimeMillis())
.build(), TransportServiceCallback.EMPTY);
}
} }

13
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/TransportServiceCallback.java

@ -20,7 +20,20 @@ package org.thingsboard.server.common.transport;
*/ */
public interface TransportServiceCallback<T> { public interface TransportServiceCallback<T> {
TransportServiceCallback<Void> EMPTY = new TransportServiceCallback<Void>() {
@Override
public void onSuccess(Void msg) {
}
@Override
public void onError(Throwable e) {
}
};
void onSuccess(T msg); void onSuccess(T msg);
void onError(Throwable e); void onError(Throwable e);
} }

4
dao/src/main/java/org/thingsboard/server/dao/oauth2/OAuth2ClientMapperConfig.java

@ -31,8 +31,8 @@ public class OAuth2ClientMapperConfig {
private String firstNameAttributeKey; private String firstNameAttributeKey;
private String lastNameAttributeKey; private String lastNameAttributeKey;
private String tenantNameStrategy; private String tenantNameStrategy;
private String tenantNameStrategyPattern; private String tenantNamePattern;
private String customerNameStrategyPattern; private String customerNamePattern;
} }
@Data @Data

6
docker/docker-compose.hybrid.yml

@ -44,17 +44,19 @@ services:
- cassandra - cassandra
tb-core2: tb-core2:
env_file: env_file:
- tb-node.cassandra.env - tb-node.hybrid.env
depends_on: depends_on:
- kafka - kafka
- redis - redis
- postgres
- cassandra - cassandra
tb-rule-engine1: tb-rule-engine1:
env_file: env_file:
- tb-node.cassandra.env - tb-node.hybrid.env
depends_on: depends_on:
- kafka - kafka
- redis - redis
- postgres
- cassandra - cassandra
tb-rule-engine2: tb-rule-engine2:
env_file: env_file:

2
docker/tb-js-executor.env

@ -1,4 +1,4 @@
TB_QUEUE_TYPE=kafka
REMOTE_JS_EVAL_REQUEST_TOPIC=js_eval.requests REMOTE_JS_EVAL_REQUEST_TOPIC=js_eval.requests
TB_KAFKA_SERVERS=kafka:9092 TB_KAFKA_SERVERS=kafka:9092
LOGGER_LEVEL=info LOGGER_LEVEL=info

12
msa/js-executor/config/custom-environment-variables.yml

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
service-type: "TB_SERVICE_TYPE" #kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ) queue_type: "TB_QUEUE_TYPE" #kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
request_topic: "REMOTE_JS_EVAL_REQUEST_TOPIC" request_topic: "REMOTE_JS_EVAL_REQUEST_TOPIC"
js: js:
@ -25,18 +25,18 @@ kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers
servers: "TB_KAFKA_SERVERS" servers: "TB_KAFKA_SERVERS"
replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR" replication_factor: "TB_QUEUE_KAFKA_REPLICATION_FACTOR"
topic-properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES" topic_properties: "TB_QUEUE_KAFKA_JE_TOPIC_PROPERTIES"
pubsub: pubsub:
project_id: "TB_QUEUE_PUBSUB_PROJECT_ID" project_id: "TB_QUEUE_PUBSUB_PROJECT_ID"
service_account: "TB_QUEUE_PUBSUB_SERVICE_ACCOUNT" service_account: "TB_QUEUE_PUBSUB_SERVICE_ACCOUNT"
queue-properties: "TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES" queue_properties: "TB_QUEUE_PUBSUB_JE_QUEUE_PROPERTIES"
aws_sqs: aws_sqs:
access_key_id: "TB_QUEUE_AWS_SQS_ACCESS_KEY_ID" access_key_id: "TB_QUEUE_AWS_SQS_ACCESS_KEY_ID"
secret_access_key: "TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY" secret_access_key: "TB_QUEUE_AWS_SQS_SECRET_ACCESS_KEY"
region: "TB_QUEUE_AWS_SQS_REGION" region: "TB_QUEUE_AWS_SQS_REGION"
queue-properties: "TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES" queue_properties: "TB_QUEUE_AWS_SQS_JE_QUEUE_PROPERTIES"
rabbitmq: rabbitmq:
host: "TB_QUEUE_RABBIT_MQ_HOST" host: "TB_QUEUE_RABBIT_MQ_HOST"
@ -44,14 +44,14 @@ rabbitmq:
virtual_host: "TB_QUEUE_RABBIT_MQ_VIRTUAL_HOST" virtual_host: "TB_QUEUE_RABBIT_MQ_VIRTUAL_HOST"
username: "TB_QUEUE_RABBIT_MQ_USERNAME" username: "TB_QUEUE_RABBIT_MQ_USERNAME"
password: "TB_QUEUE_RABBIT_MQ_PASSWORD" password: "TB_QUEUE_RABBIT_MQ_PASSWORD"
queue-properties: "TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES" queue_properties: "TB_QUEUE_RABBIT_MQ_JE_QUEUE_PROPERTIES"
service_bus: service_bus:
namespace_name: "TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME" namespace_name: "TB_QUEUE_SERVICE_BUS_NAMESPACE_NAME"
sas_key_name: "TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME" sas_key_name: "TB_QUEUE_SERVICE_BUS_SAS_KEY_NAME"
sas_key: "TB_QUEUE_SERVICE_BUS_SAS_KEY" sas_key: "TB_QUEUE_SERVICE_BUS_SAS_KEY"
max_messages: "TB_QUEUE_SERVICE_BUS_MAX_MESSAGES" max_messages: "TB_QUEUE_SERVICE_BUS_MAX_MESSAGES"
queue-properties: "TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES" queue_properties: "TB_QUEUE_SERVICE_BUS_JE_QUEUE_PROPERTIES"
logger: logger:
level: "LOGGER_LEVEL" level: "LOGGER_LEVEL"

12
msa/js-executor/config/default.yml

@ -14,7 +14,7 @@
# limitations under the License. # limitations under the License.
# #
service-type: "kafka" queue_type: "kafka"
request_topic: "js_eval.requests" request_topic: "js_eval.requests"
js: js:
@ -25,13 +25,13 @@ kafka:
# Kafka Bootstrap Servers # Kafka Bootstrap Servers
servers: "localhost:9092" servers: "localhost:9092"
replication_factor: "1" replication_factor: "1"
topic-properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600" topic_properties: "retention.ms:604800000;segment.bytes:26214400;retention.bytes:104857600"
pubsub: pubsub:
queue-properties: "ackDeadlineInSec:30;messageRetentionInSec:604800" queue_properties: "ackDeadlineInSec:30;messageRetentionInSec:604800"
aws_sqs: aws_sqs:
queue-properties: "VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800" queue_properties: "VisibilityTimeout:30;MaximumMessageSize:262144;MessageRetentionPeriod:604800"
rabbitmq: rabbitmq:
host: "localhost" host: "localhost"
@ -39,10 +39,10 @@ rabbitmq:
virtual_host: "/" virtual_host: "/"
username: "admin" username: "admin"
password: "password" password: "password"
queue-properties: "x-max-length-bytes:1048576000;x-message-ttl:604800000" queue_properties: "x-max-length-bytes:1048576000;x-message-ttl:604800000"
service_bus: service_bus:
queue-properties: "lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800" queue_properties: "lockDurationInSec:30;maxSizeInMb:1024;messageTimeToLiveInSec:604800"
logger: logger:
level: "info" level: "info"

1320
msa/js-executor/package-lock.json

File diff suppressed because it is too large

1
msa/js-executor/package.json

@ -22,6 +22,7 @@
"azure-sb": "^0.11.1", "azure-sb": "^0.11.1",
"long": "^4.0.0", "long": "^4.0.0",
"uuid-parse": "^1.0.0", "uuid-parse": "^1.0.0",
"uuid-random": "^1.3.0",
"winston": "^3.0.0", "winston": "^3.0.0",
"winston-daily-rotate-file": "^3.2.1" "winston-daily-rotate-file": "^3.2.1"
}, },

38
msa/js-executor/queue/awsSqsTemplate.js

@ -19,6 +19,7 @@
const config = require('config'), const config = require('config'),
JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'), JsInvokeMessageProcessor = require('../api/jsInvokeMessageProcessor'),
logger = require('../config/logger')._logger('awsSqsTemplate'); logger = require('../config/logger')._logger('awsSqsTemplate');
const uuid = require('uuid-random');
const requestTopic = config.get('request_topic'); const requestTopic = config.get('request_topic');
@ -26,10 +27,10 @@ const accessKeyId = config.get('aws_sqs.access_key_id');
const secretAccessKey = config.get('aws_sqs.secret_access_key'); const secretAccessKey = config.get('aws_sqs.secret_access_key');
const region = config.get('aws_sqs.region'); const region = config.get('aws_sqs.region');
const AWS = require('aws-sdk'); const AWS = require('aws-sdk');
const queueProperties = config.get('aws_sqs.queue-properties'); const queueProperties = config.get('aws_sqs.queue_properties');
const poolInterval = config.get('js.response_poll_interval'); const pollInterval = config.get('js.response_poll_interval');
let queueAttributes = {FifoQueue: 'true', ContentBasedDeduplication: 'true'}; let queueAttributes = {FifoQueue: 'true'};
let sqsClient; let sqsClient;
let requestQueueURL; let requestQueueURL;
const queueUrls = new Map(); const queueUrls = new Map();
@ -51,7 +52,12 @@ function AwsSqsProducer() {
queueUrls.set(responseTopic, responseQueueUrl); queueUrls.set(responseTopic, responseQueueUrl);
} }
let params = {MessageBody: msgBody, QueueUrl: responseQueueUrl, MessageGroupId: scriptId}; let params = {
MessageBody: msgBody,
QueueUrl: responseQueueUrl,
MessageGroupId: 'js_eval',
MessageDeduplicationId: uuid()
};
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
sqsClient.sendMessage(params, function (err, data) { sqsClient.sendMessage(params, function (err, data) {
@ -74,11 +80,13 @@ function AwsSqsProducer() {
const queues = await getQueues(); const queues = await getQueues();
queues.forEach(queueUrl => { if (queues) {
const delimiterPosition = queueUrl.lastIndexOf('/'); queues.forEach(queueUrl => {
const queueName = queueUrl.substring(delimiterPosition + 1); const delimiterPosition = queueUrl.lastIndexOf('/');
queueUrls.set(queueName, queueUrl); const queueName = queueUrl.substring(delimiterPosition + 1);
}) queueUrls.set(queueName, queueUrl);
});
}
parseQueueProperties(); parseQueueProperties();
@ -95,6 +103,7 @@ function AwsSqsProducer() {
WaitTimeSeconds: poolInterval / 1000 WaitTimeSeconds: poolInterval / 1000
}; };
while (!stopped) { while (!stopped) {
let pollStartTs = new Date().getTime();
const messages = await new Promise((resolve, reject) => { const messages = await new Promise((resolve, reject) => {
sqsClient.receiveMessage(params, function (err, data) { sqsClient.receiveMessage(params, function (err, data) {
if (err) { if (err) {
@ -127,6 +136,11 @@ function AwsSqsProducer() {
//do nothing //do nothing
} }
}); });
} else {
let pollDuration = new Date().getTime() - pollStartTs;
if (pollDuration < pollInterval) {
await sleep(pollInterval - pollDuration);
}
} }
} }
} catch (e) { } catch (e) {
@ -175,6 +189,12 @@ function parseQueueProperties() {
}); });
} }
function sleep(ms) {
return new Promise((resolve) => {
setTimeout(resolve, ms);
});
}
process.on('exit', () => { process.on('exit', () => {
stopped = true; stopped = true;
logger.info('Aws Sqs client stopped.'); logger.info('Aws Sqs client stopped.');

2
msa/js-executor/queue/kafkaTemplate.js

@ -20,7 +20,7 @@ const config = require('config'),
logger = require('../config/logger')._logger('kafkaTemplate'), logger = require('../config/logger')._logger('kafkaTemplate'),
KafkaJsWinstonLogCreator = require('../config/logger').KafkaJsWinstonLogCreator; KafkaJsWinstonLogCreator = require('../config/logger').KafkaJsWinstonLogCreator;
const replicationFactor = config.get('kafka.replication_factor'); const replicationFactor = config.get('kafka.replication_factor');
const topicProperties = config.get('kafka.topic-properties'); const topicProperties = config.get('kafka.topic_properties');
let kafkaClient; let kafkaClient;
let kafkaAdmin; let kafkaAdmin;

29
msa/js-executor/queue/pubSubTemplate.js

@ -24,7 +24,7 @@ const {PubSub} = require('@google-cloud/pubsub');
const projectId = config.get('pubsub.project_id'); const projectId = config.get('pubsub.project_id');
const credentials = JSON.parse(config.get('pubsub.service_account')); const credentials = JSON.parse(config.get('pubsub.service_account'));
const requestTopic = config.get('request_topic'); const requestTopic = config.get('request_topic');
const queueProperties = config.get('pubsub.queue-properties'); const queueProperties = config.get('pubsub.queue_properties');
let pubSubClient; let pubSubClient;
@ -98,23 +98,32 @@ function PubSubProducer() {
async function createTopic(topic) { async function createTopic(topic) {
if (!topics.includes(topic)) { if (!topics.includes(topic)) {
await pubSubClient.createTopic(topic); try {
await pubSubClient.createTopic(topic);
logger.info('Created new Pub/Sub topic: %s', topic);
} catch (e) {
logger.info('Pub/Sub topic already exists');
}
topics.push(topic); topics.push(topic);
logger.info('Created new Pub/Sub topic: %s', topic);
} }
await createSubscription(topic) await createSubscription(topic)
} }
async function createSubscription(topic) { async function createSubscription(topic) {
if (!subscriptions.includes(topic)) { if (!subscriptions.includes(topic)) {
await pubSubClient.createSubscription(topic, topic, { try {
topic: topic, await pubSubClient.createSubscription(topic, topic, {
subscription: topic, topic: topic,
ackDeadlineSeconds: queueProps['ackDeadlineInSec'], subscription: topic,
messageRetentionDuration: {seconds: queueProps['messageRetentionInSec']} ackDeadlineSeconds: queueProps['ackDeadlineInSec'],
}); messageRetentionDuration: {seconds: queueProps['messageRetentionInSec']}
});
logger.info('Created new Pub/Sub subscription: %s', topic);
} catch (e) {
logger.info('Pub/Sub subscription already exists.');
}
subscriptions.push(topic); subscriptions.push(topic);
logger.info('Created new Pub/Sub subscription: %s', topic);
} }
} }

26
msa/js-executor/queue/rabbitmqTemplate.js

@ -26,23 +26,23 @@ const port = config.get('rabbitmq.port');
const vhost = config.get('rabbitmq.virtual_host'); const vhost = config.get('rabbitmq.virtual_host');
const username = config.get('rabbitmq.username'); const username = config.get('rabbitmq.username');
const password = config.get('rabbitmq.password'); const password = config.get('rabbitmq.password');
const queueProperties = config.get('rabbitmq.queue-properties'); const queueProperties = config.get('rabbitmq.queue_properties');
const poolInterval = config.get('js.response_poll_interval'); const pollInterval = config.get('js.response_poll_interval');
const amqp = require('amqplib/callback_api'); const amqp = require('amqplib/callback_api');
let queueParams = {durable: false, exclusive: false, autoDelete: false}; let queueOptions = {durable: false, exclusive: false, autoDelete: false};
let connection; let connection;
let channel; let channel;
let stopped = false; let stopped = false;
const responseTopics = []; let queues = [];
function RabbitMqProducer() { function RabbitMqProducer() {
this.send = async (responseTopic, scriptId, rawResponse, headers) => { this.send = async (responseTopic, scriptId, rawResponse, headers) => {
if (!responseTopics.includes(responseTopic)) { if (!queues.includes(responseTopic)) {
await createQueue(responseTopic); await createQueue(responseTopic);
responseTopics.push(responseTopic); queues.push(responseTopic);
} }
let data = JSON.stringify( let data = JSON.stringify(
@ -98,6 +98,7 @@ function RabbitMqProducer() {
const messageProcessor = new JsInvokeMessageProcessor(new RabbitMqProducer()); const messageProcessor = new JsInvokeMessageProcessor(new RabbitMqProducer());
while (!stopped) { while (!stopped) {
let pollStartTs = new Date().getTime();
let message = await new Promise((resolve, reject) => { let message = await new Promise((resolve, reject) => {
channel.get(requestTopic, {}, function (err, msg) { channel.get(requestTopic, {}, function (err, msg) {
if (err) { if (err) {
@ -112,7 +113,10 @@ function RabbitMqProducer() {
messageProcessor.onJsInvokeMessage(JSON.parse(message.content.toString('utf8'))); messageProcessor.onJsInvokeMessage(JSON.parse(message.content.toString('utf8')));
channel.ack(message); channel.ack(message);
} else { } else {
await sleep(poolInterval); let pollDuration = new Date().getTime() - pollStartTs;
if (pollDuration < pollInterval) {
await sleep(pollInterval - pollDuration);
}
} }
} }
} catch (e) { } catch (e) {
@ -123,16 +127,18 @@ function RabbitMqProducer() {
})(); })();
function parseQueueProperties() { function parseQueueProperties() {
let args = {};
const props = queueProperties.split(';'); const props = queueProperties.split(';');
props.forEach(p => { props.forEach(p => {
const delimiterPosition = p.indexOf(':'); const delimiterPosition = p.indexOf(':');
queueParams[p.substring(0, delimiterPosition)] = p.substring(delimiterPosition + 1); args[p.substring(0, delimiterPosition)] = +p.substring(delimiterPosition + 1);
}); });
queueOptions['arguments'] = args;
} }
function createQueue(topic) { async function createQueue(topic) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
channel.assertQueue(topic, queueParams, function (err) { channel.assertQueue(topic, queueOptions, function (err) {
if (err) { if (err) {
reject(err); reject(err);
} else { } else {

3
msa/js-executor/queue/serviceBusTemplate.js

@ -26,7 +26,7 @@ const requestTopic = config.get('request_topic');
const namespaceName = config.get('service_bus.namespace_name'); const namespaceName = config.get('service_bus.namespace_name');
const sasKeyName = config.get('service_bus.sas_key_name'); const sasKeyName = config.get('service_bus.sas_key_name');
const sasKey = config.get('service_bus.sas_key'); const sasKey = config.get('service_bus.sas_key');
const queueProperties = config.get('service_bus.queue-properties'); const queueProperties = config.get('service_bus.queue_properties');
let sbClient; let sbClient;
let receiverClient; let receiverClient;
@ -140,6 +140,7 @@ function parseQueueProperties() {
properties[p.substring(0, delimiterPosition)] = p.substring(delimiterPosition + 1); properties[p.substring(0, delimiterPosition)] = p.substring(delimiterPosition + 1);
}); });
queueOptions = { queueOptions = {
DuplicateDetection: 'false',
MaxSizeInMegabytes: properties['maxSizeInMb'], MaxSizeInMegabytes: properties['maxSizeInMb'],
DefaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`, DefaultMessageTimeToLive: `PT${properties['messageTimeToLiveInSec']}S`,
LockDuration: `PT${properties['lockDurationInSec']}S` LockDuration: `PT${properties['lockDurationInSec']}S`

2
msa/js-executor/server.js

@ -16,7 +16,7 @@
const config = require('config'), logger = require('./config/logger')._logger('main'); const config = require('config'), logger = require('./config/logger')._logger('main');
const serviceType = config.get('service-type'); const serviceType = config.get('queue_type');
switch (serviceType) { switch (serviceType) {
case 'kafka': case 'kafka':
logger.info('Starting kafka template.'); logger.info('Starting kafka template.');

38
pom.xml

@ -30,10 +30,10 @@
<properties> <properties>
<main.dir>${basedir}</main.dir> <main.dir>${basedir}</main.dir>
<pkg.user>thingsboard</pkg.user> <pkg.user>thingsboard</pkg.user>
<spring-boot.version>2.2.4.RELEASE</spring-boot.version> <spring-boot.version>2.2.6.RELEASE</spring-boot.version>
<spring-oauth2.version>2.1.2.RELEASE</spring-oauth2.version> <spring-oauth2.version>2.1.2.RELEASE</spring-oauth2.version>
<spring.version>5.2.2.RELEASE</spring.version> <spring.version>5.2.6.RELEASE</spring.version>
<spring-security.version>5.2.2.RELEASE</spring-security.version> <spring-security.version>5.2.3.RELEASE</spring-security.version>
<spring-data-redis.version>2.2.4.RELEASE</spring-data-redis.version> <spring-data-redis.version>2.2.4.RELEASE</spring-data-redis.version>
<jedis.version>3.1.0</jedis.version> <jedis.version>3.1.0</jedis.version>
<jjwt.version>0.7.0</jjwt.version> <jjwt.version>0.7.0</jjwt.version>
@ -62,14 +62,14 @@
<gson.version>2.6.2</gson.version> <gson.version>2.6.2</gson.version>
<velocity.version>1.7</velocity.version> <velocity.version>1.7</velocity.version>
<velocity-tools.version>2.0</velocity-tools.version> <velocity-tools.version>2.0</velocity-tools.version>
<mail.version>1.4.3</mail.version> <mail.version>1.6.2</mail.version>
<curator.version>4.2.0</curator.version> <curator.version>4.2.0</curator.version>
<zookeeper.version>3.5.5</zookeeper.version> <zookeeper.version>3.5.5</zookeeper.version>
<protobuf.version>3.11.4</protobuf.version> <protobuf.version>3.11.4</protobuf.version>
<grpc.version>1.22.1</grpc.version> <grpc.version>1.22.1</grpc.version>
<lombok.version>1.16.18</lombok.version> <lombok.version>1.16.18</lombok.version>
<paho.client.version>1.1.0</paho.client.version> <paho.client.version>1.1.0</paho.client.version>
<netty.version>4.1.45.Final</netty.version> <netty.version>4.1.49.Final</netty.version>
<os-maven-plugin.version>1.5.0</os-maven-plugin.version> <os-maven-plugin.version>1.5.0</os-maven-plugin.version>
<rabbitmq.version>4.8.0</rabbitmq.version> <rabbitmq.version>4.8.0</rabbitmq.version>
<surfire.version>2.19.1</surfire.version> <surfire.version>2.19.1</surfire.version>
@ -96,7 +96,7 @@
<snakeyaml.version>1.25</snakeyaml.version> <snakeyaml.version>1.25</snakeyaml.version>
<struts.version>1.3.10</struts.version> <struts.version>1.3.10</struts.version>
<amazonaws.sqs.version>1.11.747</amazonaws.sqs.version> <amazonaws.sqs.version>1.11.747</amazonaws.sqs.version>
<pubsub.client.version>1.84.0</pubsub.client.version> <pubsub.client.version>1.105.0</pubsub.client.version>
<azure-servicebus.version>3.2.0</azure-servicebus.version> <azure-servicebus.version>3.2.0</azure-servicebus.version>
<passay.version>1.5.0</passay.version> <passay.version>1.5.0</passay.version>
<ua-parser.version>1.4.3</ua-parser.version> <ua-parser.version>1.4.3</ua-parser.version>
@ -471,12 +471,12 @@
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-client</artifactId> <artifactId>spring-security-oauth2-client</artifactId>
<version>${spring.version}</version> <version>${spring-security.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.security</groupId> <groupId>org.springframework.security</groupId>
<artifactId>spring-security-oauth2-jose</artifactId> <artifactId>spring-security-oauth2-jose</artifactId>
<version>${spring.version}</version> <version>${spring-security.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
@ -591,8 +591,8 @@
<version>${rabbitmq.version}</version> <version>${rabbitmq.version}</version>
</dependency> </dependency>
<dependency> <dependency>
<groupId>javax.mail</groupId> <groupId>com.sun.mail</groupId>
<artifactId>mail</artifactId> <artifactId>javax.mail</artifactId>
<version>${mail.version}</version> <version>${mail.version}</version>
</dependency> </dependency>
<dependency> <dependency>
@ -610,6 +610,12 @@
<groupId>org.apache.zookeeper</groupId> <groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId> <artifactId>zookeeper</artifactId>
<version>${zookeeper.version}</version> <version>${zookeeper.version}</version>
<exclusions>
<exclusion>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.jayway.jsonpath</groupId> <groupId>com.jayway.jsonpath</groupId>
@ -692,6 +698,12 @@
<groupId>com.github.fge</groupId> <groupId>com.github.fge</groupId>
<artifactId>json-schema-validator</artifactId> <artifactId>json-schema-validator</artifactId>
<version>${json-schema-validator.version}</version> <version>${json-schema-validator.version}</version>
<exclusions>
<exclusion>
<groupId>javax.mail</groupId>
<artifactId>mailapi</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>com.typesafe.akka</groupId> <groupId>com.typesafe.akka</groupId>
@ -928,12 +940,6 @@
<groupId>com.microsoft.azure</groupId> <groupId>com.microsoft.azure</groupId>
<artifactId>azure-servicebus</artifactId> <artifactId>azure-servicebus</artifactId>
<version>${azure-servicebus.version}</version> <version>${azure-servicebus.version}</version>
<exclusions>
<exclusion>
<groupId>com.microsoft.azure</groupId>
<artifactId>adal4j</artifactId>
</exclusion>
</exclusions>
</dependency> </dependency>
<dependency> <dependency>
<groupId>org.passay</groupId> <groupId>org.passay</groupId>

5
rule-engine/rule-engine-api/pom.xml

@ -93,5 +93,10 @@
<artifactId>spring-data-redis</artifactId> <artifactId>spring-data-redis</artifactId>
<scope>provided</scope> <scope>provided</scope>
</dependency> </dependency>
<dependency>
<groupId>com.sun.mail</groupId>
<artifactId>javax.mail</artifactId>
<scope>provided</scope>
</dependency>
</dependencies> </dependencies>
</project> </project>

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java

@ -81,8 +81,8 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfig
} }
alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK);
return Futures.immediateFuture(new AlarmResult(false, false, true, alarm)); return Futures.immediateFuture(new AlarmResult(false, false, true, alarm));
}, MoreExecutors.directExecutor()); }, ctx.getDbCallbackExecutor());
}, MoreExecutors.directExecutor()); }, ctx.getDbCallbackExecutor());
}, ctx.getDbCallbackExecutor()); }, ctx.getDbCallbackExecutor());
} }
} }

580
ui-ngx/package-lock.json

@ -40,21 +40,21 @@
} }
}, },
"@angular-devkit/build-angular": { "@angular-devkit/build-angular": {
"version": "0.901.3", "version": "0.901.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.901.3.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-angular/-/build-angular-0.901.4.tgz",
"integrity": "sha512-0ivl+rxiFWY/cWpAhflaJfUmTGNRZn3m4gyt0YNFyHh7oJcwKFVpd9lxJAM4t47Xfyj0ICpTL99UBi4aeo3lEg==", "integrity": "sha512-Vod4bc6d38fuvqauIDQxKMe9hKW9m4QNzPKxEc+Dv5Xkv95WaGzkbUVu8M8t4E//sDDMpmcPdYOXSdR27WBi2Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/architect": "0.901.3", "@angular-devkit/architect": "0.901.4",
"@angular-devkit/build-optimizer": "0.901.3", "@angular-devkit/build-optimizer": "0.901.4",
"@angular-devkit/build-webpack": "0.901.3", "@angular-devkit/build-webpack": "0.901.4",
"@angular-devkit/core": "9.1.3", "@angular-devkit/core": "9.1.4",
"@babel/core": "7.9.0", "@babel/core": "7.9.0",
"@babel/generator": "7.9.3", "@babel/generator": "7.9.3",
"@babel/preset-env": "7.9.0", "@babel/preset-env": "7.9.0",
"@babel/template": "7.8.6", "@babel/template": "7.8.6",
"@jsdevtools/coverage-istanbul-loader": "3.0.3", "@jsdevtools/coverage-istanbul-loader": "3.0.3",
"@ngtools/webpack": "9.1.3", "@ngtools/webpack": "9.1.4",
"ajv": "6.12.0", "ajv": "6.12.0",
"autoprefixer": "9.7.4", "autoprefixer": "9.7.4",
"babel-loader": "8.0.6", "babel-loader": "8.0.6",
@ -108,6 +108,29 @@
"worker-plugin": "4.0.2" "worker-plugin": "4.0.2"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/architect": {
"version": "0.901.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.4.tgz",
"integrity": "sha512-w4RMj7eLhUSh70HUy5tW4EXjLQFXk0Lfr9WiSy5gvPGp+zzYxknI+Wn4Xid1wU/WS+4tuMv5nJIaNaH2sABESQ==",
"dev": true,
"requires": {
"@angular-devkit/core": "9.1.4",
"rxjs": "6.5.4"
}
},
"@angular-devkit/core": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.4.tgz",
"integrity": "sha512-OPFQDmT4XabLMSRDgmnzedlOrc83DzQIgLcfoh/UhZ7aJKf/2Vq4l09p/DkMNI36vN5BRL0zDZt7TjvKNgyYgA==",
"dev": true,
"requires": {
"ajv": "6.12.0",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.5.4",
"source-map": "0.7.3"
}
},
"core-js": { "core-js": {
"version": "3.6.4", "version": "3.6.4",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.6.4.tgz",
@ -138,9 +161,9 @@
} }
}, },
"@angular-devkit/build-optimizer": { "@angular-devkit/build-optimizer": {
"version": "0.901.3", "version": "0.901.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.901.3.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-optimizer/-/build-optimizer-0.901.4.tgz",
"integrity": "sha512-fLRiQ1egH1qsmj5GMJ4ZWawtUYLwpIz1hKJ3WwAIFEFJF2+CDWhO1feUaXZRxPf5qwoj8S+8cSWjdcnKLf/mBw==", "integrity": "sha512-iDMlNRwd8ICMBKnycfw55hdnL5kCiqUjQn+aK/4uOMJMz49tiYMNJAaznXX2BFKmYSmbapKjEbzx9yMYRi9Y7w==",
"dev": true, "dev": true,
"requires": { "requires": {
"loader-utils": "2.0.0", "loader-utils": "2.0.0",
@ -159,16 +182,39 @@
} }
}, },
"@angular-devkit/build-webpack": { "@angular-devkit/build-webpack": {
"version": "0.901.3", "version": "0.901.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.901.3.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/build-webpack/-/build-webpack-0.901.4.tgz",
"integrity": "sha512-tZOiVNCrKaEAo7SuyXPpOoaPy/pbX70G8NFtyhKvoGt5dPbgybWTlibACyzOciBI9wUlGSaJiGPZzKTgL4O5OA==", "integrity": "sha512-YBOufI4TGAtIDCS6erFygYJX/fkd8xwI58d+7iFIBmtIJC4/fpGjX6qkHMCBgh8HXAkEPSORBCYQn9O44J1ZXQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/architect": "0.901.3", "@angular-devkit/architect": "0.901.4",
"@angular-devkit/core": "9.1.3", "@angular-devkit/core": "9.1.4",
"rxjs": "6.5.4" "rxjs": "6.5.4"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/architect": {
"version": "0.901.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.4.tgz",
"integrity": "sha512-w4RMj7eLhUSh70HUy5tW4EXjLQFXk0Lfr9WiSy5gvPGp+zzYxknI+Wn4Xid1wU/WS+4tuMv5nJIaNaH2sABESQ==",
"dev": true,
"requires": {
"@angular-devkit/core": "9.1.4",
"rxjs": "6.5.4"
}
},
"@angular-devkit/core": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.4.tgz",
"integrity": "sha512-OPFQDmT4XabLMSRDgmnzedlOrc83DzQIgLcfoh/UhZ7aJKf/2Vq4l09p/DkMNI36vN5BRL0zDZt7TjvKNgyYgA==",
"dev": true,
"requires": {
"ajv": "6.12.0",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.5.4",
"source-map": "0.7.3"
}
},
"rxjs": { "rxjs": {
"version": "6.5.4", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
@ -205,16 +251,29 @@
} }
}, },
"@angular-devkit/schematics": { "@angular-devkit/schematics": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular-devkit/schematics/-/schematics-9.1.4.tgz",
"integrity": "sha512-nNRoQPqoin5jzLKmTabIPLSqVw2Zjnk78XC4AhAM7rSisWMsZ6pw5+BNDITiKfq0LkSXfOs0DoJV/Vr9mLkn/A==", "integrity": "sha512-RAbdnUEZ3JTLmWSBiXT5trsVx8Fi72fxN9CiRaluM09Cytg6BUc1wC5XCO0YPvhI400+3Ro1nwjPXezjg7LXzQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/core": "9.1.3", "@angular-devkit/core": "9.1.4",
"ora": "4.0.3", "ora": "4.0.3",
"rxjs": "6.5.4" "rxjs": "6.5.4"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/core": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.4.tgz",
"integrity": "sha512-OPFQDmT4XabLMSRDgmnzedlOrc83DzQIgLcfoh/UhZ7aJKf/2Vq4l09p/DkMNI36vN5BRL0zDZt7TjvKNgyYgA==",
"dev": true,
"requires": {
"ajv": "6.12.0",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.5.4",
"source-map": "0.7.3"
}
},
"rxjs": { "rxjs": {
"version": "6.5.4", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
@ -227,29 +286,29 @@
} }
}, },
"@angular/animations": { "@angular/animations": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/animations/-/animations-9.1.4.tgz",
"integrity": "sha512-QTQZSnXxr9SCgrN2SZsTMzS/UldrZ65y6Kuqs9yyghNc694eMbabgiPdeegjvcKlDhVPCN2x9Bjfb1vXPOfdvQ==" "integrity": "sha512-gMo/DbCm5BDArladMAeC7/75T2DvhLr4CSUGJt/P/aimTEG2ywoAALs3pzwSSe4qxrHiR0OIksVW3l4km3iXEw=="
}, },
"@angular/cdk": { "@angular/cdk": {
"version": "9.2.1", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.1.tgz", "resolved": "https://registry.npmjs.org/@angular/cdk/-/cdk-9.2.2.tgz",
"integrity": "sha512-aSG1UNPszkSnpNuDCNd7ZgT29oQ8vqHPmoqjvJI0JkEv3i6uEs5tRuhWl3TK39wDNuwdlq0AY47XTa/0Ppb5RQ==", "integrity": "sha512-VNd+KuMN6cBcy4/8OyMxqYaxdjPP6IyCqIVijB2JREkc5Sg4VWmPgx2L3rHt/DzjsVBVRgx35uqOMymDezG3jQ==",
"requires": { "requires": {
"parse5": "^5.0.0" "parse5": "^5.0.0"
} }
}, },
"@angular/cli": { "@angular/cli": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/cli/-/cli-9.1.4.tgz",
"integrity": "sha512-/7yHOuiyMgpcoBuADPrF4Eo9VDysA57fsyrMlOH2WZmKdsjW032StS9EIOue5RNQ7y0DwrtgtnkjbpZ6nYo3Pw==", "integrity": "sha512-H9MqoT4zyIv+Yo3cvRVkzafWGHsqt7jUvtvGwMHIDMTfEX+Q8yiYlDLL6WM3Eb6/hDmLcRGC/GI495sKS1z5qA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/architect": "0.901.3", "@angular-devkit/architect": "0.901.4",
"@angular-devkit/core": "9.1.3", "@angular-devkit/core": "9.1.4",
"@angular-devkit/schematics": "9.1.3", "@angular-devkit/schematics": "9.1.4",
"@schematics/angular": "9.1.3", "@schematics/angular": "9.1.4",
"@schematics/update": "0.901.3", "@schematics/update": "0.901.4",
"@yarnpkg/lockfile": "1.1.0", "@yarnpkg/lockfile": "1.1.0",
"ansi-colors": "4.1.1", "ansi-colors": "4.1.1",
"debug": "4.1.1", "debug": "4.1.1",
@ -267,12 +326,44 @@
"uuid": "7.0.2" "uuid": "7.0.2"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/architect": {
"version": "0.901.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/architect/-/architect-0.901.4.tgz",
"integrity": "sha512-w4RMj7eLhUSh70HUy5tW4EXjLQFXk0Lfr9WiSy5gvPGp+zzYxknI+Wn4Xid1wU/WS+4tuMv5nJIaNaH2sABESQ==",
"dev": true,
"requires": {
"@angular-devkit/core": "9.1.4",
"rxjs": "6.5.4"
}
},
"@angular-devkit/core": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.4.tgz",
"integrity": "sha512-OPFQDmT4XabLMSRDgmnzedlOrc83DzQIgLcfoh/UhZ7aJKf/2Vq4l09p/DkMNI36vN5BRL0zDZt7TjvKNgyYgA==",
"dev": true,
"requires": {
"ajv": "6.12.0",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.5.4",
"source-map": "0.7.3"
}
},
"ansi-colors": { "ansi-colors": {
"version": "4.1.1", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
"integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
"dev": true "dev": true
}, },
"rxjs": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
"integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
},
"semver": { "semver": {
"version": "7.1.3", "version": "7.1.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz", "resolved": "https://registry.npmjs.org/semver/-/semver-7.1.3.tgz",
@ -288,19 +379,19 @@
} }
}, },
"@angular/common": { "@angular/common": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/common/-/common-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/common/-/common-9.1.4.tgz",
"integrity": "sha512-OGrd/x+WoceskeJWZI3ur+KqVoBHn2kmbg57RyJ8Ng/BkdDVYiXzQ/UvvX1WYGH/YzmiiVdlnVq55xkHkzGaXw==" "integrity": "sha512-JvCoCWVbx0tF7l/0WTi24ui/mc2SElyVSNchR4VK/FViARnkvnSBdI/Ef5QWXrsPyKU4PYBtnWWgyxRspH+FBA=="
}, },
"@angular/compiler": { "@angular/compiler": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/compiler/-/compiler-9.1.4.tgz",
"integrity": "sha512-NTqGZUwwdm/RIG1rWPIqQwNyyRcPOmNlv+t1KpydFtycpTut0lAh0shiu57h83Cl2LgaFV4veyiAAtNmxFmqXA==" "integrity": "sha512-B+f3lviFNEJtL9V9exSKYPSz2Ddb6dxgPzQR7GSjGikDo+fKMtC1PjNwgJooS9gavhQx30uwkEEMIPYQbM6nNA=="
}, },
"@angular/compiler-cli": { "@angular/compiler-cli": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/compiler-cli/-/compiler-cli-9.1.4.tgz",
"integrity": "sha512-FqDPmEbJQmzzxqF08CJfqT9XKJFPYdyZuFFStKkJEZ5s1JrjrBlgQSqdF0qp5jWNsjTB87USLHC5zAlzJtMKxQ==", "integrity": "sha512-n3PzqNnPD7s/AF9mv5CnarK0sgfoq4txFncHjJWBSltuTQoz6BDZyjuEdqsSLUvgAZPeLsmohemOzEE38HYHZA==",
"dev": true, "dev": true,
"requires": { "requires": {
"canonical-path": "1.0.0", "canonical-path": "1.0.0",
@ -501,9 +592,9 @@
} }
}, },
"@angular/core": { "@angular/core": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/core/-/core-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/core/-/core-9.1.4.tgz",
"integrity": "sha512-r+GyeCuBw9SUBzpPWhKxzTOkNYotYp3gANSWndihLC9P6W5T8Hfyg8uYcxKy7L42bpx8eXZOXFq91cioVwZtqA==" "integrity": "sha512-ND240vncmVD2KVe/KSQU3d/DxxoRipFg1+jFOFZGt0n0orCBHk/V1fu9iaG1sRyldL0+rCQ+fTI+1N4DTmMnxA=="
}, },
"@angular/flex-layout": { "@angular/flex-layout": {
"version": "9.0.0-beta.29", "version": "9.0.0-beta.29",
@ -511,35 +602,35 @@
"integrity": "sha512-93sxR+kYfYMOdnlWL0Q77FZ428gg8XnBu0YZm6GsCdkw/vLggIT/G1ZAqHlCPIODt6pxmCJ5KXh4ShvniIYDsA==" "integrity": "sha512-93sxR+kYfYMOdnlWL0Q77FZ428gg8XnBu0YZm6GsCdkw/vLggIT/G1ZAqHlCPIODt6pxmCJ5KXh4ShvniIYDsA=="
}, },
"@angular/forms": { "@angular/forms": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/forms/-/forms-9.1.4.tgz",
"integrity": "sha512-t3BexN5EHgCK6q0R04AuyNb6ZUP6sd9fJ+ER98yWws3HXV6FT+J5IB7dVnhelLoU1GTnql4TJSS+LTVradKZ7g==" "integrity": "sha512-Njt+pMLfPBchL0/ayIjJqXL6ZfM4Ccvf7KO1wS1HMzh3QlmfNa0JSgc4pfrbRJAMN9g7V/FYLyKejs1bJZkenA=="
}, },
"@angular/language-service": { "@angular/language-service": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/language-service/-/language-service-9.1.4.tgz",
"integrity": "sha512-mcZDO6C2BpDinMjDeMAdohTefpFxNu/S0JEqewYQIQ8TXtKm77xDPUdNdNb4qD4zYW+c46f7UXNMX/tbYCicQw==", "integrity": "sha512-eyVxxiegdb4ESdFGfkuDN+YfUbOVHRQLjIl6ACFJQDNHzVXzbmuqpyr5hIJANIVady103/7+dqRxxJo1DdIdTQ==",
"dev": true "dev": true
}, },
"@angular/material": { "@angular/material": {
"version": "9.2.1", "version": "9.2.2",
"resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.1.tgz", "resolved": "https://registry.npmjs.org/@angular/material/-/material-9.2.2.tgz",
"integrity": "sha512-nqn/0Eg04DxwnkRGSM1xnmGgtfHYOBcEPbFeTu8c1qAbjFEozd6tpw4y6dQrCCL/JLNIRQPsxsUsVnKeWDF/4Q==" "integrity": "sha512-gdQiMJ6PtW/5fd+0mglHFyzxULDCBGjn9RTET3sUq2rkc9+jBXr4OvnsUyBWSnqqv97XqotVDIx5JgE4/YX/Rw=="
}, },
"@angular/platform-browser": { "@angular/platform-browser": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-9.1.4.tgz",
"integrity": "sha512-ts6b7QBp4+UpuQS0Eb+FOT4MZVPUwWtWH4iIfBILJaG/0Bk4jHZeXuLFcpgeZkwMNCZEt9f0JrhnHfvuLCez5g==" "integrity": "sha512-mBCHfTl+5tQfaUiGlDujP7mFBzovFc54Zi2kcCE8DSdSSVQ2TPBo6hXa6y2cL3hJPFZzQ7mC4ORFrsGADhHn/w=="
}, },
"@angular/platform-browser-dynamic": { "@angular/platform-browser-dynamic": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/platform-browser-dynamic/-/platform-browser-dynamic-9.1.4.tgz",
"integrity": "sha512-9U8PkBflnQtKIZ4t0AqYUaKLg7Kx1BvJ1D/S8FZo/V5kBJK6NkGVu58XddWVc+dtO3V+vCC/VLG7fb2PMf/V5g==" "integrity": "sha512-YtVbnxyS6FU7xNpA6A95tmSfrB8+WC7OH3mbP8M9NaGk0OYz8B/JOe1HByP4JRpEGCvBtXdJ2NSW/MpLIT8SiQ=="
}, },
"@angular/router": { "@angular/router": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@angular/router/-/router-9.1.4.tgz",
"integrity": "sha512-NmOazeJ2WVT5eTyNHXYxBRAHic6i6Ku5DgsGWBgjUIR/Qmwb/i/xpagbA9WQQZ2OrGbp4ICEYgGkikeIrlQLPA==" "integrity": "sha512-yUyjCgG2P2Jh8MvoyC6yirmAtx1Qe7MKLuLvsa9WOB571QNEcNLTYMfAMHUKsQTcE/+o984QyLlneoibgI9wFA=="
}, },
"@auth0/angular-jwt": { "@auth0/angular-jwt": {
"version": "4.0.0", "version": "4.0.0",
@ -559,12 +650,12 @@
} }
}, },
"@babel/compat-data": { "@babel/compat-data": {
"version": "7.9.0", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.9.6.tgz",
"integrity": "sha512-zeFQrr+284Ekvd9e7KAX954LkapWiOmQtsfHirhxqfdlX6MEC32iRE+pqUGlYIBchdevaCwvzxWGSy/YBNI85g==", "integrity": "sha512-5QPTrNen2bm7RBc7dsOmcA5hbrS4O2Vhmk5XOL4zWW/zD/hV0iinpefDlkm+tBBy8kDtFaaeEvmAqt+nURAV2g==",
"dev": true, "dev": true,
"requires": { "requires": {
"browserslist": "^4.9.1", "browserslist": "^4.11.1",
"invariant": "^2.2.4", "invariant": "^2.2.4",
"semver": "^5.5.0" "semver": "^5.5.0"
} }
@ -641,13 +732,13 @@
} }
}, },
"@babel/helper-compilation-targets": { "@babel/helper-compilation-targets": {
"version": "7.8.7", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.8.7.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.9.6.tgz",
"integrity": "sha512-4mWm8DCK2LugIS+p1yArqvG1Pf162upsIsjE7cNBjez+NjliQpVhj20obE520nao0o14DaTnFJv+Fw5a0JpoUw==", "integrity": "sha512-x2Nvu0igO0ejXzx09B/1fGBxY9NXQlBW2kZsSxCJft+KHN8t9XWzIvFxtPHnBOAXpVsdxZKZFbRUC8TsNKajMw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/compat-data": "^7.8.6", "@babel/compat-data": "^7.9.6",
"browserslist": "^4.9.1", "browserslist": "^4.11.1",
"invariant": "^2.2.4", "invariant": "^2.2.4",
"levenary": "^1.1.1", "levenary": "^1.1.1",
"semver": "^5.5.0" "semver": "^5.5.0"
@ -785,15 +876,69 @@
} }
}, },
"@babel/helper-replace-supers": { "@babel/helper-replace-supers": {
"version": "7.8.6", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.8.6.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.9.6.tgz",
"integrity": "sha512-PeMArdA4Sv/Wf4zXwBKPqVj7n9UF/xg6slNRtZW84FM7JpE1CbG8B612FyM4cxrf4fMAMGO0kR7voy1ForHHFA==", "integrity": "sha512-qX+chbxkbArLyCImk3bWV+jB5gTNU/rsze+JlcF6Nf8tVTigPJSI1o1oBow/9Resa1yehUO9lIipsmu9oG4RzA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-member-expression-to-functions": "^7.8.3", "@babel/helper-member-expression-to-functions": "^7.8.3",
"@babel/helper-optimise-call-expression": "^7.8.3", "@babel/helper-optimise-call-expression": "^7.8.3",
"@babel/traverse": "^7.8.6", "@babel/traverse": "^7.9.6",
"@babel/types": "^7.8.6" "@babel/types": "^7.9.6"
},
"dependencies": {
"@babel/generator": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz",
"integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==",
"dev": true,
"requires": {
"@babel/types": "^7.9.6",
"jsesc": "^2.5.1",
"lodash": "^4.17.13",
"source-map": "^0.5.0"
}
},
"@babel/parser": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz",
"integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==",
"dev": true
},
"@babel/traverse": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz",
"integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@babel/generator": "^7.9.6",
"@babel/helper-function-name": "^7.9.5",
"@babel/helper-split-export-declaration": "^7.8.3",
"@babel/parser": "^7.9.6",
"@babel/types": "^7.9.6",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.13"
}
},
"@babel/types": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz",
"integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.9.5",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
} }
}, },
"@babel/helper-simple-access": { "@babel/helper-simple-access": {
@ -834,14 +979,68 @@
} }
}, },
"@babel/helpers": { "@babel/helpers": {
"version": "7.9.2", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.2.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.9.6.tgz",
"integrity": "sha512-JwLvzlXVPjO8eU9c/wF9/zOIN7X6h8DYf7mG4CiFRZRvZNKEF5dQ3H3V+ASkHoIB3mWhatgl5ONhyqHRI6MppA==", "integrity": "sha512-tI4bUbldloLcHWoRUMAj4g1bF313M/o6fBKhIsb3QnGVPwRm9JsNf/gqMkQ7zjqReABiffPV6RWj7hEglID5Iw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/template": "^7.8.3", "@babel/template": "^7.8.3",
"@babel/traverse": "^7.9.0", "@babel/traverse": "^7.9.6",
"@babel/types": "^7.9.0" "@babel/types": "^7.9.6"
},
"dependencies": {
"@babel/generator": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.9.6.tgz",
"integrity": "sha512-+htwWKJbH2bL72HRluF8zumBxzuX0ZZUFl3JLNyoUjM/Ho8wnVpPXM6aUz8cfKDqQ/h7zHqKt4xzJteUosckqQ==",
"dev": true,
"requires": {
"@babel/types": "^7.9.6",
"jsesc": "^2.5.1",
"lodash": "^4.17.13",
"source-map": "^0.5.0"
}
},
"@babel/parser": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.9.6.tgz",
"integrity": "sha512-AoeIEJn8vt+d/6+PXDRPaksYhnlbMIiejioBZvvMQsOjW/JYK6k/0dKnvvP3EhK5GfMBWDPtrxRtegWdAcdq9Q==",
"dev": true
},
"@babel/traverse": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.9.6.tgz",
"integrity": "sha512-b3rAHSjbxy6VEAvlxM8OV/0X4XrG72zoxme6q1MOoe2vd0bEc+TwayhuC1+Dfgqh1QEG+pj7atQqvUprHIccsg==",
"dev": true,
"requires": {
"@babel/code-frame": "^7.8.3",
"@babel/generator": "^7.9.6",
"@babel/helper-function-name": "^7.9.5",
"@babel/helper-split-export-declaration": "^7.8.3",
"@babel/parser": "^7.9.6",
"@babel/types": "^7.9.6",
"debug": "^4.1.0",
"globals": "^11.1.0",
"lodash": "^4.17.13"
}
},
"@babel/types": {
"version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.9.6.tgz",
"integrity": "sha512-qxXzvBO//jO9ZnoasKF1uJzHd2+M6Q2ZPIVfnFps8JJvXy0ZBbwbNOmE6SGIY5XOY6d1Bo5lb9d9RJ8nv3WSeA==",
"dev": true,
"requires": {
"@babel/helper-validator-identifier": "^7.9.5",
"lodash": "^4.17.13",
"to-fast-properties": "^2.0.0"
}
},
"source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
"integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=",
"dev": true
}
} }
}, },
"@babel/highlight": { "@babel/highlight": {
@ -913,9 +1112,9 @@
} }
}, },
"@babel/plugin-proposal-object-rest-spread": { "@babel/plugin-proposal-object-rest-spread": {
"version": "7.9.5", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.5.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.9.6.tgz",
"integrity": "sha512-VP2oXvAf7KCYTthbUHwBlewbl1Iq059f6seJGsxMizaCdgHIeczOr7FBqELhSqfkIl04Fi8okzWzl63UKbQmmg==", "integrity": "sha512-Ga6/fhGqA9Hj+y6whNpPv8psyaK5xzrQwSPsGPloVkvmH+PqW1ixdnfJ9uIO06OjQNYol3PMnfmJ8vfZtkzF+A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-plugin-utils": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3",
@ -1174,38 +1373,38 @@
} }
}, },
"@babel/plugin-transform-modules-amd": { "@babel/plugin-transform-modules-amd": {
"version": "7.9.0", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.9.6.tgz",
"integrity": "sha512-vZgDDF003B14O8zJy0XXLnPH4sg+9X5hFBBGN1V+B2rgrB+J2xIypSN6Rk9imB2hSTHQi5OHLrFWsZab1GMk+Q==", "integrity": "sha512-zoT0kgC3EixAyIAU+9vfaUVKTv9IxBDSabgHoUCBP6FqEJ+iNiN7ip7NBKcYqbfUDfuC2mFCbM7vbu4qJgOnDw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-module-transforms": "^7.9.0", "@babel/helper-module-transforms": "^7.9.0",
"@babel/helper-plugin-utils": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3",
"babel-plugin-dynamic-import-node": "^2.3.0" "babel-plugin-dynamic-import-node": "^2.3.3"
} }
}, },
"@babel/plugin-transform-modules-commonjs": { "@babel/plugin-transform-modules-commonjs": {
"version": "7.9.0", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.9.6.tgz",
"integrity": "sha512-qzlCrLnKqio4SlgJ6FMMLBe4bySNis8DFn1VkGmOcxG9gqEyPIOzeQrA//u0HAKrWpJlpZbZMPB1n/OPa4+n8g==", "integrity": "sha512-7H25fSlLcn+iYimmsNe3uK1at79IE6SKW9q0/QeEHTMC9MdOZ+4bA+T1VFB5fgOqBWoqlifXRzYD0JPdmIrgSQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-module-transforms": "^7.9.0", "@babel/helper-module-transforms": "^7.9.0",
"@babel/helper-plugin-utils": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3",
"@babel/helper-simple-access": "^7.8.3", "@babel/helper-simple-access": "^7.8.3",
"babel-plugin-dynamic-import-node": "^2.3.0" "babel-plugin-dynamic-import-node": "^2.3.3"
} }
}, },
"@babel/plugin-transform-modules-systemjs": { "@babel/plugin-transform-modules-systemjs": {
"version": "7.9.0", "version": "7.9.6",
"resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.0.tgz", "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.9.6.tgz",
"integrity": "sha512-FsiAv/nao/ud2ZWy4wFacoLOm5uxl0ExSQ7ErvP7jpoihLR6Cq90ilOFyX9UXct3rbtKsAiZ9kFt5XGfPe/5SQ==", "integrity": "sha512-NW5XQuW3N2tTHim8e1b7qGy7s0kZ2OH3m5octc49K1SdAKGxYxeIx7hiIz05kS1R2R+hOWcsr1eYwcGhrdHsrg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/helper-hoist-variables": "^7.8.3", "@babel/helper-hoist-variables": "^7.8.3",
"@babel/helper-module-transforms": "^7.9.0", "@babel/helper-module-transforms": "^7.9.0",
"@babel/helper-plugin-utils": "^7.8.3", "@babel/helper-plugin-utils": "^7.8.3",
"babel-plugin-dynamic-import-node": "^2.3.0" "babel-plugin-dynamic-import-node": "^2.3.3"
} }
}, },
"@babel/plugin-transform-modules-umd": { "@babel/plugin-transform-modules-umd": {
@ -1572,14 +1771,14 @@
"integrity": "sha512-Eqy2vuhgTY+BeqjOiXBBmbGPRC4HTa4nBSo9NcyZ8Z0MoaKo9YCbqC4CNCEPYqRbJeDUeBwfejnHR94eVMB2cw==" "integrity": "sha512-Eqy2vuhgTY+BeqjOiXBBmbGPRC4HTa4nBSo9NcyZ8Z0MoaKo9YCbqC4CNCEPYqRbJeDUeBwfejnHR94eVMB2cw=="
}, },
"@material-ui/core": { "@material-ui/core": {
"version": "4.9.12", "version": "4.9.13",
"resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.12.tgz", "resolved": "https://registry.npmjs.org/@material-ui/core/-/core-4.9.13.tgz",
"integrity": "sha512-JtRm1iNw3PRg+bzULS1uRKhdIJ2jhKO3/5ptO6kTADARsv5KmhzMbM+PYmVS09qm9Yu3ilwka4dYrtjqea53Lw==", "integrity": "sha512-GEXNwUr+laZ0N+F1efmHB64Fyg+uQIRXLqbSejg3ebSXgLYNpIjnMOPRfWdu4rICq0dAIgvvNXGkKDMcf3AMpA==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@material-ui/react-transition-group": "^4.3.0", "@material-ui/react-transition-group": "^4.3.0",
"@material-ui/styles": "^4.9.10", "@material-ui/styles": "^4.9.13",
"@material-ui/system": "^4.9.10", "@material-ui/system": "^4.9.13",
"@material-ui/types": "^5.0.1", "@material-ui/types": "^5.0.1",
"@material-ui/utils": "^4.9.12", "@material-ui/utils": "^4.9.12",
"@types/react-transition-group": "^4.2.0", "@types/react-transition-group": "^4.2.0",
@ -1631,15 +1830,15 @@
} }
}, },
"@material-ui/styles": { "@material-ui/styles": {
"version": "4.9.10", "version": "4.9.13",
"resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.10.tgz", "resolved": "https://registry.npmjs.org/@material-ui/styles/-/styles-4.9.13.tgz",
"integrity": "sha512-EXIXlqVyFDnjXF6tj72y6ZxiSy+mHtrsCo3Srkm3XUeu3Z01aftDBy7ZSr3TQ02gXHTvDSBvegp3Le6p/tl7eA==", "integrity": "sha512-lWlXJanBdHQ18jW/yphedRokHcvZD1GdGzUF/wQxKDsHwDDfO45ZkAxuSBI202dG+r1Ph483Z3pFykO2obeSRA==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@emotion/hash": "^0.8.0", "@emotion/hash": "^0.8.0",
"@material-ui/types": "^5.0.1", "@material-ui/types": "^5.0.1",
"@material-ui/utils": "^4.9.6", "@material-ui/utils": "^4.9.6",
"clsx": "^1.0.2", "clsx": "^1.0.4",
"csstype": "^2.5.2", "csstype": "^2.5.2",
"hoist-non-react-statics": "^3.3.2", "hoist-non-react-statics": "^3.3.2",
"jss": "^10.0.3", "jss": "^10.0.3",
@ -1654,9 +1853,9 @@
} }
}, },
"@material-ui/system": { "@material-ui/system": {
"version": "4.9.10", "version": "4.9.13",
"resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.10.tgz", "resolved": "https://registry.npmjs.org/@material-ui/system/-/system-4.9.13.tgz",
"integrity": "sha512-E+t0baX2TBZk6ALm8twG6objpsxLdMM4MDm1++LMt2m7CetCAEc3aIAfDaprk4+tm5hFT1Cah5dRWk8EeIFQYw==", "integrity": "sha512-6AlpvdW6KJJ5bF1Xo2OD13sCN8k+nlL36412/bWnWZOKIfIMo/Lb8c8d1DOIaT/RKWxTEUaWnKZjabVnA3eZjA==",
"requires": { "requires": {
"@babel/runtime": "^7.4.4", "@babel/runtime": "^7.4.4",
"@material-ui/utils": "^4.9.6", "@material-ui/utils": "^4.9.6",
@ -1694,17 +1893,30 @@
"integrity": "sha512-KDvuNBxfTgDHjESiouEoPXq8GCUYbHnA+jOs8MzzwVDmxNLeZuDy3amaVzKv9Q5nncI6mNGcXOzTKWzapET8Sw==" "integrity": "sha512-KDvuNBxfTgDHjESiouEoPXq8GCUYbHnA+jOs8MzzwVDmxNLeZuDy3amaVzKv9Q5nncI6mNGcXOzTKWzapET8Sw=="
}, },
"@ngtools/webpack": { "@ngtools/webpack": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-9.1.4.tgz",
"integrity": "sha512-yQ7LHqICC1Xf4E4Jw1jDCAJHYwPX7T7mRFxMd+R/0EBDcz1XBSh85+vnVs2PoJHVH+8ONOEkZKKSjABe1CZUvg==", "integrity": "sha512-CDlQzMnWraxf/dT3G5L+0N0VniWHWdzjLRP8pWBoyV0xlzWS1yL/lj8Mas2jEIuaUxZ8bi29mE7xa8prqewtBQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/core": "9.1.3", "@angular-devkit/core": "9.1.4",
"enhanced-resolve": "4.1.1", "enhanced-resolve": "4.1.1",
"rxjs": "6.5.4", "rxjs": "6.5.4",
"webpack-sources": "1.4.3" "webpack-sources": "1.4.3"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/core": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.4.tgz",
"integrity": "sha512-OPFQDmT4XabLMSRDgmnzedlOrc83DzQIgLcfoh/UhZ7aJKf/2Vq4l09p/DkMNI36vN5BRL0zDZt7TjvKNgyYgA==",
"dev": true,
"requires": {
"ajv": "6.12.0",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.5.4",
"source-map": "0.7.3"
}
},
"rxjs": { "rxjs": {
"version": "6.5.4", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
@ -1738,23 +1950,47 @@
} }
}, },
"@schematics/angular": { "@schematics/angular": {
"version": "9.1.3", "version": "9.1.4",
"resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.1.3.tgz", "resolved": "https://registry.npmjs.org/@schematics/angular/-/angular-9.1.4.tgz",
"integrity": "sha512-3vxrPyvcIWvyWgbx/fpeQvMThXlmcnHTXVn2dpTcE2BzstAYdG17W3qouhvmlqd6m28orxkKKiZaJYMNTtnV/A==", "integrity": "sha512-7nbiYBRgXc+f0sa5iXTcF6/VMy/Xq0wyKKnDFiLCb2aFYR7EXRHWF2GuwDIg2bvFugLuCBDoNnXeddLE1TXGWg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/core": "9.1.3", "@angular-devkit/core": "9.1.4",
"@angular-devkit/schematics": "9.1.3" "@angular-devkit/schematics": "9.1.4"
},
"dependencies": {
"@angular-devkit/core": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.4.tgz",
"integrity": "sha512-OPFQDmT4XabLMSRDgmnzedlOrc83DzQIgLcfoh/UhZ7aJKf/2Vq4l09p/DkMNI36vN5BRL0zDZt7TjvKNgyYgA==",
"dev": true,
"requires": {
"ajv": "6.12.0",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.5.4",
"source-map": "0.7.3"
}
},
"rxjs": {
"version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
"integrity": "sha512-naMQXcgEo3csAEGvw/NydRA0fuS2nDZJiw1YUWFKU7aPPAPGZEsD4Iimit96qwCieH6y614MCLYwdkrWx7z/7Q==",
"dev": true,
"requires": {
"tslib": "^1.9.0"
}
}
} }
}, },
"@schematics/update": { "@schematics/update": {
"version": "0.901.3", "version": "0.901.4",
"resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.901.3.tgz", "resolved": "https://registry.npmjs.org/@schematics/update/-/update-0.901.4.tgz",
"integrity": "sha512-kwfc9LMMi6ryN2oiat9vTqGMK1jKwI1c45Nm1xala35nsbQSQ12qeL3MrVmnSCeI14+cinFUbp8mWI9/3M+/Lg==", "integrity": "sha512-jCtZY2Fbj502gKN5gdu1brnRy/ssyzTrWm69Ty73V+t8uL7nLr+/hny/VBJ8CiQnKQvxcgVl1xgvI1cXzpysVA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@angular-devkit/core": "9.1.3", "@angular-devkit/core": "9.1.4",
"@angular-devkit/schematics": "9.1.3", "@angular-devkit/schematics": "9.1.4",
"@yarnpkg/lockfile": "1.1.0", "@yarnpkg/lockfile": "1.1.0",
"ini": "1.3.5", "ini": "1.3.5",
"npm-package-arg": "^8.0.0", "npm-package-arg": "^8.0.0",
@ -1764,6 +2000,19 @@
"semver-intersect": "1.4.0" "semver-intersect": "1.4.0"
}, },
"dependencies": { "dependencies": {
"@angular-devkit/core": {
"version": "9.1.4",
"resolved": "https://registry.npmjs.org/@angular-devkit/core/-/core-9.1.4.tgz",
"integrity": "sha512-OPFQDmT4XabLMSRDgmnzedlOrc83DzQIgLcfoh/UhZ7aJKf/2Vq4l09p/DkMNI36vN5BRL0zDZt7TjvKNgyYgA==",
"dev": true,
"requires": {
"ajv": "6.12.0",
"fast-json-stable-stringify": "2.1.0",
"magic-string": "0.25.7",
"rxjs": "6.5.4",
"source-map": "0.7.3"
}
},
"rxjs": { "rxjs": {
"version": "6.5.4", "version": "6.5.4",
"resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-6.5.4.tgz",
@ -1846,9 +2095,9 @@
} }
}, },
"@types/jquery": { "@types/jquery": {
"version": "3.3.36", "version": "3.3.38",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.36.tgz", "resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.3.38.tgz",
"integrity": "sha512-jHL8J5y5fJ0+C9zCTkeOvX4zqRnPug3r6JhAqAYl2YyBCYHiXTbZSH0MRCpayZADed5TigPjH92dEKczUFT2TQ==", "integrity": "sha512-nkDvmx7x/6kDM5guu/YpXkGZ/Xj/IwGiLDdKM99YA5Vag7pjGyTJ8BNUh/6hxEn/sEu5DKtyRgnONJ7EmOoKrA==",
"requires": { "requires": {
"@types/sizzle": "*" "@types/sizzle": "*"
} }
@ -3223,9 +3472,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001048", "version": "1.0.30001051",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001048.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001051.tgz",
"integrity": "sha512-g1iSHKVxornw0K8LG9LLdf+Fxnv7T1Z+mMsf0/YYLclQX4Cd522Ap0Lrw6NFqHgezit78dtyWxzlV2Xfc7vgRg==", "integrity": "sha512-sw8UUnTlRevawTMZKN7vpfwSjCBVoiMPlYd8oT2VwNylyPCBdMAUmLGUApnYYTtIm5JXsQegUAY7GPHqgfDzjw==",
"dev": true "dev": true
}, },
"canonical-path": { "canonical-path": {
@ -4873,9 +5122,9 @@
"dev": true "dev": true
}, },
"electron-to-chromium": { "electron-to-chromium": {
"version": "1.3.421", "version": "1.3.428",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.421.tgz", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.428.tgz",
"integrity": "sha512-ogxgmvHGfDuLA+GtgfK0jkFWlBb4MCZK2U1MM+l98sf4U3Ixtrfw1iC9w4mQqNvo+lHgM4pR62TqoT4QrvKJCw==", "integrity": "sha512-u3+5jEfgLKq/hGO96YfAoOAM1tgFnRDTCD5mLuev44tttcXix+INtVegAkmGzUcfDsnzkPt51XXurXZLLwXt0w==",
"dev": true "dev": true
}, },
"elliptic": { "elliptic": {
@ -6084,13 +6333,33 @@
} }
}, },
"hash-base": { "hash-base": {
"version": "3.0.4", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.4.tgz", "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.0.tgz",
"integrity": "sha1-X8hoaEfs1zSZQDMZprCj8/auSRg=", "integrity": "sha512-1nmYp/rhMDiE7AYkDw+lLwlAzz0AntGIe51F3RfFfEqyQ3feY2eI/NcwC6umIQVOASPMsWJLJScWKSSvzL9IVA==",
"dev": true, "dev": true,
"requires": { "requires": {
"inherits": "^2.0.1", "inherits": "^2.0.4",
"safe-buffer": "^5.0.1" "readable-stream": "^3.6.0",
"safe-buffer": "^5.2.0"
},
"dependencies": {
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dev": true,
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"safe-buffer": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
"integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg==",
"dev": true
}
} }
}, },
"hash.js": { "hash.js": {
@ -6900,10 +7169,13 @@
"dev": true "dev": true
}, },
"is-wsl": { "is-wsl": {
"version": "2.1.1", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz",
"integrity": "sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog==", "integrity": "sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==",
"dev": true "dev": true,
"requires": {
"is-docker": "^2.0.0"
}
}, },
"isarray": { "isarray": {
"version": "1.0.0", "version": "1.0.0",
@ -7172,9 +7444,9 @@
} }
}, },
"jquery": { "jquery": {
"version": "3.5.0", "version": "3.5.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.0.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.5.1.tgz",
"integrity": "sha512-Xb7SVYMvygPxbFMpTFQiHh1J7HClEaThguL15N/Gg37Lri/qKyhRGZYzHRyLH8Stq3Aow0LsHO2O2ci86fCrNQ==" "integrity": "sha512-XwIBPqcMn57FxfT+Go5pzySnm4KWkT1Tv7gjrpT1srtf8Weynl6R273VJ5GjkRb51IzMp5nbaPjJXMWeju2MKg=="
}, },
"jquery.terminal": { "jquery.terminal": {
"version": "2.15.4", "version": "2.15.4",
@ -9777,9 +10049,9 @@
"integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ==" "integrity": "sha512-Wb4p1J4zyFTbM+u6WuO4XstYx4Ky9Cewe4DWrel7B0w6VVICvPwdOpotjzcf6eD8TsckVnIMNONQyPIUFOUbCQ=="
}, },
"portfinder": { "portfinder": {
"version": "1.0.25", "version": "1.0.26",
"resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.25.tgz", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.26.tgz",
"integrity": "sha512-6ElJnHBbxVA1XSLgBp7G1FiCkQdlqGzuF7DswL5tcea+E8UpuvPU7beVAjjRwCioTS9ZluNbu+ZyRvgTsmqEBg==", "integrity": "sha512-Xi7mKxJHHMI3rIUrnm/jjUgwhbYMkp/XKEcZX3aG4BrumLpq3nmoQMX+ClYnDZnZ/New7IatC1no5RX0zo1vXQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"async": "^2.6.2", "async": "^2.6.2",
@ -10942,9 +11214,9 @@
} }
}, },
"rc-select": { "rc-select": {
"version": "10.2.4", "version": "10.2.5",
"resolved": "https://registry.npmjs.org/rc-select/-/rc-select-10.2.4.tgz", "resolved": "https://registry.npmjs.org/rc-select/-/rc-select-10.2.5.tgz",
"integrity": "sha512-PtfAHQRXof5gX1qEYQc8GIgroajtzU3mcMGbqM1aE1jqaZ4r+p0GJy3rabXoNANsKH7jUV5+zYJb5VhTxj2vmQ==", "integrity": "sha512-E5cdq+8kuTMdvDUmVXGdlHb4D0Effj/Xu988kUWSoALJtEAAdnUbuCJGhu04ehWtKMotGN5I9qlkLR4OdiLrgg==",
"requires": { "requires": {
"classnames": "2.x", "classnames": "2.x",
"rc-animate": "^2.10.0", "rc-animate": "^2.10.0",
@ -14526,9 +14798,9 @@
} }
}, },
"mime": { "mime": {
"version": "2.4.4", "version": "2.4.5",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.4.4.tgz", "resolved": "https://registry.npmjs.org/mime/-/mime-2.4.5.tgz",
"integrity": "sha512-LRxmNwziLPT828z+4YkNzloCFC2YM4wrB99k+AV5ZbEyfGNWfG8SO1FUXLmLDBSo89NrJZ4DIWeLjy1CHGhMGA==", "integrity": "sha512-3hQhEUF027BuxZjQA3s7rIv/7VCQPa27hN9u9g87sEkWaKwQPuXOkVKtOeiyUrnWqTDiOs8Ed2rwg733mB0R5w==",
"dev": true "dev": true
}, },
"mkdirp": { "mkdirp": {

38
ui-ngx/package.json

@ -13,24 +13,24 @@
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@angular/animations": "^9.1.2", "@angular/animations": "^9.1.4",
"@angular/cdk": "^9.1.3", "@angular/cdk": "^9.2.2",
"@angular/common": "^9.1.2", "@angular/common": "^9.1.4",
"@angular/compiler": "^9.1.2", "@angular/compiler": "^9.1.4",
"@angular/core": "^9.1.2", "@angular/core": "^9.1.4",
"@angular/flex-layout": "^9.0.0-beta.29", "@angular/flex-layout": "^9.0.0-beta.29",
"@angular/forms": "^9.1.2", "@angular/forms": "^9.1.4",
"@angular/material": "^9.2.1", "@angular/material": "^9.2.2",
"@angular/platform-browser": "^9.1.2", "@angular/platform-browser": "^9.1.4",
"@angular/platform-browser-dynamic": "^9.1.2", "@angular/platform-browser-dynamic": "^9.1.4",
"@angular/router": "^9.1.2", "@angular/router": "^9.1.4",
"@auth0/angular-jwt": "^4.0.0", "@auth0/angular-jwt": "^4.0.0",
"@date-io/date-fns": "^2.6.1", "@date-io/date-fns": "^2.6.1",
"@flowjs/flow.js": "^2.14.0", "@flowjs/flow.js": "^2.14.0",
"@flowjs/ngx-flow": "^0.4.3", "@flowjs/ngx-flow": "^0.4.3",
"@juggle/resize-observer": "^3.1.3", "@juggle/resize-observer": "^3.1.3",
"@mat-datetimepicker/core": "^4.1.0", "@mat-datetimepicker/core": "^4.1.0",
"@material-ui/core": "^4.9.11", "@material-ui/core": "^4.9.13",
"@material-ui/icons": "^4.9.1", "@material-ui/icons": "^4.9.1",
"@material-ui/pickers": "^3.2.10", "@material-ui/pickers": "^3.2.10",
"@ngrx/effects": "^9.1.0", "@ngrx/effects": "^9.1.0",
@ -50,7 +50,7 @@
"flot": "git://github.com/thingsboard/flot.git#0.9-work", "flot": "git://github.com/thingsboard/flot.git#0.9-work",
"flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master", "flot.curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
"font-awesome": "^4.7.0", "font-awesome": "^4.7.0",
"jquery": "^3.5.0", "jquery": "^3.5.1",
"jquery.terminal": "^2.15.4", "jquery.terminal": "^2.15.4",
"js-beautify": "^1.11.0", "js-beautify": "^1.11.0",
"json-schema-defaults": "^0.4.0", "json-schema-defaults": "^0.4.0",
@ -75,7 +75,7 @@
"objectpath": "^2.0.0", "objectpath": "^2.0.0",
"prop-types": "^15.7.2", "prop-types": "^15.7.2",
"raphael": "^2.3.0", "raphael": "^2.3.0",
"rc-select": "^10.2.4", "rc-select": "^10.2.5",
"react": "^16.13.1", "react": "^16.13.1",
"react-ace": "^8.1.0", "react-ace": "^8.1.0",
"react-dom": "^16.13.1", "react-dom": "^16.13.1",
@ -95,21 +95,21 @@
}, },
"devDependencies": { "devDependencies": {
"@angular-builders/custom-webpack": "^9.1.0", "@angular-builders/custom-webpack": "^9.1.0",
"@angular-devkit/build-angular": "^0.901.1", "@angular-devkit/build-angular": "^0.901.4",
"@angular/cli": "^9.1.1", "@angular/cli": "^9.1.4",
"@angular/compiler-cli": "^9.1.2", "@angular/compiler-cli": "^9.1.4",
"@angular/language-service": "^9.1.2", "@angular/language-service": "^9.1.4",
"@types/canvas-gauges": "^2.1.2", "@types/canvas-gauges": "^2.1.2",
"@types/flot": "^0.0.31", "@types/flot": "^0.0.31",
"@types/jasmine": "^3.5.10", "@types/jasmine": "^3.5.10",
"@types/jasminewd2": "^2.0.8", "@types/jasminewd2": "^2.0.8",
"@types/jquery": "^3.3.35", "@types/jquery": "^3.3.38",
"@types/js-beautify": "^1.8.2", "@types/js-beautify": "^1.8.2",
"@types/jstree": "^3.3.39", "@types/jstree": "^3.3.39",
"@types/jszip": "^3.1.7", "@types/jszip": "^3.1.7",
"@types/leaflet": "^1.5.12", "@types/leaflet": "^1.5.12",
"@types/leaflet-polylinedecorator": "^1.6.0",
"@types/leaflet-markercluster": "^1.0.3", "@types/leaflet-markercluster": "^1.0.3",
"@types/leaflet-polylinedecorator": "^1.6.0",
"@types/lodash": "^4.14.150", "@types/lodash": "^4.14.150",
"@types/raphael": "^2.3.0", "@types/raphael": "^2.3.0",
"@types/react": "^16.9.34", "@types/react": "^16.9.34",

4
ui-ngx/src/app/core/api/datasource-subcription.ts

@ -28,7 +28,7 @@ import {
} from '@shared/models/telemetry/telemetry.models'; } from '@shared/models/telemetry/telemetry.models';
import { DatasourceListener } from './datasource.service'; import { DatasourceListener } from './datasource.service';
import { AggregationType, SubscriptionTimewindow, YEAR } from '@shared/models/time/time.models'; import { AggregationType, SubscriptionTimewindow, YEAR } from '@shared/models/time/time.models';
import { deepClone, isDefinedAndNotNull, isObject } from '@core/utils'; import { deepClone, isDefinedAndNotNull, isObject, objectHashCode } from '@core/utils';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { DataAggregator } from '@core/api/data-aggregator'; import { DataAggregator } from '@core/api/data-aggregator';
@ -120,7 +120,7 @@ export class DatasourceSubscription {
data: [] data: []
}; };
} else { } else {
key = String(this.utils.objectHashCode(dataKey)); key = String(objectHashCode(dataKey));
this.datasourceData[key] = { this.datasourceData[key] = {
data: [] data: []
}; };

4
ui-ngx/src/app/core/api/datasource.service.ts

@ -25,7 +25,7 @@ import {
DatasourceSubscriptionOptions, DatasourceSubscriptionOptions,
SubscriptionDataKey SubscriptionDataKey
} from '@core/api/datasource-subcription'; } from '@core/api/datasource-subcription';
import { deepClone } from '@core/utils'; import { deepClone, objectHashCode } from '@core/utils';
export interface DatasourceListener { export interface DatasourceListener {
subscriptionType: widgetType; subscriptionType: widgetType;
@ -79,7 +79,7 @@ export class DatasourceService {
datasourceSubscriptionOptions.entityType = listener.entityType; datasourceSubscriptionOptions.entityType = listener.entityType;
datasourceSubscriptionOptions.entityId = listener.entityId; datasourceSubscriptionOptions.entityId = listener.entityId;
} }
listener.datasourceSubscriptionKey = this.utils.objectHashCode(datasourceSubscriptionOptions); listener.datasourceSubscriptionKey = objectHashCode(datasourceSubscriptionOptions);
let subscription: DatasourceSubscription; let subscription: DatasourceSubscription;
if (this.subscriptions[listener.datasourceSubscriptionKey]) { if (this.subscriptions[listener.datasourceSubscriptionKey]) {
subscription = this.subscriptions[listener.datasourceSubscriptionKey]; subscription = this.subscriptions[listener.datasourceSubscriptionKey];

58
ui-ngx/src/app/core/services/utils.service.ts

@ -20,7 +20,15 @@
import { Inject, Injectable, NgZone } from '@angular/core'; import { Inject, Injectable, NgZone } from '@angular/core';
import { WINDOW } from '@core/services/window.service'; import { WINDOW } from '@core/services/window.service';
import { ExceptionData } from '@app/shared/models/error.models'; import { ExceptionData } from '@app/shared/models/error.models';
import { deepClone, deleteNullProperties, guid, isDefined, isDefinedAndNotNull, isUndefined, createLabelFromDatasource } from '@core/utils'; import {
createLabelFromDatasource,
deepClone,
deleteNullProperties,
guid,
isDefined,
isDefinedAndNotNull,
isUndefined
} from '@core/utils';
import { WindowMessage } from '@shared/models/window-message.model'; import { WindowMessage } from '@shared/models/window-message.model';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { customTranslationsPrefix } from '@app/shared/models/constants'; import { customTranslationsPrefix } from '@app/shared/models/constants';
@ -34,8 +42,6 @@ import jsonSchemaDefaults from 'json-schema-defaults';
import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints'; import materialIconsCodepoints from '!raw-loader!material-design-icons/iconfont/codepoints';
import { Observable, of, ReplaySubject } from 'rxjs'; import { Observable, of, ReplaySubject } from 'rxjs';
const varsRegex = /\$\{([^}]*)\}/g;
const predefinedFunctions: { [func: string]: string } = { const predefinedFunctions: { [func: string]: string } = {
Sin: 'return Math.round(1000*Math.sin(time/5000));', Sin: 'return Math.round(1000*Math.sin(time/5000));',
Cos: 'return Math.round(1000*Math.cos(time/5000));', Cos: 'return Math.round(1000*Math.cos(time/5000));',
@ -163,32 +169,6 @@ export class UtilsService {
return obj; return obj;
} }
public hashCode(str: string): number {
let hash = 0;
let i: number;
let char: number;
if (str.length === 0) {
return hash;
}
for (i = 0; i < str.length; i++) {
char = str.charCodeAt(i);
// tslint:disable-next-line:no-bitwise
hash = ((hash << 5) - hash) + char;
// tslint:disable-next-line:no-bitwise
hash = hash & hash; // Convert to 32bit integer
}
return hash;
}
public objectHashCode(obj: any): number {
let hash = 0;
if (obj) {
const str = JSON.stringify(obj);
hash = this.hashCode(str);
}
return hash;
}
public processWidgetException(exception: any): ExceptionData { public processWidgetException(exception: any): ExceptionData {
const data = this.parseException(exception, -6); const data = this.parseException(exception, -6);
if (this.widgetEditMode) { if (this.widgetEditMode) {
@ -241,7 +221,7 @@ export class UtilsService {
} }
public customTranslation(translationValue: string, defaultValue: string): string { public customTranslation(translationValue: string, defaultValue: string): string {
let result = ''; let result: string;
const translationId = customTranslationsPrefix + translationValue; const translationId = customTranslationsPrefix + translationValue;
const translation = this.translate.instant(translationId); const translation = this.translate.instant(translationId);
if (translation !== translationId) { if (translation !== translationId) {
@ -252,20 +232,6 @@ export class UtilsService {
return result; return result;
} }
public insertVariable(pattern: string, name: string, value: any): string {
let result = deepClone(pattern);
let match = varsRegex.exec(pattern);
while (match !== null) {
const variable = match[0];
const variableName = match[1];
if (variableName === name) {
result = result.split(variable).join(value);
}
match = varsRegex.exec(pattern);
}
return result;
}
public guid(): string { public guid(): string {
return guid(); return guid();
} }
@ -379,10 +345,6 @@ export class UtilsService {
return additionalDataKey; return additionalDataKey;
} }
public createLabelFromDatasource(datasource: Datasource, pattern: string) {
return createLabelFromDatasource(datasource, pattern);
}
public generateColors(datasources: Array<Datasource>) { public generateColors(datasources: Array<Datasource>) {
let index = 0; let index = 0;
datasources.forEach((datasource) => { datasources.forEach((datasource) => {

223
ui-ngx/src/app/core/utils.ts

@ -15,11 +15,12 @@
/// ///
import _ from 'lodash'; import _ from 'lodash';
import { Observable, Observer, of, Subject } from 'rxjs'; import { Observable, Subject } from 'rxjs';
import { finalize, map, share } from 'rxjs/operators'; import { finalize, share } from 'rxjs/operators';
import base64js from 'base64-js'; import base64js from 'base64-js';
import { Datasource } from '@app/shared/models/widget.models'; import { Datasource } from '@app/shared/models/widget.models';
import { FormattedData } from '@app/modules/home/components/widget/lib/maps/map-models';
const varsRegex = /\${([^}]*)}/g;
export function onParentScrollOrWindowResize(el: Node): Observable<Event> { export function onParentScrollOrWindowResize(el: Node): Observable<Event> {
const scrollSubject = new Subject<Event>(); const scrollSubject = new Subject<Event>();
@ -33,7 +34,7 @@ export function onParentScrollOrWindowResize(el: Node): Observable<Event> {
scrollParentNode.addEventListener('scroll', eventListenerObject); scrollParentNode.addEventListener('scroll', eventListenerObject);
}); });
window.addEventListener('resize', eventListenerObject); window.addEventListener('resize', eventListenerObject);
const shared = scrollSubject.pipe( return scrollSubject.pipe(
finalize(() => { finalize(() => {
scrollParentNodes.forEach((scrollParentNode) => { scrollParentNodes.forEach((scrollParentNode) => {
scrollParentNode.removeEventListener('scroll', eventListenerObject); scrollParentNode.removeEventListener('scroll', eventListenerObject);
@ -42,18 +43,13 @@ export function onParentScrollOrWindowResize(el: Node): Observable<Event> {
}), }),
share() share()
); );
return shared;
} }
export function isLocalUrl(url: string): boolean { export function isLocalUrl(url: string): boolean {
const parser = document.createElement('a'); const parser = document.createElement('a');
parser.href = url; parser.href = url;
const host = parser.hostname; const host = parser.hostname;
if (host === 'localhost' || host === '127.0.0.1') { return host === 'localhost' || host === '127.0.0.1';
return true;
} else {
return false;
}
} }
export function animatedScroll(element: HTMLElement, scrollTop: number, delay?: number) { export function animatedScroll(element: HTMLElement, scrollTop: number, delay?: number) {
@ -68,8 +64,7 @@ export function animatedScroll(element: HTMLElement, scrollTop: number, delay?:
element.scrollTop = to; element.scrollTop = to;
} else { } else {
currentTime += increment; currentTime += increment;
const val = easeInOut(currentTime, start, remaining, duration); element.scrollTop = easeInOut(currentTime, start, remaining, duration);
element.scrollTop = val;
if (currentTime < duration) { if (currentTime < duration) {
setTimeout(animateScroll, increment); setTimeout(animateScroll, increment);
} }
@ -110,6 +105,15 @@ export function isString(value: any): boolean {
return typeof value === 'string'; return typeof value === 'string';
} }
export function isEmpty(obj: any): boolean {
for (const key of Object.keys(obj)) {
if (Object.prototype.hasOwnProperty.call(obj, key)) {
return false;
}
}
return true;
}
export function formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined { export function formatValue(value: any, dec?: number, units?: string, showZeroDecimals?: boolean): string | undefined {
if (isDefined(value) && if (isDefined(value) &&
value !== null && isNumeric(value)) { value !== null && isNumeric(value)) {
@ -118,7 +122,7 @@ export function formatValue(value: any, dec?: number, units?: string, showZeroDe
formatted = formatted.toFixed(dec); formatted = formatted.toFixed(dec);
} }
if (!showZeroDecimals) { if (!showZeroDecimals) {
formatted = (Number(formatted) * 1); formatted = (Number(formatted));
} }
formatted = formatted.toString(); formatted = formatted.toString();
if (isDefined(units) && units.length > 0) { if (isDefined(units) && units.length > 0) {
@ -154,15 +158,13 @@ export function deleteNullProperties(obj: any) {
export function objToBase64(obj: any): string { export function objToBase64(obj: any): string {
const json = JSON.stringify(obj); const json = JSON.stringify(obj);
const encoded = utf8Encode(json); const encoded = utf8Encode(json);
const b64Encoded: string = base64js.fromByteArray(encoded); return base64js.fromByteArray(encoded);
return b64Encoded;
} }
export function base64toObj(b64Encoded: string): any { export function base64toObj(b64Encoded: string): any {
const encoded: Uint8Array | number[] = base64js.toByteArray(b64Encoded); const encoded: Uint8Array | number[] = base64js.toByteArray(b64Encoded);
const json = utf8Decode(encoded); const json = utf8Decode(encoded);
const obj = JSON.parse(json); return JSON.parse(json);
return obj;
} }
function utf8Encode(str: string): Uint8Array | number[] { function utf8Encode(str: string): Uint8Array | number[] {
@ -223,11 +225,13 @@ function scrollParents(node: Node): Node[] {
return scrollParentNodes; return scrollParentNodes;
} }
function hashCode(str) { export function hashCode(str: string): number {
let hash = 0; let hash = 0;
let i; let i: number;
let char; let char: number;
if (str.length === 0) return hash; if (str.length === 0) {
return hash;
}
for (i = 0; i < str.length; i++) { for (i = 0; i < str.length; i++) {
char = str.charCodeAt(i); char = str.charCodeAt(i);
// tslint:disable-next-line:no-bitwise // tslint:disable-next-line:no-bitwise
@ -238,6 +242,15 @@ function hashCode(str) {
return hash; return hash;
} }
export function objectHashCode(obj: any): number {
let hash = 0;
if (obj) {
const str = JSON.stringify(obj);
hash = hashCode(str);
}
return hash;
}
function easeInOut( function easeInOut(
currentTime: number, currentTime: number,
startTime: number, startTime: number,
@ -433,29 +446,21 @@ export function getDescendantProp(obj: any, path: string): any {
return path.split('.').reduce((acc, part) => acc && acc[part], obj); return path.split('.').reduce((acc, part) => acc && acc[part], obj);
} }
export function imageLoader(imageUrl: string): Observable<HTMLImageElement> { export function insertVariable(pattern: string, name: string, value: any): string {
return new Observable((observer: Observer<HTMLImageElement>) => { let result = pattern;
const image = new Image(); let match = varsRegex.exec(pattern);
image.style.position = 'absolute'; while (match !== null) {
image.style.left = '-99999px'; const variable = match[0];
image.style.top = '-99999px'; const variableName = match[1];
image.onload = () => { if (variableName === name) {
observer.next(image); result = result.split(variable).join(value);
document.body.removeChild(image); }
observer.complete(); match = varsRegex.exec(pattern);
}; }
image.onerror = err => { return result;
observer.error(err);
document.body.removeChild(image);
observer.complete();
};
document.body.appendChild(image)
image.src = imageUrl;
});
} }
export function createLabelFromDatasource(datasource: Datasource, pattern: string) { export function createLabelFromDatasource(datasource: Datasource, pattern: string) {
const varsRegex = /\$\{([^}]*)\}/g;
let label = pattern; let label = pattern;
if (!datasource) { if (!datasource) {
return label; return label;
@ -482,142 +487,6 @@ export function createLabelFromDatasource(datasource: Datasource, pattern: strin
return label; return label;
} }
const imageAspectMap = {};
export function aspectCache(imageUrl: string): Observable<number> {
if (imageUrl?.length) {
const hash = hashCode(imageUrl);
let aspect = imageAspectMap[hash];
if (aspect) {
return of(aspect);
}
else return imageLoader(imageUrl).pipe(map(image => {
aspect = image.width / image.height;
imageAspectMap[hash] = aspect;
return aspect;
}))
}
}
export function parseArray(input: any[]): any[] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray, dsIndex) =>
entityArray[0].data.map((el, i) => {
const obj = {
entityName: entityArray[0]?.datasource?.entityName,
$datasource: entityArray[0]?.datasource,
dsIndex,
time: el[0],
deviceType: null
};
entityArray.filter(el => el.data.length).forEach(entity => {
obj[entity?.dataKey?.label] = entity?.data[i][1];
obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];
if (entity?.dataKey?.label === 'type') {
obj.deviceType = entity?.data[0][1];
}
});
return obj;
})
);
}
export function parseData(input: any[]): FormattedData[] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray, i) => {
const obj = {
entityName: entityArray[0]?.datasource?.entityName,
$datasource: entityArray[0]?.datasource as Datasource,
dsIndex: i,
deviceType: null
};
entityArray.filter(el => el.data.length).forEach(el => {
obj[el?.dataKey?.label] = el?.data[0][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
if (el?.dataKey?.label === 'type') {
obj.deviceType = el?.data[0][1];
}
});
return obj;
});
}
export function safeExecute(func: Function, params = []) {
let res = null;
if (func && typeof (func) === 'function') {
try {
res = func(...params);
}
catch (err) {
console.log('error in external function:', err);
res = null;
}
}
return res;
}
export function parseFunction(source: any, params: string[] = ['def']): Function {
let res = null;
if (source?.length) {
try {
res = new Function(...params, source);
}
catch (err) {
res = null;
}
}
return res;
}
export function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
translateFn?: (key: string) => string) {
let res = '';
try {
if (template.match(/<link-act/g)) {
template = template.replace(/<link-act/g, '<a').replace(/link-act>/g, 'a>')
.replace(/name=(\'|")(.*?)(\'|")/g, `class='tb-custom-action' id='$2'`);
}
if (translateFn) {
template = translateFn(template);
}
template = createLabelFromDatasource(data.$datasource, template);
const formatted = template.match(/\$\{([^}]*)\:\d*\}/g);
if (formatted)
formatted.forEach(value => {
const [variable, digits] = value.replace('${', '').replace('}', '').split(':');
data[variable] = padValue(data[variable], +digits);
if (data[variable] === 'NaN') data[variable] = '';
template = template.replace(value, '${' + variable + '}');
});
const variables = template.match(/\$\{.*?\}/g);
if (variables) {
variables.forEach(variable => {
variable = variable.replace('${', '').replace('}', '');
if (!data[variable])
data[variable] = '';
})
}
const compiled = _.template(template);
res = compiled(data);
}
catch (ex) {
console.log(ex, template)
}
return res;
}
export let parseWithTranslation = {
translate(): string {
throw console.error('Translate not assigned');
},
parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this?.translate);
},
setTranslate(translateFn: (key: string, defaultTranslation?: string) => string) {
this.translate = translateFn;
}
}
export function padValue(val: any, dec: number): string { export function padValue(val: any, dec: number): string {
let strVal; let strVal;
let n; let n;

4
ui-ngx/src/app/modules/home/components/widget/lib/alarms-table-widget.component.ts

@ -33,7 +33,7 @@ import { Datasource, WidgetActionDescriptor, WidgetConfig } from '@shared/models
import { IWidgetSubscription } from '@core/api/widget-api.models'; import { IWidgetSubscription } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils'; import { deepClone, isDefined, isNumber, createLabelFromDatasource, hashCode } from '@core/utils';
import cssjs from '@core/css/css'; import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
@ -304,7 +304,7 @@ export class AlarmsTableWidgetComponent extends PageComponent implements OnInit,
const cssString = constructTableCssString(this.widgetConfig); const cssString = constructTableCssString(this.widgetConfig);
const cssParser = new cssjs(); const cssParser = new cssjs();
cssParser.testMode = false; cssParser.testMode = false;
const namespace = 'alarms-table-' + this.utils.hashCode(cssString); const namespace = 'alarms-table-' + hashCode(cssString);
cssParser.cssPreviewNamespace = namespace; cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, cssString); cssParser.createStyleElement(namespace, cssString);
$(this.elementRef.nativeElement).addClass(namespace); $(this.elementRef.nativeElement).addClass(namespace);

4
ui-ngx/src/app/modules/home/components/widget/lib/entities-hierarchy-widget.component.ts

@ -37,7 +37,7 @@ import {
import { BaseData } from '@shared/models/base-data'; import { BaseData } from '@shared/models/base-data';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
import { deepClone } from '@core/utils'; import { deepClone, hashCode } from '@core/utils';
import { import {
defaultNodeIconFunction, defaultNodeIconFunction,
defaultNodeOpenedFunction, defaultNodeOpenedFunction,
@ -186,7 +186,7 @@ export class EntitiesHierarchyWidgetComponent extends PageComponent implements O
const cssString = constructTableCssString(this.widgetConfig); const cssString = constructTableCssString(this.widgetConfig);
const cssParser = new cssjs(); const cssParser = new cssjs();
cssParser.testMode = false; cssParser.testMode = false;
const namespace = 'entities-hierarchy-' + this.utils.hashCode(cssString); const namespace = 'entities-hierarchy-' + hashCode(cssString);
cssParser.cssPreviewNamespace = namespace; cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, cssString); cssParser.createStyleElement(namespace, cssString);
$(this.elementRef.nativeElement).addClass(namespace); $(this.elementRef.nativeElement).addClass(namespace);

4
ui-ngx/src/app/modules/home/components/widget/lib/entities-table-widget.component.ts

@ -39,7 +39,7 @@ import {
import { IWidgetSubscription } from '@core/api/widget-api.models'; import { IWidgetSubscription } from '@core/api/widget-api.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { deepClone, isDefined, isNumber, createLabelFromDatasource } from '@core/utils'; import { deepClone, isDefined, isNumber, createLabelFromDatasource, hashCode } from '@core/utils';
import cssjs from '@core/css/css'; import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
@ -226,7 +226,7 @@ export class EntitiesTableWidgetComponent extends PageComponent implements OnIni
const cssString = constructTableCssString(this.widgetConfig); const cssString = constructTableCssString(this.widgetConfig);
const cssParser = new cssjs(); const cssParser = new cssjs();
cssParser.testMode = false; cssParser.testMode = false;
const namespace = 'entities-table-' + this.utils.hashCode(cssString); const namespace = 'entities-table-' + hashCode(cssString);
cssParser.cssPreviewNamespace = namespace; cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, cssString); cssParser.createStyleElement(namespace, cssString);
$(this.elementRef.nativeElement).addClass(namespace); $(this.elementRef.nativeElement).addClass(namespace);

23
ui-ngx/src/app/modules/home/components/widget/lib/maps/leaflet-map.ts

@ -14,19 +14,26 @@
/// limitations under the License. /// limitations under the License.
/// ///
import L, { LatLngTuple, LatLngBounds, Point, MarkerClusterGroupOptions, markerClusterGroup } from 'leaflet'; import L, { LatLngBounds, LatLngTuple, markerClusterGroup, MarkerClusterGroupOptions } from 'leaflet';
import 'leaflet-providers'; import 'leaflet-providers';
import 'leaflet.markercluster/dist/leaflet.markercluster'; import 'leaflet.markercluster/dist/leaflet.markercluster';
import { MapSettings, MarkerSettings, FormattedData, UnitedMapSettings, PolygonSettings, PolylineSettings } from './map-models'; import {
FormattedData,
MapSettings,
MarkerSettings,
PolygonSettings,
PolylineSettings,
UnitedMapSettings
} from './map-models';
import { Marker } from './markers'; import { Marker } from './markers';
import { BehaviorSubject, Observable } from 'rxjs'; import { BehaviorSubject, Observable } from 'rxjs';
import { filter } from 'rxjs/operators'; import { filter } from 'rxjs/operators';
import { Polyline } from './polyline'; import { Polyline } from './polyline';
import { Polygon } from './polygon'; import { Polygon } from './polygon';
import { DatasourceData } from '@app/shared/models/widget.models'; import { DatasourceData } from '@app/shared/models/widget.models';
import { safeExecute } from '@app/core/utils'; import { safeExecute } from '@home/components/widget/lib/maps/maps-utils';
export default abstract class LeafletMap { export default abstract class LeafletMap {
@ -38,13 +45,11 @@ export default abstract class LeafletMap {
map$: BehaviorSubject<L.Map> = new BehaviorSubject(null); map$: BehaviorSubject<L.Map> = new BehaviorSubject(null);
ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map)); ready$: Observable<L.Map> = this.map$.pipe(filter(map => !!map));
options: UnitedMapSettings; options: UnitedMapSettings;
isMarketCluster: boolean;
bounds: L.LatLngBounds; bounds: L.LatLngBounds;
newMarker: L.Marker;
datasources: FormattedData[]; datasources: FormattedData[];
markersCluster; markersCluster;
constructor(public $container: HTMLElement, options: UnitedMapSettings) { protected constructor(public $container: HTMLElement, options: UnitedMapSettings) {
this.options = options; this.options = options;
} }
@ -125,7 +130,7 @@ export default abstract class LeafletMap {
addMarker.setPosition('topright') addMarker.setPosition('topright')
} }
L.Control.AddMarker = L.Control.extend({ L.Control.AddMarker = L.Control.extend({
onAdd(map) { onAdd() {
const img = L.DomUtil.create('img') as any; const img = L.DomUtil.create('img') as any;
img.src = `assets/add_location.svg`; img.src = `assets/add_location.svg`;
img.style.width = '32px'; img.style.width = '32px';
@ -138,7 +143,7 @@ export default abstract class LeafletMap {
draggableImg.on('dragend', dragListener) draggableImg.on('dragend', dragListener)
return img; return img;
}, },
onRemove(map) { onRemove() {
}, },
dragMarker: this.dragMarker dragMarker: this.dragMarker
} as any); } as any);
@ -166,7 +171,7 @@ export default abstract class LeafletMap {
this.datasources = dataSources; this.datasources = dataSources;
} }
public saveMarkerLocation(e) { public saveMarkerLocation(_e) {
} }

6
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-models.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { LatLngTuple, LeafletMouseEvent } from 'leaflet'; import { LatLngTuple } from 'leaflet';
import { Datasource } from '@app/shared/models/widget.models'; import { Datasource } from '@app/shared/models/widget.models';
export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string; export type GenericFunction = (data: FormattedData, dsData: FormattedData[], dsIndex: number) => string;
@ -116,7 +116,7 @@ export type PolygonSettings = {
autocloseTooltip: boolean; autocloseTooltip: boolean;
tooltipFunction: GenericFunction; tooltipFunction: GenericFunction;
showTooltipAction: string; showTooltipAction: string;
tooltipAction: object; tooltipAction: { [name: string]: actionsHandler };
tooltipPattern: string; tooltipPattern: string;
useTooltipFunction: boolean; useTooltipFunction: boolean;
polygonClick: { [name: string]: actionsHandler }; polygonClick: { [name: string]: actionsHandler };
@ -152,6 +152,6 @@ export interface HistorySelectSettings {
buttonColor: string; buttonColor: string;
} }
export type actionsHandler = ($event: Event | LeafletMouseEvent, datasource: Datasource) => void; export type actionsHandler = ($event: Event, datasource: Datasource) => void;
export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings; export type UnitedMapSettings = MapSettings & PolygonSettings & MarkerSettings & PolylineSettings;

12
ui-ngx/src/app/modules/home/components/widget/lib/maps/map-widget2.ts

@ -31,11 +31,10 @@ import {
} from './schemes'; } from './schemes';
import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface'; import { MapWidgetStaticInterface, MapWidgetInterface } from './map-widget.interface';
import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers'; import { OpenStreetMap, TencentMap, GoogleMap, HEREMap, ImageMap } from './providers';
import { parseFunction, parseArray, parseData, parseWithTranslation } from '@core/utils';
import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils'; import { initSchema, addToSchema, mergeSchemes, addCondition, addGroupInfo } from '@core/schema-utils';
import { of, Subject } from 'rxjs'; import { of, Subject } from 'rxjs';
import { WidgetContext } from '@app/modules/home/models/widget-component.models'; import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { getDefCenterPosition } from './maps-utils'; import { getDefCenterPosition, parseArray, parseData, parseFunction, parseWithTranslation } from './maps-utils';
import { JsonSettingsSchema, WidgetActionDescriptor, DatasourceType, widgetType, Datasource } from '@shared/models/widget.models'; import { JsonSettingsSchema, WidgetActionDescriptor, DatasourceType, widgetType, Datasource } from '@shared/models/widget.models';
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models'; import { AttributeScope, DataKeyType, LatestTelemetry } from '@shared/models/telemetry/telemetry.models';
@ -138,7 +137,7 @@ export class MapWidgetController implements MapWidgetInterface {
else return ''; else return '';
} }
getDescriptors(name: string): { [name: string]: ($event: Event) => void } { getDescriptors(name: string): { [name: string]: ($event: Event, datasource: Datasource) => void } {
const descriptors = this.ctx.actionsApi.getActionDescriptors(name); const descriptors = this.ctx.actionsApi.getActionDescriptors(name);
const actions = {}; const actions = {};
descriptors.forEach(descriptor => { descriptors.forEach(descriptor => {
@ -150,9 +149,10 @@ export class MapWidgetController implements MapWidgetInterface {
onInit() { onInit() {
} }
private onCustomAction(descriptor: WidgetActionDescriptor, $event: any, entityInfo: Datasource) { private onCustomAction(descriptor: WidgetActionDescriptor, $event: Event, entityInfo: Datasource) {
if ($event && $event.stopPropagation) { if ($event) {
$event?.stopPropagation(); $event.preventDefault();
$event.stopPropagation();
} }
const { entityId, entityName, entityLabel, entityType } = entityInfo; const { entityId, entityName, entityLabel, entityType } = entityInfo;
this.ctx.actionsApi.handleWidgetAction($event, descriptor, { this.ctx.actionsApi.handleWidgetAction($event, descriptor, {

175
ui-ngx/src/app/modules/home/components/widget/lib/maps/maps-utils.ts

@ -15,8 +15,12 @@
/// ///
import L from 'leaflet'; import L from 'leaflet';
import { MarkerSettings, PolygonSettings, PolylineSettings } from './map-models'; import { FormattedData, MarkerSettings, PolygonSettings, PolylineSettings } from './map-models';
import { Datasource } from '@app/shared/models/widget.models'; import { Datasource } from '@app/shared/models/widget.models';
import _ from 'lodash';
import { Observable, Observer, of } from 'rxjs';
import { map } from 'rxjs/operators';
import { createLabelFromDatasource, hashCode, padValue } from '@core/utils';
export function createTooltip(target: L.Layer, export function createTooltip(target: L.Layer,
settings: MarkerSettings | PolylineSettings | PolygonSettings, settings: MarkerSettings | PolylineSettings | PolygonSettings,
@ -49,7 +53,7 @@ export function createTooltip(target: L.Layer,
export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number { export function getRatio(firsMoment: number, secondMoment: number, intermediateMoment: number): number {
return (intermediateMoment - firsMoment) / (secondMoment - firsMoment); return (intermediateMoment - firsMoment) / (secondMoment - firsMoment);
}; }
export function findAngle(startPoint, endPoint) { export function findAngle(startPoint, endPoint) {
let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude); let angle = -Math.atan2(endPoint.latitude - startPoint.latitude, endPoint.longitude - startPoint.longitude);
@ -65,3 +69,170 @@ export function getDefCenterPosition(position) {
return position; return position;
return [0, 0]; return [0, 0];
} }
const imageAspectMap = {};
function imageLoader(imageUrl: string): Observable<HTMLImageElement> {
return new Observable((observer: Observer<HTMLImageElement>) => {
const image = new Image();
image.style.position = 'absolute';
image.style.left = '-99999px';
image.style.top = '-99999px';
image.onload = () => {
observer.next(image);
document.body.removeChild(image);
observer.complete();
};
image.onerror = err => {
observer.error(err);
document.body.removeChild(image);
observer.complete();
};
document.body.appendChild(image)
image.src = imageUrl;
});
}
export function aspectCache(imageUrl: string): Observable<number> {
if (imageUrl?.length) {
const hash = hashCode(imageUrl);
let aspect = imageAspectMap[hash];
if (aspect) {
return of(aspect);
}
else return imageLoader(imageUrl).pipe(map(image => {
aspect = image.width / image.height;
imageAspectMap[hash] = aspect;
return aspect;
}))
}
}
function parseTemplate(template: string, data: { $datasource?: Datasource, [key: string]: any },
translateFn?: (key: string) => string) {
let res = '';
try {
if (template.match(/<link-act/g)) {
template = template.replace(/<link-act/g, '<a href="#"').replace(/link-act>/g, 'a>')
.replace(/name=(['"])(.*?)(['"])/g, `class='tb-custom-action' id='$2'`);
}
if (translateFn) {
template = translateFn(template);
}
template = createLabelFromDatasource(data.$datasource, template);
const formatted = template.match(/\${([^}]*):\d*}/g);
if (formatted)
formatted.forEach(value => {
const [variable, digits] = value.replace('${', '').replace('}', '').split(':');
data[variable] = padValue(data[variable], +digits);
if (data[variable] === 'NaN') data[variable] = '';
template = template.replace(value, '${' + variable + '}');
});
const variables = template.match(/\${.*?}/g);
if (variables) {
variables.forEach(variable => {
variable = variable.replace('${', '').replace('}', '');
if (!data[variable])
data[variable] = '';
})
}
const compiled = _.template(template);
res = compiled(data);
}
catch (ex) {
console.log(ex, template)
}
return res;
}
type TranslateFunc = (key: string, defaultTranslation?: string) => string;
export const parseWithTranslation = {
translateFn: null,
translate(key: string, defaultTranslation?: string): string {
if (this.translateFn) {
return this.translateFn(key, defaultTranslation);
} else {
throw console.error('Translate not assigned');
}
},
parseTemplate(template: string, data: object, forceTranslate = false): string {
return parseTemplate(forceTranslate ? this.translate(template) : template, data, this.translate.bind(this));
},
setTranslate(translateFn: TranslateFunc) {
this.translateFn = translateFn;
}
}
export function parseData(input: any[]): FormattedData[] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray, i) => {
const obj = {
entityName: entityArray[0]?.datasource?.entityName,
$datasource: entityArray[0]?.datasource as Datasource,
dsIndex: i,
deviceType: null
};
entityArray.filter(el => el.data.length).forEach(el => {
obj[el?.dataKey?.label] = el?.data[0][1];
obj[el?.dataKey?.label + '|ts'] = el?.data[0][0];
if (el?.dataKey?.label === 'type') {
obj.deviceType = el?.data[0][1];
}
});
return obj;
});
}
export function parseArray(input: any[]): any[] {
return _(input).groupBy(el => el?.datasource?.entityName)
.values().value().map((entityArray, dsIndex) =>
entityArray[0].data.map((el, i) => {
const obj = {
entityName: entityArray[0]?.datasource?.entityName,
$datasource: entityArray[0]?.datasource,
dsIndex,
time: el[0],
deviceType: null
};
entityArray.filter(e => e.data.length).forEach(entity => {
obj[entity?.dataKey?.label] = entity?.data[i][1];
obj[entity?.dataKey?.label + '|ts'] = entity?.data[0][0];
if (entity?.dataKey?.label === 'type') {
obj.deviceType = entity?.data[0][1];
}
});
return obj;
})
);
}
export function parseFunction(source: any, params: string[] = ['def']): (...args: any[]) => any {
let res = null;
if (source?.length) {
try {
res = new Function(...params, source);
}
catch (err) {
res = null;
}
}
return res;
}
export function safeExecute(func: (...args: any[]) => any, params = []) {
let res = null;
if (func && typeof (func) === 'function') {
try {
res = func(...params);
}
catch (err) {
console.log('error in external function:', err);
res = null;
}
}
return res;
}

7
ui-ngx/src/app/modules/home/components/widget/lib/maps/markers.ts

@ -14,10 +14,9 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { aspectCache, parseWithTranslation, safeExecute } from '@app/core/utils';
import L, { LeafletMouseEvent } from 'leaflet'; import L, { LeafletMouseEvent } from 'leaflet';
import { FormattedData, MarkerSettings } from './map-models'; import { FormattedData, MarkerSettings } from './map-models';
import { createTooltip } from './maps-utils'; import { aspectCache, createTooltip, parseWithTranslation, safeExecute } from './maps-utils';
import tinycolor from 'tinycolor2'; import tinycolor from 'tinycolor2';
export class Marker { export class Marker {
@ -50,7 +49,7 @@ export class Marker {
this.leafletMarker.on('click', (event: LeafletMouseEvent) => { this.leafletMarker.on('click', (event: LeafletMouseEvent) => {
for (const action in this.settings.markerClick) { for (const action in this.settings.markerClick) {
if (typeof (this.settings.markerClick[action]) === 'function') { if (typeof (this.settings.markerClick[action]) === 'function') {
this.settings.markerClick[action](event, this.data.$datasource); this.settings.markerClick[action](event.originalEvent, this.data.$datasource);
} }
} }
}); });
@ -184,4 +183,4 @@ export class Marker {
setMarkerPosition(latLng) { setMarkerPosition(latLng) {
this.leafletMarker.setLatLng(latLng); this.leafletMarker.setLatLng(latLng);
} }
} }

7
ui-ngx/src/app/modules/home/components/widget/lib/maps/polygon.ts

@ -15,10 +15,9 @@
/// ///
import L, { LatLngExpression, LatLngTuple, LeafletMouseEvent } from 'leaflet'; import L, { LatLngExpression, LatLngTuple, LeafletMouseEvent } from 'leaflet';
import { createTooltip } from './maps-utils'; import { createTooltip, parseWithTranslation, safeExecute } from './maps-utils';
import { PolygonSettings, FormattedData } from './map-models'; import { PolygonSettings } from './map-models';
import { DatasourceData } from '@app/shared/models/widget.models'; import { DatasourceData } from '@app/shared/models/widget.models';
import { safeExecute, parseWithTranslation } from '@app/core/utils';
export class Polygon { export class Polygon {
@ -46,7 +45,7 @@ export class Polygon {
this.leafletPoly.on('click', (event: LeafletMouseEvent) => { this.leafletPoly.on('click', (event: LeafletMouseEvent) => {
for (const action in this.settings.polygonClick) { for (const action in this.settings.polygonClick) {
if (typeof (this.settings.polygonClick[action]) === 'function') { if (typeof (this.settings.polygonClick[action]) === 'function') {
this.settings.polygonClick[action](event, polyData.datasource); this.settings.polygonClick[action](event.originalEvent, polyData.datasource);
} }
} }
}); });

4
ui-ngx/src/app/modules/home/components/widget/lib/maps/polyline.ts

@ -17,8 +17,8 @@
import L, { PolylineDecoratorOptions } from 'leaflet'; import L, { PolylineDecoratorOptions } from 'leaflet';
import 'leaflet-polylinedecorator'; import 'leaflet-polylinedecorator';
import { safeExecute } from '@app/core/utils';
import { PolylineSettings } from './map-models'; import { PolylineSettings } from './map-models';
import { safeExecute } from '@home/components/widget/lib/maps/maps-utils';
export class Polyline { export class Polyline {
@ -95,4 +95,4 @@ export class Polyline {
setPolylineLatLngs(latLngs) { setPolylineLatLngs(latLngs) {
this.leafletPoly.setLatLngs(latLngs); this.leafletPoly.setLatLngs(latLngs);
} }
} }

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/google-map.ts

@ -29,7 +29,7 @@ export class GoogleMap extends LeafletMap {
super($container, options); super($container, options);
this.loadGoogle(() => { this.loadGoogle(() => {
const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel); const map = L.map($container).setView(options?.defaultCenterPosition, options?.defaultZoomLevel);
const roads = (L.gridLayer as any).googleMutant({ (L.gridLayer as any).googleMutant({
type: options?.gmDefaultMapType || 'roadmap' type: options?.gmDefaultMapType || 'roadmap'
}).addTo(map); }).addTo(map);
super.setMap(map); super.setMap(map);

2
ui-ngx/src/app/modules/home/components/widget/lib/maps/providers/image-map.ts

@ -17,9 +17,9 @@
import L, { LatLngLiteral, LatLngBounds, LatLngTuple } from 'leaflet'; import L, { LatLngLiteral, LatLngBounds, LatLngTuple } from 'leaflet';
import LeafletMap from '../leaflet-map'; import LeafletMap from '../leaflet-map';
import { UnitedMapSettings } from '../map-models'; import { UnitedMapSettings } from '../map-models';
import { aspectCache, parseFunction } from '@app/core/utils';
import { Observable } from 'rxjs'; import { Observable } from 'rxjs';
import { map, filter, switchMap } from 'rxjs/operators'; import { map, filter, switchMap } from 'rxjs/operators';
import { aspectCache, parseFunction } from '@home/components/widget/lib/maps/maps-utils';
const maxZoom = 4;// ? const maxZoom = 4;// ?

28
ui-ngx/src/app/modules/home/components/widget/lib/maps/schemes.ts

@ -558,32 +558,6 @@ export const markerClusteringSettingsSchema =
] ]
}; };
export const markerClusteringSettingsSchemaGoogle =
{
schema: {
title: 'Marker Clustering Configuration Google',
type: 'object',
properties: {
gridSize: {
title: 'Maximum radius that a cluster will cover in pixels',
type: 'number',
default: 60
},
minimumClusterSize: {
title: 'The minimum number of markers in a cluster',
type: 'number'
}
},
required: []
},
form: [
'gridSize',
'minimumClusterSize'
]
};
export const markerClusteringSettingsSchemaLeaflet = export const markerClusteringSettingsSchemaLeaflet =
{ {
schema: { schema: {
@ -1271,4 +1245,4 @@ export const tripAnimationSchema = {
} }
] ]
}] }]
} }

4
ui-ngx/src/app/modules/home/components/widget/lib/timeseries-table-widget.component.ts

@ -40,7 +40,7 @@ import {
} from '@shared/models/widget.models'; } from '@shared/models/widget.models';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { isDefined, isNumber } from '@core/utils'; import { hashCode, isDefined, isNumber } from '@core/utils';
import cssjs from '@core/css/css'; import cssjs from '@core/css/css';
import { PageLink } from '@shared/models/page/page-link'; import { PageLink } from '@shared/models/page/page-link';
import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order'; import { Direction, SortOrder, sortOrderFromString } from '@shared/models/page/sort-order';
@ -230,7 +230,7 @@ export class TimeseriesTableWidgetComponent extends PageComponent implements OnI
const cssParser = new cssjs(); const cssParser = new cssjs();
cssParser.testMode = false; cssParser.testMode = false;
const namespace = 'ts-table-' + this.utils.hashCode(cssString); const namespace = 'ts-table-' + hashCode(cssString);
cssParser.cssPreviewNamespace = namespace; cssParser.cssPreviewNamespace = namespace;
cssParser.createStyleElement(namespace, cssString); cssParser.createStyleElement(namespace, cssString);
$(this.elementRef.nativeElement).addClass(namespace); $(this.elementRef.nativeElement).addClass(namespace);

3
ui-ngx/src/app/modules/home/components/widget/trip-animation/trip-animation.component.ts

@ -22,12 +22,11 @@ import { interpolateOnPointSegment } from 'leaflet-geometryutil';
import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, SecurityContext, ViewChild } from '@angular/core'; import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, SecurityContext, ViewChild } from '@angular/core';
import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2'; import { MapWidgetController, TbMapWidgetV2 } from '../lib/maps/map-widget2';
import { MapProviders } from '../lib/maps/map-models'; import { MapProviders } from '../lib/maps/map-models';
import { parseArray, parseWithTranslation, safeExecute, parseTemplate } from '@app/core/utils';
import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils'; import { initSchema, addToSchema, addGroupInfo } from '@app/core/schema-utils';
import { tripAnimationSchema } from '../lib/maps/schemes'; import { tripAnimationSchema } from '../lib/maps/schemes';
import { DomSanitizer } from '@angular/platform-browser'; import { DomSanitizer } from '@angular/platform-browser';
import { WidgetContext } from '@app/modules/home/models/widget-component.models'; import { WidgetContext } from '@app/modules/home/models/widget-component.models';
import { findAngle, getRatio } from '../lib/maps/maps-utils'; import { findAngle, getRatio, parseArray, parseWithTranslation, safeExecute } from '../lib/maps/maps-utils';
import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models'; import { JsonSettingsSchema, WidgetConfig } from '@shared/models/widget.models';
import moment from 'moment'; import moment from 'moment';

2
ui-ngx/src/app/modules/home/pages/dashboard/manage-dashboard-customers-dialog.component.html

@ -34,7 +34,7 @@
<span>{{ labelText | translate }}</span> <span>{{ labelText | translate }}</span>
<tb-entity-list <tb-entity-list
formControlName="assignedCustomerIds" formControlName="assignedCustomerIds"
required [required]="data.actionType !== 'manage'"
[entityType]="entityType.CUSTOMER"> [entityType]="entityType.CUSTOMER">
</tb-entity-list> </tb-entity-list>
</fieldset> </fieldset>

27
ui-ngx/src/app/modules/home/pages/dashboard/states/entity-state-controller.component.ts

@ -23,7 +23,7 @@ import { StateControllerComponent } from './state-controller.component';
import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service'; import { StatesControllerService } from '@home/pages/dashboard/states/states-controller.service';
import { EntityId } from '@app/shared/models/id/entity-id'; import { EntityId } from '@app/shared/models/id/entity-id';
import { UtilsService } from '@core/services/utils.service'; import { UtilsService } from '@core/services/utils.service';
import { base64toObj, objToBase64 } from '@app/core/utils'; import { base64toObj, insertVariable, isEmpty, objToBase64 } from '@app/core/utils';
import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service';
import { EntityService } from '@core/http/entity.service'; import { EntityService } from '@core/http/entity.service';
import { EntityType } from '@shared/models/entity-type.models'; import { EntityType } from '@shared/models/entity-type.models';
@ -123,11 +123,10 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
if (this.states && this.states[id]) { if (this.states && this.states[id]) {
this.resolveEntity(params).subscribe( this.resolveEntity(params).subscribe(
() => { () => {
const newState: StateObject = { this.stateObject[this.stateObject.length - 1] = {
id, id,
params params
}; };
this.stateObject[this.stateObject.length - 1] = newState;
this.gotoState(this.stateObject[this.stateObject.length - 1].id, true, openRightLayout); this.gotoState(this.stateObject[this.stateObject.length - 1].id, true, openRightLayout);
} }
); );
@ -203,14 +202,14 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
const targetParams = params && params.targetEntityParamName ? params[params.targetEntityParamName] : params; const targetParams = params && params.targetEntityParamName ? params[params.targetEntityParamName] : params;
const entityName = targetParams && targetParams.entityName ? targetParams.entityName : ''; const entityName = targetParams && targetParams.entityName ? targetParams.entityName : '';
const entityLabel = targetParams && targetParams.entityLabel ? targetParams.entityLabel : ''; const entityLabel = targetParams && targetParams.entityLabel ? targetParams.entityLabel : '';
result = this.utils.insertVariable(stateName, 'entityName', entityName); result = insertVariable(stateName, 'entityName', entityName);
result = this.utils.insertVariable(result, 'entityLabel', entityLabel); result = insertVariable(result, 'entityLabel', entityLabel);
for (const prop of Object.keys(params)) { for (const prop of Object.keys(params)) {
if (params[prop] && params[prop].entityName) { if (params[prop] && params[prop].entityName) {
result = this.utils.insertVariable(result, prop + ':entityName', params[prop].entityName); result = insertVariable(result, prop + ':entityName', params[prop].entityName);
} }
if (params[prop] && params[prop].entityLabel) { if (params[prop] && params[prop].entityLabel) {
result = this.utils.insertVariable(result, prop + ':entityLabel', params[prop].entityLabel); result = insertVariable(result, prop + ':entityLabel', params[prop].entityLabel);
} }
} }
} }
@ -276,20 +275,13 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
if (this.stateObject.length === 1) { if (this.stateObject.length === 1) {
const state = this.stateObject[0]; const state = this.stateObject[0];
const rootStateId = this.dashboardUtils.getRootStateId(this.states); const rootStateId = this.dashboardUtils.getRootStateId(this.states);
if (state.id === rootStateId && (!state.params || this.isEmpty(state.params))) { if (state.id === rootStateId && (!state.params || isEmpty(state.params))) {
return true; return true;
} }
} }
return false; return false;
} }
private isEmpty(obj: any): boolean {
for (const key of Object.keys(obj)) {
return !Object.prototype.hasOwnProperty.call(obj, key);
}
return true;
}
private resolveEntity(params: StateParams): Observable<void> { private resolveEntity(params: StateParams): Observable<void> {
if (params && params.targetEntityParamName) { if (params && params.targetEntityParamName) {
params = params[params.targetEntityParamName]; params = params[params.targetEntityParamName];
@ -313,10 +305,7 @@ export class EntityStateControllerComponent extends StateControllerComponent imp
} }
private isEntityResolved(params: StateParams): boolean { private isEntityResolved(params: StateParams): boolean {
if (!params.entityName || !params.entityName.length) { return !(!params.entityName || !params.entityName.length);
return false;
}
return true;
} }
private getStateObjById(id: string): StateObject { private getStateObjById(id: string): StateObject {

1
ui-ngx/src/app/shared/pipe/public-api.ts

@ -20,4 +20,3 @@ export * from './keyboard-shortcut.pipe';
export * from './milliseconds-to-time-string.pipe'; export * from './milliseconds-to-time-string.pipe';
export * from './nospace.pipe'; export * from './nospace.pipe';
export * from './truncate.pipe'; export * from './truncate.pipe';
export * from './template.pipe';

25
ui-ngx/src/app/shared/pipe/template.pipe.ts

@ -1,25 +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.
///
import { Pipe, PipeTransform } from '@angular/core';
import { parseTemplate, parseWithTranslation } from '@app/core/utils';
@Pipe({ name: 'tbParseTemplate' })
export class TbTemplatePipe implements PipeTransform {
transform(template, data): string {
return parseWithTranslation.parseTemplate(template, data);
}
}

3
ui-ngx/src/app/shared/shared.module.ts

@ -131,7 +131,6 @@ import { LedLightComponent } from '@shared/components/led-light.component';
import { TbJsonToStringDirective } from '@shared/components/directives/tb-json-to-string.directive'; import { TbJsonToStringDirective } from '@shared/components/directives/tb-json-to-string.directive';
import { JsonObjectEditDialogComponent } from '@shared/components/dialog/json-object-edit-dialog.component'; import { JsonObjectEditDialogComponent } from '@shared/components/dialog/json-object-edit-dialog.component';
import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component'; import { HistorySelectorComponent } from './components/time/history-selector/history-selector.component';
import { TbTemplatePipe } from '@shared/pipe/template.pipe';
import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-gateway-select.component'; import { EntityGatewaySelectComponent } from '@shared/components/entity/entity-gateway-select.component';
import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list.component'; import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list.component';
@ -215,7 +214,6 @@ import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list
HighlightPipe, HighlightPipe,
TruncatePipe, TruncatePipe,
TbJsonPipe, TbJsonPipe,
TbTemplatePipe,
KeyboardShortcutPipe, KeyboardShortcutPipe,
TbJsonToStringDirective, TbJsonToStringDirective,
JsonObjectEditDialogComponent, JsonObjectEditDialogComponent,
@ -377,7 +375,6 @@ import { QueueTypeListComponent } from '@shared/components/queue/queue-type-list
HighlightPipe, HighlightPipe,
TruncatePipe, TruncatePipe,
TbJsonPipe, TbJsonPipe,
TbTemplatePipe,
KeyboardShortcutPipe, KeyboardShortcutPipe,
TranslateModule, TranslateModule,
JsonObjectEditDialogComponent, JsonObjectEditDialogComponent,

6
ui/src/app/locale/locale.constant-de_DE.json

@ -1149,7 +1149,7 @@
"total": "Gesamt" "total": "Gesamt"
}, },
"login": { "login": {
"login": "Login", "login": "Anmelden",
"request-password-reset": "Passwortzurücksetzung anfordern", "request-password-reset": "Passwortzurücksetzung anfordern",
"reset-password": "Passwort zurücksetzen", "reset-password": "Passwort zurücksetzen",
"create-password": "Passwort erstellen", "create-password": "Passwort erstellen",
@ -1163,7 +1163,9 @@
"new-password": "Neues Passwort", "new-password": "Neues Passwort",
"new-password-again": "Neues Passwort wiederholen", "new-password-again": "Neues Passwort wiederholen",
"password-link-sent-message": "Der Link zum Zurücksetzen des Passworts wurde erfolgreich versendet!", "password-link-sent-message": "Der Link zum Zurücksetzen des Passworts wurde erfolgreich versendet!",
"email": "E-Mail" "email": "E-Mail",
"login-with": "Mit {{name}} anmelden",
"or": "oder"
}, },
"position": { "position": {
"top": "Oben", "top": "Oben",

4
ui/src/app/locale/locale.constant-el_GR.json

@ -1705,7 +1705,9 @@
"password-link-sent-message": "Ο σύνδεσμος επαναφοράς κωδικού πρόσβασης στάλθηκε με επιτυχία!", "password-link-sent-message": "Ο σύνδεσμος επαναφοράς κωδικού πρόσβασης στάλθηκε με επιτυχία!",
"email": "Email", "email": "Email",
"no-account": "Δεν έχετε λογαριασμό;", "no-account": "Δεν έχετε λογαριασμό;",
"create-account": "Δημιουργία λογαριασμού" "create-account": "Δημιουργία λογαριασμού",
"login-with": "Σύνδεση μέσω {{name}}",
"or": "ή"
}, },
"signup": { "signup": {
"firstname": "Όνομα", "firstname": "Όνομα",

4
ui/src/app/locale/locale.constant-es_ES.json

@ -1229,7 +1229,9 @@
"new-password": "Nueva contraseña", "new-password": "Nueva contraseña",
"new-password-again": "Repita la nueva contraseña", "new-password-again": "Repita la nueva contraseña",
"password-link-sent-message": "¡El enlace para el restablecer la contraseña fue enviado correctamente!", "password-link-sent-message": "¡El enlace para el restablecer la contraseña fue enviado correctamente!",
"email": "Correo electrónico" "email": "Correo electrónico",
"login-with": "Iniciar sesión con {{name}}",
"or": "o"
}, },
"position": { "position": {
"top": "Superior", "top": "Superior",

6
ui/src/app/locale/locale.constant-fr_FR.json

@ -1197,7 +1197,7 @@
"create-password": "Créer un mot de passe", "create-password": "Créer un mot de passe",
"email": "Email", "email": "Email",
"forgot-password": "Mot de passe oublié?", "forgot-password": "Mot de passe oublié?",
"login": "Login", "login": "Connexion",
"new-password": "Nouveau mot de passe", "new-password": "Nouveau mot de passe",
"new-password-again": "nouveau mot de passe", "new-password-again": "nouveau mot de passe",
"password-again": "Mot de passe à nouveau", "password-again": "Mot de passe à nouveau",
@ -1208,7 +1208,9 @@
"request-password-reset": "Demander la réinitialisation du mot de passe", "request-password-reset": "Demander la réinitialisation du mot de passe",
"reset-password": "Réinitialiser le mot de passe", "reset-password": "Réinitialiser le mot de passe",
"sign-in": "Veuillez vous connecter", "sign-in": "Veuillez vous connecter",
"username": "Nom d'utilisateur (courriel)" "username": "Nom d'utilisateur (courriel)",
"login-with": "Se connecter avec {{name}}",
"or": "ou"
}, },
"position": { "position": {
"bottom": "Bas", "bottom": "Bas",

6
ui/src/app/locale/locale.constant-it_IT.json

@ -1160,7 +1160,7 @@
"total": "totale" "total": "totale"
}, },
"login": { "login": {
"login": "Login", "login": "Accedi",
"request-password-reset": "Richiesta reset password", "request-password-reset": "Richiesta reset password",
"reset-password": "Reset Password", "reset-password": "Reset Password",
"create-password": "Crea Password", "create-password": "Crea Password",
@ -1174,7 +1174,9 @@
"new-password": "Nuova password", "new-password": "Nuova password",
"new-password-again": "Ripeti nuova password", "new-password-again": "Ripeti nuova password",
"password-link-sent-message": "Link reset password inviato con successo!", "password-link-sent-message": "Link reset password inviato con successo!",
"email": "Email" "email": "Email",
"login-with": "Accedi con {{name}}",
"or": "o"
}, },
"position": { "position": {
"top": "Alto", "top": "Alto",

4
ui/src/app/locale/locale.constant-ja_JA.json

@ -1024,7 +1024,9 @@
"new-password": "新しいパスワード", "new-password": "新しいパスワード",
"new-password-again": "新しいパスワードを再入力", "new-password-again": "新しいパスワードを再入力",
"password-link-sent-message": "パスワードリセットリンクが正常に送信されました!", "password-link-sent-message": "パスワードリセットリンクが正常に送信されました!",
"email": "Eメール" "email": "Eメール",
"login-with": "{{name}}でログイン",
"or": "または"
}, },
"position": { "position": {
"top": "上", "top": "上",

4
ui/src/app/locale/locale.constant-ko_KR.json

@ -934,7 +934,9 @@
"new-password": "새 비밀번호", "new-password": "새 비밀번호",
"new-password-again": "새 비밀번호 확인", "new-password-again": "새 비밀번호 확인",
"password-link-sent-message": "비밀번호 재설정 링크가 성공적으로 전송되었습니다!", "password-link-sent-message": "비밀번호 재설정 링크가 성공적으로 전송되었습니다!",
"email": "이메일" "email": "이메일",
"login-with": "{{name}}으로 로그인",
"or": "또는"
}, },
"position": { "position": {
"top": "상단", "top": "상단",

6
ui/src/app/locale/locale.constant-ro_RO.json

@ -1230,7 +1230,7 @@
} }
}, },
"login": { "login": {
"login": "Intră în Cont", "login": "Conectare",
"request-password-reset": "Solicită Resetarea Parolei", "request-password-reset": "Solicită Resetarea Parolei",
"reset-password": "Resetează Parolă", "reset-password": "Resetează Parolă",
"create-password": "Creează Parolă", "create-password": "Creează Parolă",
@ -1245,7 +1245,9 @@
"new-password": "Parolă nouă", "new-password": "Parolă nouă",
"new-password-again": "Verificare parolă nouă", "new-password-again": "Verificare parolă nouă",
"password-link-sent-message": "Ți-am trimis pe eMail un link pentru resetarea parolei", "password-link-sent-message": "Ți-am trimis pe eMail un link pentru resetarea parolei",
"email": "eMail" "email": "eMail",
"login-with": "Conectare cu {{name}}",
"or": "sau"
}, },
"position": { "position": {
"top": "Sus", "top": "Sus",

6
ui/src/app/locale/locale.constant-tr_TR.json

@ -1090,7 +1090,7 @@
"total": "toplam" "total": "toplam"
}, },
"login": { "login": {
"login": "Oturum aç", "login": "Giriş Yap",
"request-password-reset": "Parola Sıfırlama İsteği Gönder", "request-password-reset": "Parola Sıfırlama İsteği Gönder",
"reset-password": "Parola Sıfırla", "reset-password": "Parola Sıfırla",
"create-password": "Parola Oluştur", "create-password": "Parola Oluştur",
@ -1104,7 +1104,9 @@
"new-password": "Yeni parola", "new-password": "Yeni parola",
"new-password-again": "Yeni parola tekrarı", "new-password-again": "Yeni parola tekrarı",
"password-link-sent-message": "Parola sıfırlama e-postası başarıyla gönderildi!", "password-link-sent-message": "Parola sıfırlama e-postası başarıyla gönderildi!",
"email": "E-posta" "email": "E-posta",
"login-with": "{{name}} ile Giriş Yap",
"or": "ya da"
}, },
"position": { "position": {
"top": "Üst", "top": "Üst",

Loading…
Cancel
Save