diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
index 5d1262f233..e44c28dc39 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
@@ -52,6 +52,8 @@ import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.List;
import java.util.NoSuchElementException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
import java.util.stream.Collectors;
import static org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent.Type.CHILD_REMOVED;
@@ -96,11 +98,13 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
@Lazy
private ClusterRoutingService routingService;
+ private ExecutorService reconnectExecutorService;
+
private CuratorFramework client;
private PathChildrenCache cache;
private String nodePath;
- private volatile boolean stopped = false;
+ private volatile boolean stopped = true;
@PostConstruct
public void init() {
@@ -110,9 +114,15 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
Assert.notNull(zkConnectionTimeout, MiscUtils.missingProperty("zk.connection_timeout_ms"));
Assert.notNull(zkSessionTimeout, MiscUtils.missingProperty("zk.session_timeout_ms"));
+ reconnectExecutorService = Executors.newSingleThreadExecutor();
+
log.info("Initializing discovery service using ZK connect string: {}", zkUrl);
zkNodesDir = zkDir + "/nodes";
+ initZkClient();
+ }
+
+ private void initZkClient() {
try {
client = CuratorFrameworkFactory.newClient(zkUrl, zkSessionTimeout, zkConnectionTimeout, new RetryForever(zkRetryInterval));
client.start();
@@ -120,6 +130,8 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
cache = new PathChildrenCache(client, zkNodesDir, true);
cache.getListenable().addListener(this);
cache.start();
+ stopped = false;
+ log.info("ZK client connected");
} catch (Exception e) {
log.error("Failed to connect to ZK: {}", e.getMessage(), e);
CloseableUtils.closeQuietly(cache);
@@ -128,12 +140,20 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
}
}
- @PreDestroy
- public void destroy() {
+ private void destroyZkClient() {
stopped = true;
- unpublishCurrentServer();
+ try {
+ unpublishCurrentServer();
+ } catch (Exception e) {}
CloseableUtils.closeQuietly(cache);
CloseableUtils.closeQuietly(client);
+ log.info("ZK client disconnected");
+ }
+
+ @PreDestroy
+ public void destroy() {
+ destroyZkClient();
+ reconnectExecutorService.shutdownNow();
log.info("Stopped discovery service");
}
@@ -180,20 +200,21 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
return (client, newState) -> {
log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState);
if (newState == ConnectionState.LOST) {
- reconnect();
+ reconnectExecutorService.submit(this::reconnect);
}
};
}
- private boolean reconnectInProgress = false;
+ private volatile boolean reconnectInProgress = false;
private synchronized void reconnect() {
if (!reconnectInProgress) {
reconnectInProgress = true;
try {
- client.blockUntilConnected();
+ destroyZkClient();
+ initZkClient();
publishCurrentServer();
- } catch (InterruptedException e) {
+ } catch (Exception e) {
log.error("Failed to reconnect to ZK: {}", e.getMessage(), e);
} finally {
reconnectInProgress = false;
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 72b8159407..1069f9d6e6 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -273,23 +273,17 @@ updates:
spring.mvc.cors:
mappings:
# Intercept path
- "/api/auth/**":
+ "[/api/**]":
#Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled.
allowed-origins: "*"
#Comma-separated list of methods to allow. '*' allows all methods.
- allowed-methods: "POST,GET,OPTIONS"
+ allowed-methods: "*"
#Comma-separated list of headers to allow in a request. '*' allows all headers.
allowed-headers: "*"
#How long, in seconds, the response from a pre-flight request can be cached by clients.
max-age: "1800"
#Set whether credentials are supported. When not set, credentials are not supported.
allow-credentials: "true"
- "/api/v1/**":
- allowed-origins: "*"
- allowed-methods: "*"
- allowed-headers: "*"
- max-age: "1800"
- allow-credentials: "true"
# spring serve gzip compressed static resources
spring.resources.chain:
diff --git a/pom.xml b/pom.xml
index b0bf2a51c3..59cc98aef0 100755
--- a/pom.xml
+++ b/pom.xml
@@ -50,7 +50,7 @@
1.5.0
2.5
1.4
- 2.9.7
+ 2.9.8
2.2.6
2.11
2.4.2
@@ -59,7 +59,7 @@
1.7
2.0
1.4.3
- 4.0.1
+ 4.2.0
3.6.1
1.16.1
1.16.18
diff --git a/ui/src/app/components/contact.directive.js b/ui/src/app/components/contact.directive.js
index 93912882ed..cab6f52111 100644
--- a/ui/src/app/components/contact.directive.js
+++ b/ui/src/app/components/contact.directive.js
@@ -283,7 +283,7 @@ function Contact($compile, $templateCache) {
"Austria": "[0-9]{4}",
"Belgium": "[0-9]{4}",
"Brazil": "[0-9]{5}[\\-]?[0-9]{3}",
- "Canada": "[A-Za-z][0-9][A-Za-z] [0-9][A-Za-z][0-9]",
+ "Canada": "^(?!.*[DFIOQU])[A-VXY][0-9][A-Z][ -]?[0-9][A-Z][0-9]$",
"Denmark": "[0-9]{3,4}",
"Faroe Islands": "[0-9]{3,4}",
"Netherlands": "[1-9][0-9]{3}\\s?[a-zA-Z]{2}",
diff --git a/ui/src/app/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index dfef502f09..d1fe2c88c9 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -1295,7 +1295,8 @@
"metadata-required": "Metadata entries can't be empty.",
"output": "Output",
"test": "Test",
- "help": "Help"
+ "help": "Help",
+ "reset-debug-mode": "Reset debug mode in all nodes"
},
"tenant": {
"tenant": "Tenant",
diff --git a/ui/src/app/locale/locale.constant-ru_RU.json b/ui/src/app/locale/locale.constant-ru_RU.json
index 3ad4371cc1..2d05051de8 100644
--- a/ui/src/app/locale/locale.constant-ru_RU.json
+++ b/ui/src/app/locale/locale.constant-ru_RU.json
@@ -1288,7 +1288,8 @@
"metadata-required": "Метаданные объекта не могут быть пустыми.",
"output": "Выход",
"test": "Протестировать",
- "help": "Помощь"
+ "help": "Помощь",
+ "reset-debug-mode": "Сбросить режим отладки во всех правилах"
},
"tenant": {
"tenant": "Владелец",
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index 3744b63198..195dcffef5 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -108,6 +108,9 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
vm.objectsSelected = objectsSelected;
vm.deleteSelected = deleteSelected;
+ vm.isDebugModeEnabled = isDebugModeEnabled;
+ vm.resetDebugModeInAllNodes = resetDebugModeInAllNodes;
+
vm.triggerResize = triggerResize;
vm.openRuleChainContextMenu = openRuleChainContextMenu;
@@ -1342,6 +1345,19 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
vm.modelservice.deleteSelected();
}
+ function isDebugModeEnabled() {
+ var res = $filter('filter')(vm.ruleChainModel.nodes, {debugMode: true});
+ return (res && res.length);
+ }
+
+ function resetDebugModeInAllNodes() {
+ vm.ruleChainModel.nodes.forEach((node) => {
+ if (node.component.type != types.ruleNodeType.INPUT.value && node.component.type != types.ruleNodeType.RULE_CHAIN.value) {
+ node.debugMode = false;
+ }
+ });
+ }
+
function triggerResize() {
var w = angular.element($window);
w.triggerHandler('resize');
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index 80425e59c8..4a20947497 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -223,6 +223,15 @@
+