Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/1598/head
ShvaykaD 7 years ago
parent
commit
e40a6227c2
  1. 16
      application/src/main/data/json/system/widget_bundles/cards.json
  2. 16
      application/src/main/data/json/system/widget_bundles/maps.json
  3. 22
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  4. 68
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  5. 13
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  6. 2
      application/src/main/java/org/thingsboard/server/config/AuditLogLevelProperties.java
  7. 46
      application/src/main/java/org/thingsboard/server/config/SchedulingConfiguration.java
  8. 128
      application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java
  9. 2
      application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java
  10. 4
      application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java
  11. 2
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  12. 2
      application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java
  13. 2
      application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java
  14. 31
      application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java
  15. 7
      application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java
  16. 1
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  17. 8
      application/src/main/resources/thingsboard.yml
  18. 4
      application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java
  19. 2
      application/src/test/java/org/thingsboard/server/mqtt/telemetry/AbstractMqttTelemetryIntegrationTest.java
  20. 14
      dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java
  21. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  22. 4
      dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java
  23. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
  24. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/sink/DummyAuditLogSink.java
  25. 16
      dao/src/main/java/org/thingsboard/server/dao/audit/sink/ElasticsearchAuditLogSink.java
  26. 43
      dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java
  27. 2
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java
  28. 2
      dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java
  29. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java
  30. 2
      dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java
  31. 9
      dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java
  32. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
  33. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java
  34. 8
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java
  35. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java
  36. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
  37. 1
      dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java
  38. 8
      dao/src/test/resources/application-test.properties
  39. 1
      dao/src/test/resources/sql-test.properties
  40. 5
      k8s/.env
  41. 100
      k8s/README.md
  42. 164
      k8s/cassandra.yml
  43. 43
      k8s/database-setup.yml
  44. 18
      k8s/k8s-delete-all.sh
  45. 21
      k8s/k8s-delete-resources.sh
  46. 26
      k8s/k8s-deploy-resources.sh
  47. 93
      k8s/k8s-install-tb.sh
  48. 43
      k8s/k8s-upgrade-tb.sh
  49. 95
      k8s/postgres.yml
  50. 65
      k8s/tb-coap-transport-configmap.yml
  51. 65
      k8s/tb-http-transport-configmap.yml
  52. 65
      k8s/tb-mqtt-transport-configmap.yml
  53. 22
      k8s/tb-namespace.yml
  54. 28
      k8s/tb-node-cassandra-configmap.yml
  55. 67
      k8s/tb-node-configmap.yml
  56. 31
      k8s/tb-node-postgres-configmap.yml
  57. 608
      k8s/thingsboard.yml
  58. 2
      msa/js-executor/package-lock.json
  59. 2
      msa/web-ui/package-lock.json
  60. 48
      pom.xml
  61. 22
      ui/package-lock.json
  62. 4
      ui/package.json
  63. 8
      ui/src/app/api/entity-relation.service.js
  64. 19
      ui/src/app/api/user.service.js
  65. 4
      ui/src/app/api/widget.service.js
  66. 3
      ui/src/app/app.js
  67. 9
      ui/src/app/components/json-form.directive.js
  68. 4
      ui/src/app/components/json-form.tpl.html
  69. 206
      ui/src/app/components/nav-tree.directive.js
  70. 346
      ui/src/app/components/nav-tree.scss
  71. 18
      ui/src/app/components/nav-tree.tpl.html
  72. 36
      ui/src/app/components/react/json-form-ace-editor.jsx
  73. 7
      ui/src/app/components/react/json-form-ace-editor.scss
  74. 2
      ui/src/app/components/react/json-form-array.jsx
  75. 2
      ui/src/app/components/react/json-form-fieldset.jsx
  76. 3
      ui/src/app/components/react/json-form-react.jsx
  77. 18
      ui/src/app/components/react/json-form-schema-form.jsx
  78. 18
      ui/src/app/components/react/json-form.scss
  79. 2
      ui/src/app/layout/index.js
  80. 3
      ui/src/app/locale/locale.constant-en_US.json
  81. 8
      ui/src/app/locale/translate-handler.js
  82. 530
      ui/src/app/widget/lib/entities-hierarchy-widget.js
  83. 110
      ui/src/app/widget/lib/entities-hierarchy-widget.scss
  84. 51
      ui/src/app/widget/lib/entities-hierarchy-widget.tpl.html
  85. 14
      ui/src/app/widget/lib/google-map.js
  86. 8
      ui/src/app/widget/lib/map-widget2.js
  87. 9
      ui/src/app/widget/lib/tencent-map.js
  88. 700
      ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js
  89. 106
      ui/src/app/widget/lib/tripAnimation/trip-animation-widget.scss
  90. 54
      ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html
  91. BIN
      ui/src/png/jstree/32px.png
  92. BIN
      ui/src/png/jstree/40px.png

16
application/src/main/data/json/system/widget_bundles/cards.json

File diff suppressed because one or more lines are too long

16
application/src/main/data/json/system/widget_bundles/maps.json

File diff suppressed because one or more lines are too long

22
application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java

@ -118,17 +118,23 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
this.rpcSubscriptions = new HashMap<>();
this.toDeviceRpcPendingMap = new HashMap<>();
this.toServerRpcPendingMap = new HashMap<>();
initAttributes();
restoreSessions();
if (initAttributes()) {
restoreSessions();
}
}
private void initAttributes() {
private boolean initAttributes() {
Device device = systemContext.getDeviceService().findDeviceById(tenantId, deviceId);
this.deviceName = device.getName();
this.deviceType = device.getType();
this.defaultMetaData = new TbMsgMetaData();
this.defaultMetaData.putValue("deviceName", deviceName);
this.defaultMetaData.putValue("deviceType", deviceType);
if (device != null) {
this.deviceName = device.getName();
this.deviceType = device.getType();
this.defaultMetaData = new TbMsgMetaData();
this.defaultMetaData.putValue("deviceName", deviceName);
this.defaultMetaData.putValue("deviceType", deviceType);
return true;
} else {
return false;
}
}
void processRpcRequest(ActorContext context, ToDeviceRpcRequestActorMsg msg) {

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

@ -91,17 +91,19 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
public void start(ActorContext context) {
if (!started) {
RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
ruleChainName = ruleChain.getName();
List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
// Creating and starting the actors;
for (RuleNode ruleNode : ruleNodeList) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
if (ruleChain != null) {
ruleChainName = ruleChain.getName();
List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
log.trace("[{}][{}] Starting rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
// Creating and starting the actors;
for (RuleNode ruleNode : ruleNodeList) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
}
initRoutes(ruleChain, ruleNodeList);
started = true;
}
initRoutes(ruleChain, ruleNodeList);
started = true;
} else {
onUpdate(context);
}
@ -110,31 +112,33 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
@Override
public void onUpdate(ActorContext context) {
RuleChain ruleChain = service.findRuleChainById(tenantId, entityId);
ruleChainName = ruleChain.getName();
List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
for (RuleNode ruleNode : ruleNodeList) {
RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
if (existing == null) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
} else {
log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
existing.setSelf(ruleNode);
existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
if (ruleChain != null) {
ruleChainName = ruleChain.getName();
List<RuleNode> ruleNodeList = service.getRuleChainNodes(tenantId, entityId);
log.trace("[{}][{}] Updating rule chain with {} nodes", tenantId, entityId, ruleNodeList.size());
for (RuleNode ruleNode : ruleNodeList) {
RuleNodeCtx existing = nodeActors.get(ruleNode.getId());
if (existing == null) {
log.trace("[{}][{}] Creating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
ActorRef ruleNodeActor = createRuleNodeActor(context, ruleNode);
nodeActors.put(ruleNode.getId(), new RuleNodeCtx(tenantId, self, ruleNodeActor, ruleNode));
} else {
log.trace("[{}][{}] Updating rule node [{}]: {}", entityId, ruleNode.getId(), ruleNode.getName(), ruleNode);
existing.setSelf(ruleNode);
existing.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, existing.getSelf().getId(), ComponentLifecycleEvent.UPDATED), self);
}
}
}
Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
removedRules.forEach(ruleNodeId -> {
log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
});
Set<RuleNodeId> existingNodes = ruleNodeList.stream().map(RuleNode::getId).collect(Collectors.toSet());
List<RuleNodeId> removedRules = nodeActors.keySet().stream().filter(node -> !existingNodes.contains(node)).collect(Collectors.toList());
removedRules.forEach(ruleNodeId -> {
log.trace("[{}][{}] Removing rule node [{}]", tenantId, entityId, ruleNodeId);
RuleNodeCtx removed = nodeActors.remove(ruleNodeId);
removed.getSelfActor().tell(new ComponentLifecycleMsg(tenantId, removed.getSelf().getId(), ComponentLifecycleEvent.DELETED), self);
});
initRoutes(ruleChain, ruleNodeList);
initRoutes(ruleChain, ruleNodeList);
}
}
@Override

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

@ -55,7 +55,9 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
@Override
public void start(ActorContext context) throws Exception {
tbNode = initComponent(ruleNode);
state = ComponentLifecycleState.ACTIVE;
if (tbNode != null) {
state = ComponentLifecycleState.ACTIVE;
}
}
@Override
@ -118,9 +120,12 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor<RuleNod
}
private TbNode initComponent(RuleNode ruleNode) throws Exception {
Class<?> componentClazz = Class.forName(ruleNode.getType());
TbNode tbNode = (TbNode) (componentClazz.newInstance());
tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration()));
TbNode tbNode = null;
if (ruleNode != null) {
Class<?> componentClazz = Class.forName(ruleNode.getType());
tbNode = (TbNode) (componentClazz.newInstance());
tbNode.init(defaultCtx, new TbNodeConfiguration(ruleNode.getConfiguration()));
}
return tbNode;
}

2
application/src/main/java/org/thingsboard/server/config/AuditLogLevelProperties.java

@ -22,7 +22,7 @@ import java.util.HashMap;
import java.util.Map;
@Configuration
@ConfigurationProperties(prefix = "audit_log.logging_level")
@ConfigurationProperties(prefix = "audit-log.logging-level")
public class AuditLogLevelProperties {
private Map<String, String> mask = new HashMap<>();

46
application/src/main/java/org/thingsboard/server/config/SchedulingConfiguration.java

@ -0,0 +1,46 @@
/**
* Copyright © 2016-2019 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.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@Configuration
@EnableScheduling
public class SchedulingConfiguration implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskScheduler());
}
@Bean(destroyMethod="shutdown")
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler threadPoolScheduler = new ThreadPoolTaskScheduler();
threadPoolScheduler.setThreadNamePrefix("TB-Scheduling-");
threadPoolScheduler.setPoolSize(Runtime.getRuntime().availableProcessors());
threadPoolScheduler.setRemoveOnCancelPolicy(true);
return threadPoolScheduler;
}
}

128
application/src/main/java/org/thingsboard/server/config/ThingsboardMessageConfiguration.java

@ -15,11 +15,28 @@
*/
package org.thingsboard.server.config;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.ExtendedProperties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.ResourceNotFoundException;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;
import org.springframework.context.MessageSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.support.ResourceBundleMessageSource;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.StringUtils;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
@Configuration
public class ThingsboardMessageConfiguration {
@ -32,5 +49,114 @@ public class ThingsboardMessageConfiguration {
messageSource.setDefaultEncoding("UTF-8");
return messageSource;
}
private static final String DEFAULT_RESOURCE_LOADER_PATH = "classpath:/templates/";
private ResourceLoader resourceLoader = new DefaultResourceLoader();
@Bean
public VelocityEngine velocityEngine() {
VelocityEngine velocityEngine = new VelocityEngine();
try {
Resource resource = resourceLoader.getResource(DEFAULT_RESOURCE_LOADER_PATH);
File file = resource.getFile();
velocityEngine.setProperty(RuntimeConstants.RESOURCE_LOADER, "file");
velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_CACHE, "true");
velocityEngine.setProperty(RuntimeConstants.FILE_RESOURCE_LOADER_PATH, file.getAbsolutePath());
} catch (IOException e) {
initSpringResourceLoader(velocityEngine, DEFAULT_RESOURCE_LOADER_PATH);
}
velocityEngine.init();
return velocityEngine;
}
private void initSpringResourceLoader(VelocityEngine velocityEngine, String resourceLoaderPath) {
velocityEngine.setProperty(
RuntimeConstants.RESOURCE_LOADER, SpringResourceLoader.NAME);
velocityEngine.setProperty(
SpringResourceLoader.SPRING_RESOURCE_LOADER_CLASS, SpringResourceLoader.class.getName());
velocityEngine.setProperty(
SpringResourceLoader.SPRING_RESOURCE_LOADER_CACHE, "true");
velocityEngine.setApplicationAttribute(
SpringResourceLoader.SPRING_RESOURCE_LOADER, resourceLoader);
velocityEngine.setApplicationAttribute(
SpringResourceLoader.SPRING_RESOURCE_LOADER_PATH, resourceLoaderPath);
}
@Slf4j
static class SpringResourceLoader extends org.apache.velocity.runtime.resource.loader.ResourceLoader {
public static final String NAME = "spring";
public static final String SPRING_RESOURCE_LOADER_CLASS = "spring.resource.loader.class";
public static final String SPRING_RESOURCE_LOADER_CACHE = "spring.resource.loader.cache";
public static final String SPRING_RESOURCE_LOADER = "spring.resource.loader";
public static final String SPRING_RESOURCE_LOADER_PATH = "spring.resource.loader.path";
private org.springframework.core.io.ResourceLoader resourceLoader;
private String[] resourceLoaderPaths;
@Override
public void init(ExtendedProperties configuration) {
this.resourceLoader = (org.springframework.core.io.ResourceLoader)
this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER);
String resourceLoaderPath = (String) this.rsvc.getApplicationAttribute(SPRING_RESOURCE_LOADER_PATH);
if (this.resourceLoader == null) {
throw new IllegalArgumentException(
"'resourceLoader' application attribute must be present for SpringResourceLoader");
}
if (resourceLoaderPath == null) {
throw new IllegalArgumentException(
"'resourceLoaderPath' application attribute must be present for SpringResourceLoader");
}
this.resourceLoaderPaths = StringUtils.commaDelimitedListToStringArray(resourceLoaderPath);
for (int i = 0; i < this.resourceLoaderPaths.length; i++) {
String path = this.resourceLoaderPaths[i];
if (!path.endsWith("/")) {
this.resourceLoaderPaths[i] = path + "/";
}
}
if (log.isInfoEnabled()) {
log.info("SpringResourceLoader for Velocity: using resource loader [" + this.resourceLoader +
"] and resource loader paths " + Arrays.asList(this.resourceLoaderPaths));
}
}
@Override
public InputStream getResourceStream(String source) throws ResourceNotFoundException {
if (log.isDebugEnabled()) {
log.debug("Looking for Velocity resource with name [" + source + "]");
}
for (String resourceLoaderPath : this.resourceLoaderPaths) {
org.springframework.core.io.Resource resource =
this.resourceLoader.getResource(resourceLoaderPath + source);
try {
return resource.getInputStream();
}
catch (IOException ex) {
if (log.isDebugEnabled()) {
log.debug("Could not find Velocity resource: " + resource);
}
}
}
throw new ResourceNotFoundException(
"Could not find resource [" + source + "] in Spring resource loader path");
}
@Override
public boolean isSourceModified(org.apache.velocity.runtime.resource.Resource resource) {
return false;
}
@Override
public long getLastModified(org.apache.velocity.runtime.resource.Resource resource) {
return 0;
}
}
}

2
application/src/main/java/org/thingsboard/server/config/ThingsboardSecurityConfiguration.java

@ -57,7 +57,7 @@ import java.util.List;
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled=true)
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
@Order(SecurityProperties.BASIC_AUTH_ORDER)
public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapter {
public static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization";

4
application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java

@ -58,7 +58,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
Map<String, Object> attributes) throws Exception {
SecurityUser user = null;
try {
user = getCurrentUser();
@ -73,7 +73,7 @@ public class WebSocketConfiguration implements WebSocketConfigurer {
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
Exception exception) {
//Do nothing
}
});

2
application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java

@ -392,4 +392,4 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
}
}
}
}

2
application/src/main/java/org/thingsboard/server/service/install/CassandraAbstractDatabaseSchemaService.java

@ -17,6 +17,7 @@ package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.thingsboard.server.dao.cassandra.CassandraInstallCluster;
import org.thingsboard.server.service.install.cql.CQLStatementsParser;
@ -30,6 +31,7 @@ public abstract class CassandraAbstractDatabaseSchemaService implements Database
private static final String CASSANDRA_DIR = "cassandra";
@Autowired
@Qualifier("CassandraInstallCluster")
private CassandraInstallCluster cluster;
@Autowired

2
application/src/main/java/org/thingsboard/server/service/install/CassandraDatabaseUpgradeService.java

@ -18,6 +18,7 @@ package org.thingsboard.server.service.install;
import com.datastax.driver.core.KeyspaceMetadata;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
@ -65,6 +66,7 @@ public class CassandraDatabaseUpgradeService implements DatabaseUpgradeService {
private CassandraCluster cluster;
@Autowired
@Qualifier("CassandraInstallCluster")
private CassandraInstallCluster installCluster;
@Autowired

31
application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java

@ -18,7 +18,9 @@ package org.thingsboard.server.service.mail;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.VelocityException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.MessageSource;
@ -26,7 +28,6 @@ import org.springframework.core.NestedRuntimeException;
import org.springframework.mail.javamail.JavaMailSenderImpl;
import org.springframework.mail.javamail.MimeMessageHelper;
import org.springframework.stereotype.Service;
import org.springframework.ui.velocity.VelocityEngineUtils;
import org.thingsboard.rule.engine.api.MailService;
import org.thingsboard.server.common.data.AdminSettings;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
@ -39,6 +40,8 @@ import org.thingsboard.server.dao.settings.AdminSettingsService;
import javax.annotation.PostConstruct;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.StringWriter;
import java.io.Writer;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
@ -126,7 +129,7 @@ public class DefaultMailService implements MailService {
Map<String, Object> model = new HashMap<String, Object>();
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
String message = mergeTemplateIntoString(this.engine,
"test.vm", UTF_8, model);
sendMail(testMailSender, mailFrom, email, subject, message);
@ -141,7 +144,7 @@ public class DefaultMailService implements MailService {
model.put("activationLink", activationLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
String message = mergeTemplateIntoString(this.engine,
"activation.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
@ -156,7 +159,7 @@ public class DefaultMailService implements MailService {
model.put("loginLink", loginLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
String message = mergeTemplateIntoString(this.engine,
"account.activated.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
@ -171,7 +174,7 @@ public class DefaultMailService implements MailService {
model.put("passwordResetLink", passwordResetLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
String message = mergeTemplateIntoString(this.engine,
"reset.password.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
@ -186,7 +189,7 @@ public class DefaultMailService implements MailService {
model.put("loginLink", loginLink);
model.put(TARGET_EMAIL, email);
String message = VelocityEngineUtils.mergeTemplateIntoString(this.engine,
String message = mergeTemplateIntoString(this.engine,
"password.was.reset.vm", UTF_8, model);
sendMail(mailSender, mailFrom, email, subject, message);
@ -225,6 +228,22 @@ public class DefaultMailService implements MailService {
}
}
private static String mergeTemplateIntoString(VelocityEngine velocityEngine, String templateLocation,
String encoding, Map<String, Object> model) throws VelocityException {
StringWriter result = new StringWriter();
mergeTemplate(velocityEngine, templateLocation, encoding, model, result);
return result.toString();
}
private static void mergeTemplate(
VelocityEngine velocityEngine, String templateLocation, String encoding,
Map<String, Object> model, Writer writer) throws VelocityException {
VelocityContext velocityContext = new VelocityContext(model);
velocityEngine.mergeTemplate(templateLocation, encoding, velocityContext, writer);
}
protected ThingsboardException handleException(Exception exception) {
String message;
if (exception instanceof NestedRuntimeException) {

7
application/src/main/java/org/thingsboard/server/service/session/DefaultDeviceSessionCacheService.java

@ -47,11 +47,4 @@ public class DefaultDeviceSessionCacheService implements DeviceSessionCacheServi
log.debug("[{}] Pushing session data to cache: {}", deviceId, sessions);
return sessions;
}
public static void main (String[] args){
UUID uuid = UUID.fromString("d5db434e-9cd2-4903-8b3b-421b2d93664d");
System.out.println(uuid.getMostSignificantBits());
System.out.println(uuid.getLeastSignificantBits());
}
}

1
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java

@ -27,7 +27,6 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.WebSocketSession;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;

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

@ -293,11 +293,13 @@ spring.mvc.cors:
# spring serve gzip compressed static resources
spring.resources.chain:
gzipped: "true"
compressed: "true"
strategy:
content:
enabled: "true"
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true"
# HSQLDB DAO Configuration
spring:
data:
@ -331,7 +333,7 @@ spring:
# password: "${SPRING_DATASOURCE_PASSWORD:postgres}"
# Audit log parameters
audit_log:
audit-log:
# Enable/disable audit log functionality.
enabled: "${AUDIT_LOG_ENABLED:true}"
# Specify partitioning size for audit log by tenant id storage. Example MINUTES, HOURS, DAYS, MONTHS
@ -340,7 +342,7 @@ audit_log:
default_query_period: "${AUDIT_LOG_DEFAULT_QUERY_PERIOD:30}"
# Logging levels per each entity type.
# Allowed values: OFF (disable), W (log write operations), RW (log read and write operations)
logging_level:
logging-level:
mask:
"device": "${AUDIT_LOG_MASK_DEVICE:W}"
"asset": "${AUDIT_LOG_MASK_ASSET:W}"

4
application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java

@ -128,7 +128,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(),
@ -183,7 +183,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC
String accessToken = deviceCredentials.getCredentialsId();
assertNotNull(accessToken);
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}";
String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1},\"timeout\": 6000}";
String deviceId = savedDevice.getId().getId().toString();
doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(),

2
application/src/test/java/org/thingsboard/server/mqtt/telemetry/AbstractMqttTelemetryIntegrationTest.java

@ -111,7 +111,7 @@ public abstract class AbstractMqttTelemetryIntegrationTest extends AbstractContr
client.subscribe("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE.value());
String payload = "{\"key\":\"value\"}";
String result = doPostAsync("/api/plugins/telemetry/" + savedDevice.getId() + "/SHARED_SCOPE", payload, String.class, status().isOk());
latch.await(3, TimeUnit.SECONDS);
latch.await(10, TimeUnit.SECONDS);
assertEquals(payload, callback.getPayload());
assertEquals(MqttQoS.AT_MOST_ONCE.value(), callback.getQoS());
}

14
dao/src/main/java/org/thingsboard/server/dao/DaoUtil.java

@ -18,11 +18,7 @@ package org.thingsboard.server.dao;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.dao.model.ToData;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.*;
public abstract class DaoUtil {
@ -50,6 +46,14 @@ public abstract class DaoUtil {
return object;
}
public static <T> T getData(Optional<? extends ToData<T>> data) {
T object = null;
if (data.isPresent()) {
object = data.get().toData();
}
return object;
}
public static UUID getId(UUIDBased idBased) {
UUID id = null;
if (idBased != null) {

2
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java

@ -60,7 +60,7 @@ import static org.thingsboard.server.dao.service.Validator.validateId;
@Slf4j
@Service
@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "true")
@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "true")
public class AuditLogServiceImpl implements AuditLogService {
private static final ObjectMapper objectMapper = new ObjectMapper();

4
dao/src/main/java/org/thingsboard/server/dao/audit/CassandraAuditLogDao.java

@ -88,11 +88,11 @@ public class CassandraAuditLogDao extends CassandraAbstractSearchTimeDao<AuditLo
protected ExecutorService readResultsProcessingExecutor;
@Value("${audit_log.by_tenant_partitioning}")
@Value("${audit-log.by_tenant_partitioning}")
private String partitioning;
private TsPartitionDate tsFormat;
@Value("${audit_log.default_query_period}")
@Value("${audit-log.default_query_period}")
private Integer defaultQueryPeriodInDays;
private PreparedStatement partitionInsertStmt;

2
dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java

@ -33,7 +33,7 @@ import org.thingsboard.server.common.data.page.TimePageLink;
import java.util.List;
@Service
@ConditionalOnProperty(prefix = "audit_log", value = "enabled", havingValue = "false")
@ConditionalOnProperty(prefix = "audit-log", value = "enabled", havingValue = "false")
public class DummyAuditLogServiceImpl implements AuditLogService {
@Override

2
dao/src/main/java/org/thingsboard/server/dao/audit/sink/DummyAuditLogSink.java

@ -20,7 +20,7 @@ import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.audit.AuditLog;
@Component
@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "none")
@ConditionalOnProperty(prefix = "audit-log.sink", value = "type", havingValue = "none")
public class DummyAuditLogSink implements AuditLogSink {
@Override

16
dao/src/main/java/org/thingsboard/server/dao/audit/sink/ElasticsearchAuditLogSink.java

@ -44,7 +44,7 @@ import java.time.format.DateTimeFormatter;
import java.util.Collections;
@Component
@ConditionalOnProperty(prefix = "audit_log.sink", value = "type", havingValue = "elasticsearch")
@ConditionalOnProperty(prefix = "audit-log.sink", value = "type", havingValue = "elasticsearch")
@Slf4j
public class ElasticsearchAuditLogSink implements AuditLogSink {
@ -54,19 +54,19 @@ public class ElasticsearchAuditLogSink implements AuditLogSink {
private final ObjectMapper mapper = new ObjectMapper();
@Value("${audit_log.sink.index_pattern}")
@Value("${audit-log.sink.index_pattern}")
private String indexPattern;
@Value("${audit_log.sink.scheme_name}")
@Value("${audit-log.sink.scheme_name}")
private String schemeName;
@Value("${audit_log.sink.host}")
@Value("${audit-log.sink.host}")
private String host;
@Value("${audit_log.sink.port}")
@Value("${audit-log.sink.port}")
private int port;
@Value("${audit_log.sink.user_name}")
@Value("${audit-log.sink.user_name}")
private String userName;
@Value("${audit_log.sink.password}")
@Value("${audit-log.sink.password}")
private String password;
@Value("${audit_log.sink.date_format}")
@Value("${audit-log.sink.date_format}")
private String dateFormat;
private RestClient restClient;

43
dao/src/main/java/org/thingsboard/server/dao/cache/TBRedisCacheConfiguration.java

@ -19,14 +19,37 @@ import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.CacheManager;
import org.springframework.cache.interceptor.SimpleKey;
import org.springframework.core.convert.ConversionService;
import org.springframework.core.convert.TypeDescriptor;
import org.springframework.core.convert.converter.ConditionalGenericConverter;
import org.springframework.core.convert.converter.Converter;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.interceptor.KeyGenerator;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.ConverterRegistry;
import org.springframework.data.convert.ReadingConverter;
import org.springframework.data.convert.WritingConverter;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.convert.RedisCustomConversions;
import org.springframework.format.support.DefaultFormattingConversionService;
import org.springframework.lang.Nullable;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.UUID;
@Configuration
@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis", matchIfMissing = false)
@ -57,15 +80,12 @@ public class TBRedisCacheConfiguration {
}
@Bean
public RedisTemplate<String, String> redisTemplate(RedisConnectionFactory cf) {
RedisTemplate<String, String> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(cf);
return redisTemplate;
}
@Bean
public CacheManager cacheManager(RedisTemplate redisTemplate) {
return new RedisCacheManager(redisTemplate);
public CacheManager cacheManager(RedisConnectionFactory cf) {
DefaultFormattingConversionService redisConversionService = new DefaultFormattingConversionService();
RedisCacheConfiguration.registerDefaultConverters(redisConversionService);
registerDefaultConverters(redisConversionService);
RedisCacheConfiguration configuration = RedisCacheConfiguration.defaultCacheConfig().withConversionService(redisConversionService);
return RedisCacheManager.builder(cf).cacheDefaults(configuration).build();
}
@Bean
@ -73,5 +93,8 @@ public class TBRedisCacheConfiguration {
return new PreviousDeviceCredentialsIdKeyGenerator();
}
private static void registerDefaultConverters(ConverterRegistry registry) {
Assert.notNull(registry, "ConverterRegistry must not be null!");
registry.addConverter(EntityId.class, String.class, EntityId::toString);
}
}

2
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraCluster.java

@ -21,7 +21,7 @@ import org.thingsboard.server.dao.util.NoSqlAnyDao;
import javax.annotation.PostConstruct;
@Component
@Component("CassandraCluster")
@NoSqlAnyDao
public class CassandraCluster extends AbstractCassandraCluster {

2
dao/src/main/java/org/thingsboard/server/dao/cassandra/CassandraInstallCluster.java

@ -21,7 +21,7 @@ import org.thingsboard.server.dao.util.NoSqlAnyDao;
import javax.annotation.PostConstruct;
@Component
@Component("CassandraInstallCluster")
@NoSqlAnyDao
@Profile("install")
public class CassandraInstallCluster extends AbstractCassandraCluster {

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/AuditLogEntity.java

@ -140,7 +140,7 @@ public class AuditLogEntity extends BaseSqlEntity<AuditLog> implements BaseEntit
auditLog.setEntityId(EntityIdFactory.getByTypeAndId(entityType.name(), toUUID(entityId).toString()));
}
if (userId != null) {
auditLog.setUserId(new UserId(toUUID(entityId)));
auditLog.setUserId(new UserId(toUUID(userId)));
}
auditLog.setEntityName(this.entityName);
auditLog.setUserName(this.userName);

2
dao/src/main/java/org/thingsboard/server/dao/nosql/CassandraAbstractDao.java

@ -27,6 +27,7 @@ import com.datastax.driver.core.TypeCodec;
import com.datastax.driver.core.exceptions.CodecNotFoundException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.cassandra.CassandraCluster;
import org.thingsboard.server.dao.model.type.AuthorityCodec;
@ -44,6 +45,7 @@ import java.util.concurrent.ConcurrentMap;
public abstract class CassandraAbstractDao {
@Autowired
@Qualifier("CassandraCluster")
protected CassandraCluster cluster;
private ConcurrentMap<String, PreparedStatement> preparedStatementMap = new ConcurrentHashMap<>();

9
dao/src/main/java/org/thingsboard/server/dao/sql/JpaAbstractDao.java

@ -27,6 +27,7 @@ import org.thingsboard.server.dao.DaoUtil;
import org.thingsboard.server.dao.model.BaseEntity;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
@ -67,23 +68,23 @@ public abstract class JpaAbstractDao<E extends BaseEntity<D>, D>
@Override
public D findById(TenantId tenantId, UUID key) {
log.debug("Get entity by key {}", key);
E entity = getCrudRepository().findOne(fromTimeUUID(key));
Optional<E> entity = getCrudRepository().findById(fromTimeUUID(key));
return DaoUtil.getData(entity);
}
@Override
public ListenableFuture<D> findByIdAsync(TenantId tenantId, UUID key) {
log.debug("Get entity by key async {}", key);
return service.submit(() -> DaoUtil.getData(getCrudRepository().findOne(fromTimeUUID(key))));
return service.submit(() -> DaoUtil.getData(getCrudRepository().findById(fromTimeUUID(key))));
}
@Override
@Transactional
public boolean removeById(TenantId tenantId, UUID id) {
String key = fromTimeUUID(id);
getCrudRepository().delete(key);
getCrudRepository().deleteById(key);
log.debug("Remove request: {}", key);
return getCrudRepository().findOne(key) == null;
return !getCrudRepository().existsById(key);
}
@Override

6
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java

@ -52,7 +52,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
AttributeKvCompositeKey compositeKey =
getAttributeKvCompositeKey(entityId, attributeType, attributeKey);
return Futures.immediateFuture(
Optional.ofNullable(DaoUtil.getData(attributeKvRepository.findOne(compositeKey))));
Optional.ofNullable(DaoUtil.getData(attributeKvRepository.findById(compositeKey))));
}
@Override
@ -64,7 +64,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
getAttributeKvCompositeKey(entityId, attributeType, attributeKey))
.collect(Collectors.toList());
return Futures.immediateFuture(
DaoUtil.convertDataList(Lists.newArrayList(attributeKvRepository.findAll(compositeKeys))));
DaoUtil.convertDataList(Lists.newArrayList(attributeKvRepository.findAllById(compositeKeys))));
}
@Override
@ -103,7 +103,7 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
}).collect(Collectors.toList());
return service.submit(() -> {
attributeKvRepository.delete(entitiesToDelete);
attributeKvRepository.deleteAll(entitiesToDelete);
return null;
});
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/component/JpaBaseComponentDescriptorDao.java

@ -66,7 +66,7 @@ public class JpaBaseComponentDescriptorDao extends JpaAbstractSearchTextDao<Comp
if (component.getId() == null) {
component.setId(new ComponentDescriptorId(UUIDs.timeBased()));
}
if (componentDescriptorRepository.findOne(UUIDConverter.fromTimeUUID(component.getId().getId())) == null) {
if (!componentDescriptorRepository.existsById(UUIDConverter.fromTimeUUID(component.getId().getId()))) {
return Optional.of(save(tenantId, component));
}
return Optional.empty();

8
dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationDao.java

@ -97,13 +97,13 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
@Override
public ListenableFuture<Boolean> checkRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup);
return service.submit(() -> relationRepository.findOne(key) != null);
return service.submit(() -> relationRepository.existsById(key));
}
@Override
public ListenableFuture<EntityRelation> getRelation(TenantId tenantId, EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
RelationCompositeKey key = getRelationCompositeKey(from, to, relationType, typeGroup);
return service.submit(() -> DaoUtil.getData(relationRepository.findOne(key)));
return service.submit(() -> DaoUtil.getData(relationRepository.findById(key)));
}
private RelationCompositeKey getRelationCompositeKey(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
@ -152,9 +152,9 @@ public class JpaRelationDao extends JpaAbstractDaoListeningExecutorService imple
}
private boolean deleteRelationIfExists(RelationCompositeKey key) {
boolean relationExistsBeforeDelete = relationRepository.exists(key);
boolean relationExistsBeforeDelete = relationRepository.existsById(key);
if (relationExistsBeforeDelete) {
relationRepository.delete(key);
relationRepository.deleteById(key);
}
return relationExistsBeforeDelete;
}

2
dao/src/main/java/org/thingsboard/server/dao/sql/relation/RelationRepository.java

@ -53,7 +53,7 @@ public interface RelationRepository
RelationEntity save(RelationEntity entity);
@Transactional
void delete(RelationCompositeKey id);
void deleteById(RelationCompositeKey id);
@Transactional
void deleteByFromIdAndFromType(String fromId, String fromType);

6
dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java

@ -284,10 +284,10 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
entityId.getEntityType(),
fromTimeUUID(entityId.getId()),
key);
TsKvLatestEntity entry = tsKvLatestRepository.findOne(compositeKey);
Optional<TsKvLatestEntity> entry = tsKvLatestRepository.findById(compositeKey);
TsKvEntry result;
if (entry != null) {
result = DaoUtil.getData(entry);
if (entry.isPresent()) {
result = DaoUtil.getData(entry.get());
} else {
result = new BasicTsKvEntry(System.currentTimeMillis(), new StringDataEntry(key, null));
}

1
dao/src/test/java/org/thingsboard/server/dao/CustomCassandraCQLUnit.java

@ -82,6 +82,7 @@ public class CustomCassandraCQLUnit extends BaseCassandraUnit {
session = null;
cluster = null;
}
System.setSecurityManager(null);
}
// Getters for those who do not like to directly access fields

8
dao/src/test/resources/application-test.properties

@ -4,10 +4,10 @@ zk.zk_dir=/thingsboard
updates.enabled=false
audit_log.enabled=true
audit_log.by_tenant_partitioning=MONTHS
audit_log.default_query_period=30
audit_log.sink.type=none
audit-log.enabled=true
audit-log.by_tenant_partitioning=MONTHS
audit-log.default_query_period=30
audit-log.sink.type=none
cache.type=caffeine
#cache.type=redis

1
dao/src/test/resources/sql-test.properties

@ -4,6 +4,7 @@ database.entities.type=sql
sql.ts_inserts_executor_type=fixed
sql.ts_inserts_fixed_thread_pool_size=10
spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true
spring.jpa.show-sql=false
spring.jpa.hibernate.ddl-auto=validate
spring.jpa.database-platform=org.hibernate.dialect.HSQLDialect

5
k8s/.env

@ -0,0 +1,5 @@
# Database used by ThingsBoard, can be either postgres (PostgreSQL) or cassandra (Cassandra).
# According to the database type corresponding kubernetes resources will be deployed (see postgres.yml, cassandra.yml for details).
DATABASE=postgres

100
k8s/README.md

@ -0,0 +1,100 @@
# Kubernetes resources configuration for ThingsBoard Microservices
This folder containing scripts and Kubernetes resources configurations to run ThingsBoard in Microservices mode.
## Prerequisites
ThingsBoard Microservices are running on Kubernetes cluster.
You need to have a Kubernetes cluster, and the kubectl command-line tool must be configured to communicate with your cluster.
If you do not already have a cluster, you can create one by using [Minikube](https://kubernetes.io/docs/setup/minikube),
or you can choose any other available [Kubernetes cluster deployment solutions](https://kubernetes.io/docs/setup/pick-right-solution/).
## Installation
Before performing initial installation you can configure the type of database to be used with ThingsBoard.
In order to set database type change the value of `DATABASE` variable in `.env` file to one of the following:
- `postgres` - use PostgreSQL database;
- `cassandra` - use Cassandra database;
**NOTE**: According to the database type corresponding kubernetes resources will be deployed (see `postgres.yml`, `cassandra.yml` for details).
Execute the following command to run installation:
`
$ ./k8s-install-tb.sh --loadDemo
`
Where:
- `--loadDemo` - optional argument. Whether to load additional demo data.
## Running
Execute the following command to deploy resources:
`
$ ./k8s-deploy-resources.sh
`
After a while when all resources will be successfully started you can open `http://{your-cluster-ip}` in you browser (for ex. `http://192.168.99.101`).
You should see ThingsBoard login page.
Use the following default credentials:
- **System Administrator**: sysadmin@thingsboard.org / sysadmin
If you installed DataBase with demo data (using `--loadDemo` flag) you can also use the following credentials:
- **Tenant Administrator**: tenant@thingsboard.org / tenant
- **Customer User**: customer@thingsboard.org / customer
In case of any issues you can examine service logs for errors.
For example to see ThingsBoard node logs execute the following commands:
1) Get list of the running tb-node pods:
`
$ kubectl get pods -l app=tb-node
`
2) Fetch logs of tb-node pod:
`
$ kubectl logs -f [tb-node-pod-name]
`
Where:
- `tb-node-pod-name` - tb-node pod name obtained from the list of the running tb-node pods.
Or use `kubectl get pods` to see the state of all the pods.
Or use `kubectl get services` to see the state of all the services.
Or use `kubectl get deployments` to see the state of all the deployments.
See [kubectl Cheat Sheet](https://kubernetes.io/docs/reference/kubectl/cheatsheet/) command reference for details.
Execute the following command to delete all deployed microservices:
`
$ ./k8s-delete-resources.sh
`
Execute the following command to delete all resources (including database):
`
$ ./k8s-delete-all.sh
`
## Upgrading
In case when database upgrade is needed, execute the following commands:
```
$ ./k8s-delete-resources.sh
$ ./k8s-upgrade-tb.sh --fromVersion=[FROM_VERSION]
$ ./k8s-deploy-resources.sh
```
Where:
- `FROM_VERSION` - from which version upgrade should be started. See [Upgrade Instructions](https://thingsboard.io/docs/user-guide/install/upgrade-instructions) for valid `fromVersion` values.

164
k8s/cassandra.yml

@ -0,0 +1,164 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: fast
namespace: thingsboard
provisioner: k8s.io/minikube-hostpath
parameters:
type: pd-ssd
---
apiVersion: v1
kind: ConfigMap
metadata:
name: cassandra-probe-config
namespace: thingsboard
labels:
name: cassandra-probe-config
data:
probe: |
if [[ $(nodetool status | grep $POD_IP) == *"UN"* ]]; then
if [[ $DEBUG ]]; then
echo "UN";
fi
exit 0;
else
if [[ $DEBUG ]]; then
echo "Not Up";
fi
exit 1;
fi
---
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: cassandra
namespace: thingsboard
labels:
app: cassandra
spec:
serviceName: cassandra
replicas: 1
selector:
matchLabels:
app: cassandra
template:
metadata:
labels:
app: cassandra
spec:
volumes:
- name: cassandra-probe-config
configMap:
name: cassandra-probe-config
items:
- key: probe
path: ready-probe.sh
mode: 0777
terminationGracePeriodSeconds: 1800
containers:
- name: cassandra
image: cassandra:3.11.3
imagePullPolicy: Always
ports:
- containerPort: 7000
name: intra-node
- containerPort: 7001
name: tls-intra-node
- containerPort: 7199
name: jmx
- containerPort: 9042
name: cql
- containerPort: 9160
name: thrift
resources:
limits:
cpu: "1000m"
memory: 2Gi
requests:
cpu: "1000m"
memory: 2Gi
securityContext:
capabilities:
add:
- IPC_LOCK
lifecycle:
preStop:
exec:
command:
- /bin/sh
- -c
- nodetool drain
env:
- name: CASSANDRA_SEEDS
value: "cassandra-0.cassandra.thingsboard.svc.cluster.local"
- name: MAX_HEAP_SIZE
value: 1024M
- name: HEAP_NEWSIZE
value: 256M
- name: CASSANDRA_CLUSTER_NAME
value: "Thingsboard Cluster"
- name: CASSANDRA_DC
value: "DC1-Thingsboard-Cluster"
- name: CASSANDRA_RACK
value: "Rack-Thingsboard-Cluster"
- name: CASSANDRA_AUTO_BOOTSTRAP
value: "false"
- name: CASSANDRA_ENDPOINT_SNITCH
value: GossipingPropertyFileSnitch
- name: POD_IP
valueFrom:
fieldRef:
fieldPath: status.podIP
readinessProbe:
exec:
command:
- /bin/bash
- -c
- /probe/ready-probe.sh
initialDelaySeconds: 60
timeoutSeconds: 5
volumeMounts:
- name: cassandra-probe-config
mountPath: /probe
- name: cassandra-data
mountPath: /var/lib/cassandra
volumeClaimTemplates:
- metadata:
name: cassandra-data
spec:
accessModes: [ "ReadWriteOnce" ]
storageClassName: fast
resources:
requests:
storage: 1Gi
---
apiVersion: v1
kind: Service
metadata:
labels:
app: cassandra
name: cassandra
namespace: thingsboard
spec:
clusterIP: None
ports:
- port: 9042
selector:
app: cassandra
---

43
k8s/database-setup.yml

@ -0,0 +1,43 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: Pod
metadata:
name: tb-db-setup
namespace: thingsboard
spec:
volumes:
- name: tb-node-config
configMap:
name: tb-node-config
items:
- key: conf
path: thingsboard.conf
- key: logback
path: logback.xml
containers:
- name: tb-db-setup
imagePullPolicy: Always
image: thingsboard/tb-node:latest
envFrom:
- configMapRef:
name: tb-node-db-config
volumeMounts:
- mountPath: /config
name: tb-node-config
command: ['sh', '-c', 'while [ ! -f /install-finished ]; do sleep 2; done;']
restartPolicy: Never

18
k8s/k8s-delete-all.sh

@ -0,0 +1,18 @@
#!/bin/bash
#
# Copyright © 2016-2019 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.
#
kubectl -n thingsboard delete svc,sts,deploy,pv,pvc,cm,po,ing --all --include-uninitialized

21
k8s/k8s-delete-resources.sh

@ -0,0 +1,21 @@
#!/bin/bash
#
# Copyright © 2016-2019 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.
#
set -e
kubectl config set-context $(kubectl config current-context) --namespace=thingsboard
kubectl delete -f thingsboard.yml

26
k8s/k8s-deploy-resources.sh

@ -0,0 +1,26 @@
#!/bin/bash
#
# Copyright © 2016-2019 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.
#
set -e
kubectl apply -f tb-namespace.yml
kubectl config set-context $(kubectl config current-context) --namespace=thingsboard
kubectl apply -f tb-node-configmap.yml
kubectl apply -f tb-mqtt-transport-configmap.yml
kubectl apply -f tb-http-transport-configmap.yml
kubectl apply -f tb-coap-transport-configmap.yml
kubectl apply -f thingsboard.yml

93
k8s/k8s-install-tb.sh

@ -0,0 +1,93 @@
#!/bin/bash
#
# Copyright © 2016-2019 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.
#
function installTb() {
loadDemo=$1
kubectl apply -f tb-node-configmap.yml
kubectl apply -f database-setup.yml &&
kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s &&
kubectl exec tb-db-setup -- sh -c 'export INSTALL_TB=true; export LOAD_DEMO='"$loadDemo"'; start-tb-node.sh; touch /install-finished;'
kubectl delete pod tb-db-setup
}
function installPostgres() {
kubectl apply -f postgres.yml
kubectl apply -f tb-node-postgres-configmap.yml
kubectl rollout status deployment/postgres
}
function installCassandra() {
kubectl apply -f cassandra.yml
kubectl apply -f tb-node-cassandra-configmap.yml
kubectl rollout status statefulset/cassandra
kubectl exec -it cassandra-0 -- bash -c "cqlsh -e \
\"CREATE KEYSPACE IF NOT EXISTS thingsboard \
WITH replication = { \
'class' : 'SimpleStrategy', \
'replication_factor' : 1 \
};\""
}
while [[ $# -gt 0 ]]
do
key="$1"
case $key in
--loadDemo)
LOAD_DEMO=true
shift # past argument
;;
*)
# unknown option
;;
esac
shift # past argument or value
done
if [ "$LOAD_DEMO" == "true" ]; then
loadDemo=true
else
loadDemo=false
fi
source .env
kubectl apply -f tb-namespace.yml
kubectl config set-context $(kubectl config current-context) --namespace=thingsboard
case $DATABASE in
postgres)
installPostgres
installTb ${loadDemo}
;;
cassandra)
installCassandra
installTb ${loadDemo}
;;
*)
echo "Unknown DATABASE value specified: '${DATABASE}'. Should be either postgres or cassandra." >&2
exit 1
esac

43
k8s/k8s-upgrade-tb.sh

@ -0,0 +1,43 @@
#!/bin/bash
#
# Copyright © 2016-2019 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.
#
for i in "$@"
do
case $i in
--fromVersion=*)
FROM_VERSION="${i#*=}"
shift
;;
*)
# unknown option
;;
esac
done
if [[ -z "${FROM_VERSION// }" ]]; then
echo "--fromVersion parameter is invalid or unspecified!"
echo "Usage: k8s-upgrade-tb.sh --fromVersion={VERSION}"
exit 1
else
fromVersion="${FROM_VERSION// }"
fi
kubectl apply -f database-setup.yml &&
kubectl wait --for=condition=Ready pod/tb-db-setup --timeout=120s &&
kubectl exec tb-db-setup -- sh -c 'export UPGRADE_TB=true; export FROM_VERSION='"$fromVersion"'; start-tb-node.sh; touch /install-finished;'
kubectl delete pod tb-db-setup

95
k8s/postgres.yml

@ -0,0 +1,95 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: postgres-pv-claim
namespace: thingsboard
labels:
app: postgres
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: 5Gi
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: postgres
namespace: thingsboard
labels:
app: postgres
spec:
template:
metadata:
labels:
app: postgres
spec:
volumes:
- name: postgres-data
persistentVolumeClaim:
claimName: postgres-pv-claim
containers:
- name: postgres
imagePullPolicy: Always
image: postgres:9.6
ports:
- containerPort: 5432
name: postgres
env:
- name: POSTGRES_DB
value: "thingsboard"
volumeMounts:
- mountPath: /var/lib/postgresql/data
name: postgres-data
livenessProbe:
exec:
command:
- pg_isready
- -h
- localhost
- -U
- postgres
initialDelaySeconds: 60
timeoutSeconds: 30
readinessProbe:
exec:
command:
- pg_isready
- -h
- localhost
- -U
- postgres
initialDelaySeconds: 5
timeoutSeconds: 1
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-database
namespace: thingsboard
spec:
type: ClusterIP
selector:
app: postgres
ports:
- port: 5432
name: postgres
---

65
k8s/tb-coap-transport-configmap.yml

@ -0,0 +1,65 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: ConfigMap
metadata:
name: tb-coap-transport-config
namespace: thingsboard
labels:
name: tb-coap-transport-config
data:
conf: |
export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-coap-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-coap-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
export LOG_FILENAME=tb-coap-transport.out
export LOADER_PATH=/usr/share/tb-coap-transport/conf
logback: |
<!DOCTYPE configuration>
<configuration scan="true" scanPeriod="10 seconds">
<appender name="fileLogAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/tb-coap-transport/${TB_HOST}/tb-coap-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<root level="INFO">
<appender-ref ref="fileLogAppender"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

65
k8s/tb-http-transport-configmap.yml

@ -0,0 +1,65 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: ConfigMap
metadata:
name: tb-http-transport-config
namespace: thingsboard
labels:
name: tb-http-transport-config
data:
conf: |
export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-http-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-http-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
export LOG_FILENAME=tb-http-transport.out
export LOADER_PATH=/usr/share/tb-http-transport/conf
logback: |
<!DOCTYPE configuration>
<configuration scan="true" scanPeriod="10 seconds">
<appender name="fileLogAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/tb-http-transport/${TB_HOST}/tb-http-transport.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/tb-http-transport/${TB_HOST}/tb-http-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<root level="INFO">
<appender-ref ref="fileLogAppender"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

65
k8s/tb-mqtt-transport-configmap.yml

@ -0,0 +1,65 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: ConfigMap
metadata:
name: tb-mqtt-transport-config
namespace: thingsboard
labels:
name: tb-mqtt-transport-config
data:
conf: |
export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/tb-mqtt-transport/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/tb-mqtt-transport/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
export LOG_FILENAME=tb-mqtt-transport.out
export LOADER_PATH=/usr/share/tb-mqtt-transport/conf
logback: |
<!DOCTYPE configuration>
<configuration scan="true" scanPeriod="10 seconds">
<appender name="fileLogAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/tb-mqtt-transport/${TB_HOST}/tb-mqtt-transport.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<root level="INFO">
<appender-ref ref="fileLogAppender"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

22
k8s/tb-namespace.yml

@ -0,0 +1,22 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: Namespace
metadata:
name: thingsboard
labels:
name: thingsboard

28
k8s/tb-node-cassandra-configmap.yml

@ -0,0 +1,28 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: ConfigMap
metadata:
name: tb-node-db-config
namespace: thingsboard
labels:
name: tb-node-db-config
data:
DATABASE_TS_TYPE: cassandra
DATABASE_ENTITIES_TYPE: cassandra
CASSANDRA_URL: cassandra:9042
CASSANDRA_SOCKET_READ_TIMEOUT: "60000"

67
k8s/tb-node-configmap.yml

@ -0,0 +1,67 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: ConfigMap
metadata:
name: tb-node-config
namespace: thingsboard
labels:
name: tb-node-config
data:
conf: |
export JAVA_OPTS="$JAVA_OPTS -Dplatform=deb -Dinstall.data_dir=/usr/share/thingsboard/data"
export JAVA_OPTS="$JAVA_OPTS -Xloggc:/var/log/thingsboard/${TB_HOST}/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/thingsboard/${TB_HOST}/heapdump.bin -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
export JAVA_OPTS="$JAVA_OPTS -XX:+CMSEdenChunksRecordAlways -XX:CMSInitiatingOccupancyFraction=75 -XX:+UseCMSInitiatingOccupancyOnly -XX:+ExitOnOutOfMemoryError"
export LOG_FILENAME=thingsboard.out
export LOADER_PATH=/usr/share/thingsboard/conf,/usr/share/thingsboard/extensions
logback: |
<!DOCTYPE configuration>
<configuration scan="true" scanPeriod="10 seconds">
<appender name="fileLogAppender"
class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>/var/log/thingsboard/${TB_HOST}/thingsboard.log</file>
<rollingPolicy
class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy">
<fileNamePattern>/var/log/thingsboard/${TB_HOST}/thingsboard.%d{yyyy-MM-dd}.%i.log</fileNamePattern>
<maxFileSize>100MB</maxFileSize>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{ISO8601} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<logger name="org.thingsboard.server" level="INFO" />
<logger name="com.google.common.util.concurrent.AggregateFuture" level="OFF" />
<root level="INFO">
<appender-ref ref="fileLogAppender"/>
<appender-ref ref="STDOUT"/>
</root>
</configuration>

31
k8s/tb-node-postgres-configmap.yml

@ -0,0 +1,31 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: v1
kind: ConfigMap
metadata:
name: tb-node-db-config
namespace: thingsboard
labels:
name: tb-node-db-config
data:
DATABASE_TS_TYPE: sql
DATABASE_ENTITIES_TYPE: sql
SPRING_JPA_DATABASE_PLATFORM: org.hibernate.dialect.PostgreSQLDialect
SPRING_DRIVER_CLASS_NAME: org.postgresql.Driver
SPRING_DATASOURCE_URL: jdbc:postgresql://tb-database:5432/thingsboard
SPRING_DATASOURCE_USERNAME: postgres
SPRING_DATASOURCE_PASSWORD: postgres

608
k8s/thingsboard.yml

@ -0,0 +1,608 @@
#
# Copyright © 2016-2019 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.
#
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: zookeeper
namespace: thingsboard
spec:
template:
metadata:
labels:
app: zookeeper
spec:
containers:
- name: server
imagePullPolicy: Always
image: zookeeper:3.5
ports:
- containerPort: 2181
readinessProbe:
periodSeconds: 5
tcpSocket:
port: 2181
livenessProbe:
periodSeconds: 5
tcpSocket:
port: 2181
env:
- name: ZOO_MY_ID
value: "1"
- name: ZOO_SERVERS
value: "server.1=0.0.0.0:2888:3888;0.0.0.0:2181"
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: zookeeper
namespace: thingsboard
spec:
type: ClusterIP
selector:
app: zookeeper
ports:
- name: zk-port
port: 2181
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-kafka
namespace: thingsboard
spec:
template:
metadata:
labels:
app: tb-kafka
spec:
containers:
- name: server
imagePullPolicy: Always
image: wurstmeister/kafka
ports:
- containerPort: 9092
readinessProbe:
periodSeconds: 20
tcpSocket:
port: 9092
livenessProbe:
periodSeconds: 5
tcpSocket:
port: 9092
env:
- name: KAFKA_ZOOKEEPER_CONNECT
value: "zookeeper:2181"
- name: KAFKA_LISTENERS
value: "INSIDE://:9093,OUTSIDE://:9092"
- name: KAFKA_ADVERTISED_LISTENERS
value: "INSIDE://:9093,OUTSIDE://tb-kafka:9092"
- name: KAFKA_LISTENER_SECURITY_PROTOCOL_MAP
value: "INSIDE:PLAINTEXT,OUTSIDE:PLAINTEXT"
- name: KAFKA_INTER_BROKER_LISTENER_NAME
value: "INSIDE"
- name: KAFKA_CREATE_TOPICS
value: "js.eval.requests:100:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.transport.api.requests:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600,tb.rule-engine:30:1:delete --config=retention.ms=60000 --config=segment.bytes=26214400 --config=retention.bytes=104857600"
- name: KAFKA_AUTO_CREATE_TOPICS_ENABLE
value: "false"
- name: KAFKA_LOG_RETENTION_BYTES
value: "1073741824"
- name: KAFKA_LOG_SEGMENT_BYTES
value: "268435456"
- name: KAFKA_LOG_RETENTION_MS
value: "300000"
- name: KAFKA_LOG_CLEANUP_POLICY
value: "delete"
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-kafka
namespace: thingsboard
spec:
type: ClusterIP
selector:
app: tb-kafka
ports:
- name: tb-kafka-port
port: 9092
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-redis
namespace: thingsboard
spec:
template:
metadata:
labels:
app: tb-redis
spec:
containers:
- name: server
imagePullPolicy: Always
image: redis:4.0
ports:
- containerPort: 6379
readinessProbe:
periodSeconds: 5
tcpSocket:
port: 6379
livenessProbe:
periodSeconds: 5
tcpSocket:
port: 6379
volumeMounts:
- mountPath: /data
name: redis-data
volumes:
- name: redis-data
emptyDir: {}
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-redis
namespace: thingsboard
spec:
type: ClusterIP
selector:
app: tb-redis
ports:
- name: tb-redis-port
port: 6379
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-js-executor
namespace: thingsboard
spec:
replicas: 20
selector:
matchLabels:
app: tb-js-executor
template:
metadata:
labels:
app: tb-js-executor
spec:
containers:
- name: server
imagePullPolicy: Always
image: thingsboard/tb-js-executor:latest
env:
- name: REMOTE_JS_EVAL_REQUEST_TOPIC
value: "js.eval.requests"
- name: TB_KAFKA_SERVERS
value: "tb-kafka:9092"
- name: LOGGER_LEVEL
value: "info"
- name: LOG_FOLDER
value: "logs"
- name: LOGGER_FILENAME
value: "tb-js-executor-%DATE%.log"
- name: DOCKER_MODE
value: "true"
- name: SCRIPT_BODY_TRACE_FREQUENCY
value: "1000"
restartPolicy: Always
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-node
namespace: thingsboard
spec:
replicas: 2
selector:
matchLabels:
app: tb-node
template:
metadata:
labels:
app: tb-node
spec:
volumes:
- name: tb-node-config
configMap:
name: tb-node-config
items:
- key: conf
path: thingsboard.conf
- key: logback
path: logback.xml
containers:
- name: server
imagePullPolicy: Always
image: thingsboard/tb-node:latest
ports:
- containerPort: 8080
name: http
- containerPort: 9001
name: rpc
env:
- name: RPC_HOST
valueFrom:
fieldRef:
fieldPath: status.podIP
- name: CLUSTER_NODE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: TB_HOST
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: ZOOKEEPER_ENABLED
value: "true"
- name: ZOOKEEPER_URL
value: "zookeeper:2181"
- name: TB_KAFKA_SERVERS
value: "tb-kafka:9092"
- name: JS_EVALUATOR
value: "remote"
- name: TRANSPORT_TYPE
value: "remote"
- name: CACHE_TYPE
value: "redis"
- name: REDIS_HOST
value: "tb-redis"
- name: HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE
value: "false"
envFrom:
- configMapRef:
name: tb-node-db-config
volumeMounts:
- mountPath: /config
name: tb-node-config
livenessProbe:
httpGet:
path: /login
port: http
initialDelaySeconds: 120
timeoutSeconds: 10
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-node
namespace: thingsboard
spec:
type: ClusterIP
selector:
app: tb-node
ports:
- port: 8080
name: http
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-mqtt-transport
namespace: thingsboard
spec:
replicas: 2
selector:
matchLabels:
app: tb-mqtt-transport
template:
metadata:
labels:
app: tb-mqtt-transport
spec:
volumes:
- name: tb-mqtt-transport-config
configMap:
name: tb-mqtt-transport-config
items:
- key: conf
path: tb-mqtt-transport.conf
- key: logback
path: logback.xml
containers:
- name: server
imagePullPolicy: Always
image: thingsboard/tb-mqtt-transport:latest
ports:
- containerPort: 1883
name: mqtt
env:
- name: CLUSTER_NODE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: TB_HOST
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: MQTT_BIND_ADDRESS
value: "0.0.0.0"
- name: MQTT_BIND_PORT
value: "1883"
- name: MQTT_TIMEOUT
value: "10000"
- name: TB_KAFKA_SERVERS
value: "tb-kafka:9092"
volumeMounts:
- mountPath: /config
name: tb-mqtt-transport-config
readinessProbe:
periodSeconds: 20
tcpSocket:
port: 1883
livenessProbe:
periodSeconds: 20
tcpSocket:
port: 1883
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-mqtt-transport
namespace: thingsboard
spec:
type: LoadBalancer
selector:
app: tb-mqtt-transport
ports:
- port: 1883
targetPort: 1883
name: mqtt
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-http-transport
namespace: thingsboard
spec:
replicas: 2
selector:
matchLabels:
app: tb-http-transport
template:
metadata:
labels:
app: tb-http-transport
spec:
volumes:
- name: tb-http-transport-config
configMap:
name: tb-http-transport-config
items:
- key: conf
path: tb-http-transport.conf
- key: logback
path: logback.xml
containers:
- name: server
imagePullPolicy: Always
image: thingsboard/tb-http-transport:latest
ports:
- containerPort: 8080
name: http
env:
- name: CLUSTER_NODE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: TB_HOST
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: HTTP_BIND_ADDRESS
value: "0.0.0.0"
- name: HTTP_BIND_PORT
value: "8080"
- name: HTTP_REQUEST_TIMEOUT
value: "60000"
- name: TB_KAFKA_SERVERS
value: "tb-kafka:9092"
volumeMounts:
- mountPath: /config
name: tb-http-transport-config
readinessProbe:
periodSeconds: 20
tcpSocket:
port: 8080
livenessProbe:
periodSeconds: 20
tcpSocket:
port: 8080
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-http-transport
namespace: thingsboard
spec:
type: ClusterIP
selector:
app: tb-http-transport
ports:
- port: 8080
name: http
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-coap-transport
namespace: thingsboard
spec:
replicas: 2
selector:
matchLabels:
app: tb-coap-transport
template:
metadata:
labels:
app: tb-coap-transport
spec:
volumes:
- name: tb-coap-transport-config
configMap:
name: tb-coap-transport-config
items:
- key: conf
path: tb-coap-transport.conf
- key: logback
path: logback.xml
containers:
- name: server
imagePullPolicy: Always
image: thingsboard/tb-coap-transport:latest
ports:
- containerPort: 5683
name: coap
protocol: UDP
env:
- name: CLUSTER_NODE_ID
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: TB_HOST
valueFrom:
fieldRef:
fieldPath: metadata.name
- name: COAP_BIND_ADDRESS
value: "0.0.0.0"
- name: COAP_BIND_PORT
value: "5683"
- name: COAP_TIMEOUT
value: "10000"
- name: TB_KAFKA_SERVERS
value: "tb-kafka:9092"
volumeMounts:
- mountPath: /config
name: tb-coap-transport-config
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-coap-transport
namespace: thingsboard
spec:
type: LoadBalancer
selector:
app: tb-coap-transport
ports:
- port: 5683
name: coap
protocol: UDP
---
apiVersion: extensions/v1beta1
kind: Deployment
metadata:
name: tb-web-ui
namespace: thingsboard
spec:
replicas: 2
selector:
matchLabels:
app: tb-web-ui
template:
metadata:
labels:
app: tb-web-ui
spec:
containers:
- name: server
imagePullPolicy: Always
image: thingsboard/tb-web-ui:latest
ports:
- containerPort: 8080
name: http
env:
- name: HTTP_BIND_ADDRESS
value: "0.0.0.0"
- name: HTTP_BIND_PORT
value: "8080"
- name: TB_ENABLE_PROXY
value: "false"
- name: LOGGER_LEVEL
value: "info"
- name: LOG_FOLDER
value: "logs"
- name: LOGGER_FILENAME
value: "tb-web-ui-%DATE%.log"
- name: DOCKER_MODE
value: "true"
livenessProbe:
httpGet:
path: /index.html
port: http
initialDelaySeconds: 120
timeoutSeconds: 10
restartPolicy: Always
---
apiVersion: v1
kind: Service
metadata:
name: tb-web-ui
namespace: thingsboard
spec:
type: ClusterIP
selector:
app: tb-web-ui
ports:
- port: 8080
name: http
---
apiVersion: extensions/v1beta1
kind: Ingress
metadata:
name: tb-ingress
namespace: thingsboard
annotations:
nginx.ingress.kubernetes.io/use-regex: "true"
nginx.ingress.kubernetes.io/ssl-redirect: "false"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
spec:
rules:
- http:
paths:
- path: /api/v1/.*
backend:
serviceName: tb-http-transport
servicePort: 8080
- path: /static/rulenode/.*
backend:
serviceName: tb-node
servicePort: 8080
- path: /static/.*
backend:
serviceName: tb-web-ui
servicePort: 8080
- path: /index.html.*
backend:
serviceName: tb-web-ui
servicePort: 8080
- path: /
backend:
serviceName: tb-web-ui
servicePort: 8080
- path: /.*
backend:
serviceName: tb-node
servicePort: 8080
---

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

@ -1,6 +1,6 @@
{
"name": "thingsboard-js-executor",
"version": "2.3.0",
"version": "2.3.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

2
msa/web-ui/package-lock.json

@ -1,6 +1,6 @@
{
"name": "thingsboard-web-ui",
"version": "2.3.0",
"version": "2.3.1",
"lockfileVersion": 1,
"requires": true,
"dependencies": {

48
pom.xml

@ -29,10 +29,10 @@
<properties>
<main.dir>${basedir}</main.dir>
<spring-boot.version>1.4.3.RELEASE</spring-boot.version>
<spring.version>4.3.4.RELEASE</spring.version>
<spring-security.version>4.2.0.RELEASE</spring-security.version>
<spring-data-redis.version>1.8.10.RELEASE</spring-data-redis.version>
<spring-boot.version>2.1.3.RELEASE</spring-boot.version>
<spring.version>5.1.5.RELEASE</spring.version>
<spring-security.version>5.1.4.RELEASE</spring-security.version>
<spring-data-redis.version>2.1.5.RELEASE</spring-data-redis.version>
<jedis.version>2.9.0</jedis.version>
<jjwt.version>0.7.0</jjwt.version>
<json-path.version>2.2.0</json-path.version>
@ -41,8 +41,8 @@
<logback.version>1.2.3</logback.version>
<mockito.version>1.9.5</mockito.version>
<rat.version>0.10</rat.version>
<cassandra.version>3.5.0</cassandra.version>
<cassandra-unit.version>3.3.0.2</cassandra-unit.version>
<cassandra.version>3.6.0</cassandra.version>
<cassandra-unit.version>3.5.0.1</cassandra-unit.version>
<takari-cpsuite.version>1.2.7</takari-cpsuite.version>
<guava.version>21.0</guava.version>
<caffeine.version>2.6.1</caffeine.version>
@ -50,7 +50,7 @@
<commons-validator.version>1.5.0</commons-validator.version>
<commons-io.version>2.5</commons-io.version>
<commons-csv.version>1.4</commons-csv.version>
<jackson.version>2.8.11.1</jackson.version>
<jackson.version>2.9.7</jackson.version>
<json-schema-validator.version>2.2.6</json-schema-validator.version>
<scala.version>2.11</scala.version>
<akka.version>2.4.2</akka.version>
@ -60,11 +60,11 @@
<velocity-tools.version>2.0</velocity-tools.version>
<mail.version>1.4.3</mail.version>
<curator.version>4.0.1</curator.version>
<protobuf.version>3.0.2</protobuf.version>
<grpc.version>1.12.0</grpc.version>
<protobuf.version>3.6.1</protobuf.version>
<grpc.version>1.16.1</grpc.version>
<lombok.version>1.16.18</lombok.version>
<paho.client.version>1.1.0</paho.client.version>
<netty.version>4.1.22.Final</netty.version>
<netty.version>4.1.30.Final</netty.version>
<os-maven-plugin.version>1.5.0</os-maven-plugin.version>
<rabbitmq.version>4.8.0</rabbitmq.version>
<surfire.version>2.19.1</surfire.version>
@ -85,6 +85,8 @@
<kafka.version>2.0.0</kafka.version>
<bucket4j.version>4.1.1</bucket4j.version>
<fst.version>2.57</fst.version>
<antlr.version>2.7.7</antlr.version>
<snakeyaml.version>1.23</snakeyaml.version>
</properties>
<modules>
@ -512,6 +514,16 @@
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>${snakeyaml.version}</version>
</dependency>
<dependency>
<groupId>antlr</groupId>
<artifactId>antlr</artifactId>
<version>${antlr.version}</version>
</dependency>
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
@ -600,6 +612,16 @@
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.github.fge</groupId>
<artifactId>json-schema-validator</artifactId>
@ -794,6 +816,12 @@
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>${fst.version}</version>
<exclusions>
<exclusion>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>io.springfox.ui</groupId>

22
ui/package-lock.json

@ -468,9 +468,9 @@
"integrity": "sha512-o+V/OzwNGpS30QmgP7DJWTdBJ2BMDut481qqB72sM0L59dkO6TNjRV7qubQCntGqGe98h9vObweQUVYTfEO4vg=="
},
"angular-material": {
"version": "1.1.9",
"resolved": "https://registry.npmjs.org/angular-material/-/angular-material-1.1.9.tgz",
"integrity": "sha512-kxyigi+7823k/31qQ0j6wL5FkCe/mw2bAg1kfEFzIvhUoe5Myr+0YoQyN8D8EGaaOyolXU/VPtxgKSfOCSLEBw=="
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/angular-material/-/angular-material-1.1.13.tgz",
"integrity": "sha512-qWc5WOhRa/sbQmiRwenOla2Pky3w+wgW0l5Wp3J6jmB/WWxMWW7+JMdCXo1diGEETTKTF2vLdeWTceDTNehmSw=="
},
"angular-material-data-table": {
"version": "0.10.10",
@ -7675,6 +7675,22 @@
}
}
},
"jstree": {
"version": "3.3.7",
"resolved": "https://registry.npmjs.org/jstree/-/jstree-3.3.7.tgz",
"integrity": "sha512-yzzalO1TbZ4HdPezO43LesGI4Wv2sB0Nl+4GfwO0YYvehGws5qtTAhlBISxfur9phMLwCtf9GjHlRx2ZLXyRnw==",
"requires": {
"jquery": ">=1.9.1"
}
},
"jstree-bootstrap-theme": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/jstree-bootstrap-theme/-/jstree-bootstrap-theme-1.0.1.tgz",
"integrity": "sha1-fV7cc6hG6Np/lPV6HMXd7p2eq0s=",
"requires": {
"jquery": ">=1.9.1"
}
},
"keycode": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/keycode/-/keycode-2.2.0.tgz",

4
ui/package.json

@ -27,7 +27,7 @@
"angular-gridster": "^0.13.14",
"angular-hotkeys": "^1.7.0",
"angular-jwt": "^0.1.6",
"angular-material": "1.1.9",
"angular-material": "1.1.13",
"angular-material-data-table": "^0.10.9",
"angular-material-expansion-panel": "^0.7.2",
"angular-material-icons": "^0.7.1",
@ -60,6 +60,8 @@
"jquery.terminal": "^1.5.0",
"js-beautify": "^1.6.4",
"json-schema-defaults": "^0.2.0",
"jstree": "^3.3.7",
"jstree-bootstrap-theme": "^1.0.1",
"leaflet": "^1.0.3",
"leaflet-providers": "^1.1.17",
"material-ui": "^0.16.1",

8
ui/src/app/api/entity-relation.service.js

@ -164,13 +164,13 @@ function EntityRelationService($http, $q) {
return deferred.promise;
}
function findByQuery(query) {
function findByQuery(query, config) {
var deferred = $q.defer();
var url = '/api/relations';
$http.post(url, query).then(function success(response) {
$http.post(url, query, config).then(function success(response) {
deferred.resolve(response.data);
}, function fail() {
deferred.reject();
}, function fail(e) {
deferred.reject(e);
});
return deferred.promise;
}

19
ui/src/app/api/user.service.js

@ -362,6 +362,25 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, time
$location.search('publicId', null);
deferred.reject();
});
} else if (locationSearch.accessToken) {
var token = locationSearch.accessToken;
var refreshToken = locationSearch.refreshToken;
$location.search('accessToken', null);
if (refreshToken) {
$location.search('refreshToken', null);
}
try {
updateAndValidateToken(token, 'jwt_token', false);
if (refreshToken) {
updateAndValidateToken(refreshToken, 'refresh_token', false);
} else {
store.remove('refresh_token');
store.remove('refresh_token_expiration');
}
} catch (e) {
deferred.reject();
}
procceedJwtTokenValidate();
} else {
procceedJwtTokenValidate();
}

4
ui/src/app/api/widget.service.js

@ -21,6 +21,7 @@ import thingsboardLedLight from '../components/led-light.directive';
import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget';
import thingsboardAlarmsTableWidget from '../widget/lib/alarms-table-widget';
import thingsboardEntitiesTableWidget from '../widget/lib/entities-table-widget';
import thingsboardEntitiesHierarchyWidget from '../widget/lib/entities-hierarchy-widget';
import thingsboardExtensionsTableWidget from '../widget/lib/extensions-table-widget';
import thingsboardRpcWidgets from '../widget/lib/rpc';
@ -32,6 +33,7 @@ import TbAnalogueCompass from '../widget/lib/analogue-compass';
import TbCanvasDigitalGauge from '../widget/lib/canvas-digital-gauge';
import TbMapWidget from '../widget/lib/map-widget';
import TbMapWidgetV2 from '../widget/lib/map-widget2';
import TripAnimationWidget from '../widget/lib/tripAnimation/trip-animation-widget';
import 'jquery.terminal/js/jquery.terminal.min.js';
import 'jquery.terminal/css/jquery.terminal.min.css';
@ -43,7 +45,7 @@ import thingsboardTypes from '../common/types.constant';
import thingsboardUtils from '../common/utils.service';
export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardExtensionsTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils])
thingsboardAlarmsTableWidget, thingsboardEntitiesTableWidget, thingsboardEntitiesHierarchyWidget, thingsboardExtensionsTableWidget, thingsboardRpcWidgets, thingsboardTypes, thingsboardUtils, TripAnimationWidget])
.factory('widgetService', WidgetService)
.name;

3
ui/src/app/app.js

@ -52,7 +52,8 @@ import 'react-schema-form';
import react from 'ngreact';
import '@flowjs/ng-flow/dist/ng-flow-standalone.min';
import 'ngFlowchart/dist/ngFlowchart';
import 'jstree/dist/jstree.min';
import 'jstree-bootstrap-theme/dist/themes/proton/style.min.css';
import 'typeface-roboto';
import 'font-awesome/css/font-awesome.min.css';
import 'angular-material/angular-material.min.css';

9
ui/src/app/components/json-form.directive.js

@ -71,7 +71,10 @@ function JsonForm($compile, $templateCache, $mdColorPicker) {
$compile(element.contents())(childScope);
}
scope.isFullscreen = false;
scope.formProps = {
isFullscreen: false,
option: {
formDefaults: {
startEmpty: true
@ -86,6 +89,10 @@ function JsonForm($compile, $templateCache, $mdColorPicker) {
},
onColorClick: function(event, key, val) {
scope.showColorPicker(event, val);
},
onToggleFullscreen: function() {
scope.isFullscreen = !scope.isFullscreen;
scope.formProps.isFullscreen = scope.isFullscreen;
}
};
@ -116,6 +123,8 @@ function JsonForm($compile, $templateCache, $mdColorPicker) {
});
}
scope.onFullscreenChanged = function() {}
scope.validate = function(){
if (scope.schema && scope.model) {
var result = utils.validateBySchema(scope.schema, scope.model);

4
ui/src/app/components/json-form.tpl.html

@ -15,4 +15,6 @@
limitations under the License.
-->
<react-component name="ReactSchemaForm" props="formProps" watch-depth="value"></react-component>
<div style="background: #fff;" tb-expand-fullscreen="isFullscreen" hide-expand-button="true" fullscreen-zindex="100" on-fullscreen-changed="onFullscreenChanged()">
<react-component name="ReactSchemaForm" props="formProps" watch-depth="value"></react-component>
</div>

206
ui/src/app/components/nav-tree.directive.js

@ -0,0 +1,206 @@
/*
* Copyright © 2016-2019 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 './nav-tree.scss';
/* eslint-disable import/no-unresolved, import/default */
import navTreeTemplate from './nav-tree.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.directives.navTree', [])
.directive('tbNavTree', NavTree)
.name;
/*@ngInject*/
function NavTree() {
return {
restrict: "E",
scope: true,
bindToController: {
loadNodes: '=',
editCallbacks: '=',
enableSearch: '@?',
onNodeSelected: '&',
onNodesInserted: '&',
searchCallback: '&?'
},
controller: NavTreeController,
controllerAs: 'vm',
templateUrl: navTreeTemplate
};
}
/*@ngInject*/
function NavTreeController($scope, $element, types) {
var vm = this;
vm.types = types;
$scope.$watch('vm.loadNodes', (newVal) => {
if (newVal) {
initTree();
}
});
function initTree() {
var config = {
core: {
multiple: false,
check_callback: true,
themes: { name: 'proton', responsive: true },
data: vm.loadNodes
}
};
if (vm.enableSearch) {
config.plugins = ["search"];
config.search = {
case_sensitive: false,
show_only_matches: true,
show_only_matches_children: false,
search_leaves_only: false
};
if (vm.searchCallback) {
config.search.search_callback = (searchText, node) => vm.searchCallback({searchText: searchText, node: node});
}
}
vm.treeElement = angular.element('.tb-nav-tree-container', $element)
.jstree(config);
vm.treeElement.on("changed.jstree", function (e, data) {
if (vm.onNodeSelected) {
vm.onNodeSelected({node: data.instance.get_selected(true)[0], event: e});
}
});
vm.treeElement.on("model.jstree", function (e, data) {
if (vm.onNodesInserted) {
vm.onNodesInserted({nodes: data.nodes, parent: data.parent});
}
});
if (vm.editCallbacks) {
vm.editCallbacks.selectNode = (id) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
vm.treeElement.jstree('deselect_all', true);
vm.treeElement.jstree('select_node', node);
}
};
vm.editCallbacks.deselectAll = () => {
vm.treeElement.jstree('deselect_all');
};
vm.editCallbacks.getNode = (id) => {
var node = vm.treeElement.jstree('get_node', id);
return node;
};
vm.editCallbacks.getParentNodeId = (id) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
return vm.treeElement.jstree('get_parent', node);
}
};
vm.editCallbacks.openNode = (id, cb) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
vm.treeElement.jstree('open_node', node, cb);
}
};
vm.editCallbacks.nodeIsOpen = (id) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
return vm.treeElement.jstree('is_open', node);
} else {
return true;
}
};
vm.editCallbacks.nodeIsLoaded = (id) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
return vm.treeElement.jstree('is_loaded', node);
} else {
return true;
}
};
vm.editCallbacks.refreshNode = (id) => {
if (id === '#') {
vm.treeElement.jstree('refresh');
vm.treeElement.jstree('redraw');
} else {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
var opened = vm.treeElement.jstree('is_open', node);
vm.treeElement.jstree('refresh_node', node);
vm.treeElement.jstree('redraw');
if (node.children && opened/* && !node.children.length*/) {
vm.treeElement.jstree('open_node', node);
}
}
}
};
vm.editCallbacks.updateNode = (id, newName) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
vm.treeElement.jstree('rename_node', node, newName);
}
};
vm.editCallbacks.createNode = (parentId, node, pos) => {
var parentNode = vm.treeElement.jstree('get_node', parentId);
if (parentNode) {
vm.treeElement.jstree('create_node', parentNode, node, pos);
}
};
vm.editCallbacks.deleteNode = (id) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
vm.treeElement.jstree('delete_node', node);
}
};
vm.editCallbacks.disableNode = (id) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
vm.treeElement.jstree('disable_node', node);
}
};
vm.editCallbacks.enableNode = (id) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
vm.treeElement.jstree('enable_node', node);
}
};
vm.editCallbacks.setNodeHasChildren = (id, hasChildren) => {
var node = vm.treeElement.jstree('get_node', id);
if (node) {
if (!node.children || !node.children.length) {
node.children = hasChildren;
node.state.loaded = !hasChildren;
node.state.opened = false;
vm.treeElement.jstree('_node_changed', node.id);
vm.treeElement.jstree('redraw');
}
}
};
vm.editCallbacks.search = (searchText) => {
vm.treeElement.jstree('search', searchText);
};
vm.editCallbacks.clearSearch = () => {
vm.treeElement.jstree('clear_search');
};
}
}
}

346
ui/src/app/components/nav-tree.scss

@ -0,0 +1,346 @@
/**
* Copyright © 2016-2019 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.
*/
.tb-nav-tree-container {
padding: 15px;
font-family: Roboto, "Helvetica Neue", sans-serif;
&.jstree-proton {
.jstree-node,
.jstree-icon {
background-image: url("../../png/jstree/32px.png");
}
.jstree-last {
background: transparent;
}
.jstree-themeicon-custom {
background-image: none;
&.material-icons {
font-size: 18px;
}
}
.jstree-anchor {
font-size: 16px;
}
}
&.jstree-proton-small {
.jstree-node,
.jstree-icon {
background-image: url("../../png/jstree/32px.png");
}
.jstree-last {
background: transparent;
}
.jstree-themeicon-custom {
background-image: none;
&.material-icons {
font-size: 14px;
}
}
.jstree-anchor {
font-size: 14px;
}
}
&.jstree-proton-large {
.jstree-node,
.jstree-icon {
background-image: url("../../png/jstree/32px.png");
}
.jstree-last {
background: transparent;
}
.jstree-themeicon-custom {
background-image: none;
&.material-icons {
font-size: 24px;
}
}
.jstree-anchor {
font-size: 20px;
}
}
a {
border-bottom: none;
i.jstree-themeicon-custom {
&.tb-user-group {
&::before {
content: "account_circle";
}
}
&.tb-customer-group {
&::before {
content: "supervisor_account";
}
}
&.tb-asset-group {
&::before {
content: "domain";
}
}
&.tb-device-group {
&::before {
content: "devices_other";
}
}
&.tb-entity-view-group {
&::before {
content: "view_quilt";
}
}
&.tb-dashboard-group {
&::before {
content: "dashboard";
}
}
&.tb-customer {
&::before {
content: "supervisor_account";
}
}
}
}
}
@media (max-width: 768px) {
.tb-nav-tree-container {
&.jstree-proton-responsive {
.jstree-node,
.jstree-icon,
.jstree-node > .jstree-ocl,
.jstree-themeicon,
.jstree-checkbox {
background-image: url("../../png/jstree/40px.png");
background-size: 120px 240px;
}
.jstree-container-ul {
overflow: visible;
}
.jstree-themeicon-custom {
background-color: transparent;
background-image: none;
background-position: 0 0;
&.material-icons {
margin: 0;
font-size: 24px;
}
}
.jstree-node,
.jstree-leaf > .jstree-ocl {
background: 0 0;
}
.jstree-node {
min-width: 40px;
min-height: 40px;
margin-left: 40px;
line-height: 40px;
white-space: nowrap;
background-repeat: repeat-y;
background-position: -80px 0;
}
.jstree-last {
background: 0 0;
}
.jstree-anchor {
height: 40px;
font-size: 1.1em;
font-weight: 700;
line-height: 40px;
text-shadow: 1px 1px #fff;
}
.jstree-icon,
.jstree-icon:empty {
width: 40px;
height: 40px;
line-height: 40px;
}
> {
.jstree-container-ul > .jstree-node {
margin-right: 0;
margin-left: 0;
}
}
.jstree-ocl,
.jstree-themeicon,
.jstree-checkbox {
background-size: 120px 240px;
}
.jstree-leaf > .jstree-ocl {
background: 0 0;
background-position: -40px -120px;
}
.jstree-last > .jstree-ocl {
background-position: -40px -160px;
}
.jstree-open > .jstree-ocl {
background-position: 0 0 !important;
}
.jstree-closed > .jstree-ocl {
background-position: 0 -40px !important;
}
.jstree-themeicon {
background-position: -40px -40px;
}
.jstree-checkbox,
.jstree-checkbox:hover {
background-position: -40px -80px;
}
&.jstree-checkbox-selection {
.jstree-clicked > .jstree-checkbox,
.jstree-clicked > .jstree-checkbox:hover {
background-position: 0 -80px;
}
}
.jstree-checked > .jstree-checkbox,
.jstree-checked > .jstree-checkbox:hover {
background-position: 0 -80px;
}
.jstree-anchor > .jstree-undetermined,
.jstree-anchor > .jstree-undetermined:hover {
background-position: 0 -120px;
}
.jstree-striped {
background: 0 0;
}
.jstree-wholerow {
height: 40px;
background: #ebebeb;
border-top: 1px solid rgba(255, 255, 255, .7);
border-bottom: 1px solid rgba(64, 64, 64, .2);
}
.jstree-wholerow-hovered {
background: #e7f4f9;
}
.jstree-wholerow-clicked {
background: #beebff;
}
.jstree-children {
.jstree-last > .jstree-wholerow {
box-shadow: inset 0 -6px 3px -5px #666;
}
.jstree-open > .jstree-wholerow {
border-top: 0;
box-shadow: inset 0 6px 3px -5px #666;
}
.jstree-open + .jstree-open {
box-shadow: none;
}
}
&.jstree-rtl {
.jstree-node {
margin-right: 40px;
margin-left: 0;
}
.jstree-container-ul > .jstree-node {
margin-right: 0;
}
.jstree-closed > .jstree-ocl {
background-position: -40px 0 !important;
}
}
}
}
}
.tb-nav-tree .md-button.tb-active {
font-weight: 500;
background-color: rgba(255, 255, 255, .15);
}
.tb-nav-tree,
.tb-nav-tree ul {
margin-top: 0;
list-style: none;
&:first-child {
padding: 0;
}
li {
.md-button {
width: 100%;
max-height: 40px;
padding: 0 16px;
margin: 0;
overflow: hidden;
line-height: 40px;
color: inherit;
text-align: left;
text-decoration: none;
text-overflow: ellipsis;
text-transform: none;
text-rendering: optimizeLegibility;
white-space: nowrap;
cursor: pointer;
border-radius: 0;
span {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
}

18
ui/src/app/components/nav-tree.tpl.html

@ -0,0 +1,18 @@
<!--
Copyright © 2016-2019 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.
-->
<div class="tb-nav-tree-container"></div>

36
ui/src/app/components/react/json-form-ace-editor.jsx

@ -34,8 +34,10 @@ class ThingsboardAceEditor extends React.Component {
this.onFocus = this.onFocus.bind(this);
this.onTidy = this.onTidy.bind(this);
this.onLoad = this.onLoad.bind(this);
this.onToggleFull = this.onToggleFull.bind(this);
var value = props.value ? props.value + '' : '';
this.state = {
isFull: false,
value: value,
focused: false
};
@ -76,9 +78,26 @@ class ThingsboardAceEditor extends React.Component {
}
onLoad(editor) {
this.aceEditor = editor;
fixAceEditor(editor);
}
onToggleFull() {
this.setState({ isFull: !this.state.isFull });
this.props.onToggleFullscreen();
this.updateAceEditorSize = true;
}
componentDidUpdate() {
if (this.updateAceEditorSize) {
if (this.aceEditor) {
this.aceEditor.resize();
this.aceEditor.renderer.updateFull();
}
this.updateAceEditorSize = false;
}
}
render() {
const styles = reactCSS({
@ -108,18 +127,23 @@ class ThingsboardAceEditor extends React.Component {
if (this.state.focused) {
labelClass += " tb-focused";
}
var containerClass = "tb-container";
var style = this.props.form.style || {width: '100%'};
if (this.state.isFull) {
containerClass += " fullscreen-form-field";
}
return (
<div className="tb-container">
<div className={containerClass}>
<label className={labelClass}>{this.props.form.title}</label>
<div className="json-form-ace-editor">
<div className="title-panel">
<label>{this.props.mode}</label>
<FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={'Tidy'} onTouchTap={this.onTidy}/>
<FlatButton style={ styles.tidyButtonStyle } className="tidy-button" label={this.state.isFull ? 'Exit fullscreen' : 'Fullscreen'} onTouchTap={this.onToggleFull}/>
</div>
<AceEditor mode={this.props.mode}
height="150px"
width="300px"
height={this.state.isFull ? "100%" : "150px"}
width={this.state.isFull ? "100%" : "300px"}
theme="github"
onChange={this.onValueChanged}
onFocus={this.onFocus}
@ -132,10 +156,10 @@ class ThingsboardAceEditor extends React.Component {
enableBasicAutocompletion={true}
enableSnippets={true}
enableLiveAutocompletion={true}
style={this.props.form.style || {width: '100%'}}/>
style={style}/>
</div>
<div className="json-form-error"
style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div>
style={{opacity: this.props.valid ? '0' : '1'}}>{this.props.error}</div>
</div>
);
}

7
ui/src/app/components/react/json-form-ace-editor.scss

@ -13,6 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.fullscreen-form-field {
.json-form-ace-editor {
height: calc(100% - 60px);
}
}
.json-form-ace-editor {
position: relative;
height: 100%;

2
ui/src/app/components/react/json-form-array.jsx

@ -131,7 +131,7 @@ class ThingsboardArray extends React.Component {
}
let forms = this.props.form.items.map(function(form, index){
var copy = this.copyWithIndex(form, i);
return this.props.builder(copy, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.mapper, this.props.builder);
return this.props.builder(copy, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
}.bind(this));
arrays.push(
<li key={keys[i]} className="list-group-item">

2
ui/src/app/components/react/json-form-fieldset.jsx

@ -19,7 +19,7 @@ class ThingsboardFieldSet extends React.Component {
render() {
let forms = this.props.form.items.map(function(form, index){
return this.props.builder(form, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.mapper, this.props.builder);
return this.props.builder(form, this.props.model, index, this.props.onChange, this.props.onColorClick, this.props.onToggleFullscreen, this.props.mapper, this.props.builder);
}.bind(this));
return (

3
ui/src/app/components/react/json-form-react.jsx

@ -50,7 +50,8 @@ ReactSchemaForm.propTypes = {
model: React.PropTypes.object,
option: React.PropTypes.object,
onModelChange: React.PropTypes.func,
onColorClick: React.PropTypes.func
onColorClick: React.PropTypes.func,
onToggleFullscreen: React.PropTypes.func
}
ReactSchemaForm.defaultProps = {

18
ui/src/app/components/react/json-form-schema-form.jsx

@ -63,6 +63,7 @@ class ThingsboardSchemaForm extends React.Component {
this.onChange = this.onChange.bind(this);
this.onColorClick = this.onColorClick.bind(this);
this.onToggleFullscreen = this.onToggleFullscreen.bind(this);
this.hasConditions = false;
}
@ -78,7 +79,11 @@ class ThingsboardSchemaForm extends React.Component {
this.props.onColorClick(event, key, val);
}
builder(form, model, index, onChange, onColorClick, mapper) {
onToggleFullscreen() {
this.props.onToggleFullscreen();
}
builder(form, model, index, onChange, onColorClick, onToggleFullscreen, mapper) {
var type = form.type;
let Field = this.mapper[type];
if(!Field) {
@ -91,7 +96,7 @@ class ThingsboardSchemaForm extends React.Component {
return null;
}
}
return <Field model={model} form={form} key={index} onChange={onChange} onColorClick={onColorClick} mapper={mapper} builder={this.builder}/>
return <Field model={model} form={form} key={index} onChange={onChange} onColorClick={onColorClick} onToggleFullscreen={onToggleFullscreen} mapper={mapper} builder={this.builder}/>
}
render() {
@ -101,11 +106,16 @@ class ThingsboardSchemaForm extends React.Component {
mapper = _.merge(this.mapper, this.props.mapper);
}
let forms = merged.map(function(form, index) {
return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, mapper);
return this.builder(form, this.props.model, index, this.onChange, this.onColorClick, this.onToggleFullscreen, mapper);
}.bind(this));
let formClass = 'SchemaForm';
if (this.props.isFullscreen) {
formClass += ' SchemaFormFullscreen';
}
return (
<div style={{width: '100%'}} className='SchemaForm'>{forms}</div>
<div style={{width: '100%'}} className={formClass}>{forms}</div>
);
}
}

18
ui/src/app/components/react/json-form.scss

@ -21,6 +21,24 @@ $swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
$input-label-float-offset: 6px !default;
$input-label-float-scale: .75 !default;
.SchemaForm {
&.SchemaFormFullscreen {
position: relative;
width: 100%;
height: 100%;
> div:not(.fullscreen-form-field) {
display: none;
}
> div.fullscreen-form-field {
position: relative;
width: 100%;
height: 100%;
}
}
}
.json-form-error {
position: relative;
bottom: -5px;

2
ui/src/app/layout/index.js

@ -27,6 +27,7 @@ import thingsboardApiUser from '../api/user.service';
import thingsboardNoAnimate from '../components/no-animate.directive';
import thingsboardOnFinishRender from '../components/finish-render.directive';
import thingsboardSideMenu from '../components/side-menu.directive';
import thingsboardNavTree from '../components/nav-tree.directive';
import thingsboardDashboardAutocomplete from '../components/dashboard-autocomplete.directive';
import thingsboardKvMap from '../components/kv-map.directive';
import thingsboardJsonObjectEdit from '../components/json-object-edit.directive';
@ -89,6 +90,7 @@ export default angular.module('thingsboard.home', [
thingsboardNoAnimate,
thingsboardOnFinishRender,
thingsboardSideMenu,
thingsboardNavTree,
thingsboardDashboardAutocomplete,
thingsboardKvMap,
thingsboardJsonObjectEdit,

3
ui/src/app/locale/locale.constant-en_US.json

@ -1566,7 +1566,8 @@
"row-click": "On row click",
"polygon-click": "On polygon click",
"marker-click": "On marker click",
"tooltip-tag-action": "Tooltip tag action"
"tooltip-tag-action": "Tooltip tag action",
"node-selected": "On node selected"
}
},
"language": {

8
ui/src/app/locale/translate-handler.js

@ -18,12 +18,12 @@
.name;
/*@ngInject*/
function ThingsboardMissingTranslateHandler($log, types) {
function ThingsboardMissingTranslateHandler(/*$log, types*/) {
return function (translationId) {
if (translationId && !translationId.startsWith(types.translate.customTranslationsPrefix)) {
return function (/*translationId*/) {
/*if (translationId && !translationId.startsWith(types.translate.customTranslationsPrefix)) {
$log.warn('Translation for ' + translationId + ' doesn\'t exist');
}
}*/
};
}

530
ui/src/app/widget/lib/entities-hierarchy-widget.js

@ -0,0 +1,530 @@
/*
* Copyright © 2016-2019 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 './entities-hierarchy-widget.scss';
/* eslint-disable import/no-unresolved, import/default */
import entitiesHierarchyWidgetTemplate from './entities-hierarchy-widget.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
export default angular.module('thingsboard.widgets.entitiesHierarchyWidget', [])
.directive('tbEntitiesHierarchyWidget', EntitiesHierarchyWidget)
.name;
/*@ngInject*/
function EntitiesHierarchyWidget() {
return {
restrict: "E",
scope: true,
bindToController: {
hierarchyId: '=',
ctx: '='
},
controller: EntitiesHierarchyWidgetController,
controllerAs: 'vm',
templateUrl: entitiesHierarchyWidgetTemplate
};
}
/*@ngInject*/
function EntitiesHierarchyWidgetController($element, $scope, $q, $timeout, toast, types, entityService, entityRelationService /*$filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types*/) {
var vm = this;
vm.showData = true;
vm.nodeEditCallbacks = {};
vm.nodeIdCounter = 0;
vm.nodesMap = {};
vm.pendingUpdateNodeTasks = {};
vm.query = {
search: null
};
vm.searchAction = {
name: 'action.search',
show: true,
onAction: function() {
vm.enterFilterMode();
},
icon: 'search'
};
vm.onNodesInserted = onNodesInserted;
vm.onNodeSelected = onNodeSelected;
vm.enterFilterMode = enterFilterMode;
vm.exitFilterMode = exitFilterMode;
vm.searchCallback = searchCallback;
$scope.$watch('vm.ctx', function() {
if (vm.ctx && vm.ctx.defaultSubscription) {
vm.settings = vm.ctx.settings;
vm.widgetConfig = vm.ctx.widgetConfig;
vm.subscription = vm.ctx.defaultSubscription;
vm.datasources = vm.subscription.datasources;
initializeConfig();
updateDatasources();
}
});
$scope.$watch("vm.query.search", function(newVal, prevVal) {
if (!angular.equals(newVal, prevVal) && vm.query.search != null) {
updateSearchNodes();
}
});
$scope.$on('entities-hierarchy-data-updated', function(event, hierarchyId) {
if (vm.hierarchyId == hierarchyId) {
if (vm.subscription) {
updateNodeData(vm.subscription.data);
}
}
});
function initializeConfig() {
vm.ctx.widgetActions = [ vm.searchAction ];
var testNodeCtx = {
entity: {
id: {
entityType: 'DEVICE',
id: '123'
},
name: 'TEST DEV1'
},
data: {},
level: 2
};
var parentNodeCtx = angular.copy(testNodeCtx);
parentNodeCtx.level = 1;
testNodeCtx.parentNodeCtx = parentNodeCtx;
var nodeRelationQueryFunction = loadNodeCtxFunction(vm.settings.nodeRelationQueryFunction, 'nodeCtx', testNodeCtx);
var nodeIconFunction = loadNodeCtxFunction(vm.settings.nodeIconFunction, 'nodeCtx', testNodeCtx);
var nodeTextFunction = loadNodeCtxFunction(vm.settings.nodeTextFunction, 'nodeCtx', testNodeCtx);
var nodeDisabledFunction = loadNodeCtxFunction(vm.settings.nodeDisabledFunction, 'nodeCtx', testNodeCtx);
var nodeOpenedFunction = loadNodeCtxFunction(vm.settings.nodeOpenedFunction, 'nodeCtx', testNodeCtx);
var nodeHasChildrenFunction = loadNodeCtxFunction(vm.settings.nodeHasChildrenFunction, 'nodeCtx', testNodeCtx);
var testNodeCtx2 = angular.copy(testNodeCtx);
testNodeCtx2.entity.name = 'TEST DEV2';
var nodesSortFunction = loadNodeCtxFunction(vm.settings.nodesSortFunction, 'nodeCtx1,nodeCtx2', testNodeCtx, testNodeCtx2);
vm.nodeRelationQueryFunction = nodeRelationQueryFunction || defaultNodeRelationQueryFunction;
vm.nodeIconFunction = nodeIconFunction || defaultNodeIconFunction;
vm.nodeTextFunction = nodeTextFunction || ((nodeCtx) => nodeCtx.entity.name);
vm.nodeDisabledFunction = nodeDisabledFunction || (() => false);
vm.nodeOpenedFunction = nodeOpenedFunction || defaultNodeOpenedFunction;
vm.nodeHasChildrenFunction = nodeHasChildrenFunction || (() => true);
vm.nodesSortFunction = nodesSortFunction || defaultSortFunction;
}
function loadNodeCtxFunction(functionBody, argNames, ...args) {
var nodeCtxFunction = null;
if (angular.isDefined(functionBody) && functionBody.length) {
try {
nodeCtxFunction = new Function(argNames, functionBody);
var res = nodeCtxFunction.apply(null, args);
if (angular.isUndefined(res)) {
nodeCtxFunction = null;
}
} catch (e) {
nodeCtxFunction = null;
}
}
return nodeCtxFunction;
}
function enterFilterMode () {
vm.query.search = '';
vm.ctx.hideTitlePanel = true;
$timeout(()=>{
angular.element(vm.ctx.$container).find('.searchInput').focus();
})
}
function exitFilterMode () {
vm.query.search = null;
updateSearchNodes();
vm.ctx.hideTitlePanel = false;
}
function searchCallback (searchText, node) {
var theNode = vm.nodesMap[node.id];
if (theNode && theNode.data.searchText) {
return theNode.data.searchText.includes(searchText.toLowerCase());
}
return false;
}
function updateDatasources() {
vm.loadNodes = loadNodes;
}
function updateSearchNodes() {
if (vm.query.search != null) {
vm.nodeEditCallbacks.search(vm.query.search);
} else {
vm.nodeEditCallbacks.clearSearch();
}
}
function onNodesInserted(nodes/*, parent*/) {
if (nodes) {
nodes.forEach((nodeId) => {
var task = vm.pendingUpdateNodeTasks[nodeId];
if (task) {
task();
delete vm.pendingUpdateNodeTasks[nodeId];
}
});
}
}
function onNodeSelected(node, event) {
var nodeId;
if (!node) {
nodeId = -1;
} else {
nodeId = node.id;
}
if (nodeId !== -1) {
var selectedNode = vm.nodesMap[nodeId];
if (selectedNode) {
var descriptors = vm.ctx.actionsApi.getActionDescriptors('nodeSelected');
if (descriptors.length) {
var entity = selectedNode.data.nodeCtx.entity;
vm.ctx.actionsApi.handleWidgetAction(event, descriptors[0], entity.id, entity.name, { nodeCtx: selectedNode.data.nodeCtx });
}
}
}
}
function updateNodeData(subscriptionData) {
var affectedNodes = [];
if (subscriptionData) {
for (var i=0;i<subscriptionData.length;i++) {
var datasource = subscriptionData[i].datasource;
if (datasource.nodeId) {
var node = vm.nodesMap[datasource.nodeId];
var key = subscriptionData[i].dataKey.label;
var value = undefined;
if (subscriptionData[i].data && subscriptionData[i].data.length) {
value = subscriptionData[i].data[0][1];
}
if (node.data.nodeCtx.data[key] !== value) {
if (affectedNodes.indexOf(datasource.nodeId) === -1) {
affectedNodes.push(datasource.nodeId);
}
node.data.nodeCtx.data[key] = value;
}
}
}
}
affectedNodes.forEach((nodeId) => {
var node = vm.nodeEditCallbacks.getNode(nodeId);
if (node) {
updateNodeStyle(vm.nodesMap[nodeId]);
} else {
vm.pendingUpdateNodeTasks[nodeId] = () => {
updateNodeStyle(vm.nodesMap[nodeId]);
};
}
});
}
function updateNodeStyle(node) {
var newText = prepareNodeText(node);
if (!angular.equals(node.text, newText)) {
node.text = newText;
vm.nodeEditCallbacks.updateNode(node.id, node.text);
}
var newDisabled = vm.nodeDisabledFunction(node.data.nodeCtx);
if (!angular.equals(node.state.disabled, newDisabled)) {
node.state.disabled = newDisabled;
if (node.state.disabled) {
vm.nodeEditCallbacks.disableNode(node.id);
} else {
vm.nodeEditCallbacks.enableNode(node.id);
}
}
var newHasChildren = vm.nodeHasChildrenFunction(node.data.nodeCtx);
if (!angular.equals(node.children, newHasChildren)) {
node.children = newHasChildren;
vm.nodeEditCallbacks.setNodeHasChildren(node.id, node.children);
}
}
function prepareNodeText(node) {
var nodeIcon = prepareNodeIcon(node.data.nodeCtx);
var nodeText = vm.nodeTextFunction(node.data.nodeCtx);
node.data.searchText = nodeText ? nodeText.replace(/<[^>]+>/g, '').toLowerCase() : "";
return nodeIcon + nodeText;
}
function loadNodes(node, cb) {
if (node.id === '#') {
var tasks = [];
for (var i=0;i<vm.datasources.length;i++) {
var datasource = vm.datasources[i];
tasks.push(datasourceToNode(datasource));
}
$q.all(tasks).then((nodes) => {
cb(prepareNodes(nodes));
updateNodeData(vm.subscription.data);
});
} else {
if (node.data && node.data.nodeCtx.entity && node.data.nodeCtx.entity.id && node.data.nodeCtx.entity.id.entityType !== 'function') {
var relationQuery = prepareNodeRelationQuery(node.data.nodeCtx);
entityRelationService.findByQuery(relationQuery, {ignoreErrors: true, ignoreLoading: true}).then(
(entityRelations) => {
var tasks = [];
for (var i=0;i<entityRelations.length;i++) {
var relation = entityRelations[i];
var targetId = relationQuery.parameters.direction === types.entitySearchDirection.from ? relation.to : relation.from;
tasks.push(entityIdToNode(targetId.entityType, targetId.id, node.data.datasource, node.data.nodeCtx));
}
$q.all(tasks).then((nodes) => {
cb(prepareNodes(nodes));
});
},
(error) => {
var errorText = "Failed to get relations!";
if (error && error.status === 400) {
errorText = "Invalid relations query returned by 'Node relations query function'! Please check widget configuration!";
}
showError(errorText);
}
);
} else {
cb([]);
}
}
}
function showError(errorText) {
var toastParent = angular.element('.tb-entities-hierarchy', $element);
toast.showError(errorText, toastParent, 'bottom left');
}
function prepareNodes(nodes) {
nodes = nodes.filter((node) => node !== null);
nodes.sort((node1, node2) => vm.nodesSortFunction(node1.data.nodeCtx, node2.data.nodeCtx));
return nodes;
}
function datasourceToNode(datasource, parentNodeCtx) {
var deferred = $q.defer();
resolveEntity(datasource).then(
(entity) => {
if (entity != null) {
var node = {
id: ++vm.nodeIdCounter
};
vm.nodesMap[node.id] = node;
datasource.nodeId = node.id;
node.icon = false;
var nodeCtx = {
parentNodeCtx: parentNodeCtx,
entity: entity,
data: {}
};
nodeCtx.level = parentNodeCtx ? parentNodeCtx.level + 1 : 1;
node.data = {
datasource: datasource,
nodeCtx: nodeCtx
};
node.state = {
disabled: vm.nodeDisabledFunction(node.data.nodeCtx),
opened: vm.nodeOpenedFunction(node.data.nodeCtx)
};
node.text = prepareNodeText(node);
node.children = vm.nodeHasChildrenFunction(node.data.nodeCtx);
deferred.resolve(node);
} else {
deferred.resolve(null);
}
}
);
return deferred.promise;
}
function entityIdToNode(entityType, entityId, parentDatasource, parentNodeCtx) {
var deferred = $q.defer();
var datasource = {
dataKeys: parentDatasource.dataKeys,
type: types.datasourceType.entity,
entityType: entityType,
entityId: entityId
};
datasourceToNode(datasource, parentNodeCtx).then(
(node) => {
if (node != null) {
var subscriptionOptions = {
type: types.widgetType.latest.value,
datasources: [datasource],
callbacks: {
onDataUpdated: (subscription) => {
updateNodeData(subscription.data);
}
}
};
vm.ctx.subscriptionApi.createSubscription(subscriptionOptions, true).then(
(/*subscription*/) => {
deferred.resolve(node);
}
);
} else {
deferred.resolve(node);
}
}
);
return deferred.promise;
}
function resolveEntity(datasource) {
var deferred = $q.defer();
if (datasource.type === types.datasourceType.function) {
var entity = {
id: {
entityType: "function"
},
name: datasource.name
}
deferred.resolve(entity);
} else {
entityService.getEntity(datasource.entityType, datasource.entityId, {ignoreLoading: true}).then(
(entity) => {
deferred.resolve(entity);
},
() => {
deferred.resolve(null);
}
);
}
return deferred.promise;
}
function prepareNodeRelationQuery(nodeCtx) {
var relationQuery = vm.nodeRelationQueryFunction(nodeCtx);
if (relationQuery && relationQuery === 'default') {
relationQuery = defaultNodeRelationQueryFunction(nodeCtx);
}
return relationQuery;
}
function defaultNodeRelationQueryFunction(nodeCtx) {
var entity = nodeCtx.entity;
var query = {
parameters: {
rootId: entity.id.id,
rootType: entity.id.entityType,
direction: types.entitySearchDirection.from,
relationTypeGroup: "COMMON",
maxLevel: 1
},
filters: [
{
relationType: "Contains",
entityTypes: []
}
]
};
return query;
}
function prepareNodeIcon(nodeCtx) {
var iconInfo = vm.nodeIconFunction(nodeCtx);
if (iconInfo && iconInfo === 'default') {
iconInfo = defaultNodeIconFunction(nodeCtx);
}
if (iconInfo && (iconInfo.iconUrl || iconInfo.materialIcon)) {
if (iconInfo.materialIcon) {
return materialIconHtml(iconInfo.materialIcon);
} else {
return iconUrlHtml(iconInfo.iconUrl);
}
} else {
return "";
}
}
function materialIconHtml(materialIcon) {
return '<md-icon aria-label="'+materialIcon+'" class="node-icon material-icons" role="img" aria-hidden="false">'+materialIcon+'</md-icon>';
}
function iconUrlHtml(iconUrl) {
return '<div class="node-icon" style="background-image: url('+iconUrl+');">&nbsp;</div>';
}
function defaultNodeIconFunction(nodeCtx) {
var materialIcon = 'insert_drive_file';
var entity = nodeCtx.entity;
if (entity && entity.id && entity.id.entityType) {
switch (entity.id.entityType) {
case 'function':
materialIcon = 'functions';
break;
case types.entityType.device:
materialIcon = 'devices_other';
break;
case types.entityType.asset:
materialIcon = 'domain';
break;
case types.entityType.tenant:
materialIcon = 'supervisor_account';
break;
case types.entityType.customer:
materialIcon = 'supervisor_account';
break;
case types.entityType.user:
materialIcon = 'account_circle';
break;
case types.entityType.dashboard:
materialIcon = 'dashboards';
break;
case types.entityType.alarm:
materialIcon = 'notifications_active';
break;
case types.entityType.entityView:
materialIcon = 'view_quilt';
break;
}
}
return {
materialIcon: materialIcon
};
}
function defaultNodeOpenedFunction(nodeCtx) {
return nodeCtx.level <= 4;
}
function defaultSortFunction(nodeCtx1, nodeCtx2) {
var result = nodeCtx1.entity.id.entityType.localeCompare(nodeCtx2.entity.id.entityType);
if (result === 0) {
result = nodeCtx1.entity.name.localeCompare(nodeCtx2.entity.name);
}
return result;
}
}

110
ui/src/app/widget/lib/entities-hierarchy-widget.scss

@ -0,0 +1,110 @@
/**
* Copyright © 2016-2019 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.
*/
.tb-has-timewindow {
.tb-entities-hierarchy {
md-toolbar {
min-height: 60px;
max-height: 60px;
}
}
}
.tb-entities-hierarchy {
md-toolbar {
min-height: 39px;
max-height: 39px;
}
.tb-entities-nav-tree-panel {
overflow-x: auto;
overflow-y: auto;
.tb-nav-tree-container {
&.jstree-proton {
.jstree-anchor {
div.node-icon {
display: inline-block;
width: 22px;
height: 22px;
margin-right: 2px;
margin-bottom: 2px;
background-color: transparent;
background-repeat: no-repeat;
background-attachment: scroll;
background-position: center center;
background-size: 18px 18px;
}
md-icon.node-icon {
width: 22px;
min-width: 22px;
height: 22px;
min-height: 22px;
margin-right: 2px;
margin-bottom: 2px;
color: inherit;
&.material-icons { /* stylelint-disable-line selector-max-class */
font-size: 18px;
line-height: 22px;
text-align: center;
}
}
&.jstree-hovered:not(.jstree-clicked),
&.jstree-disabled {
div.node-icon { /* stylelint-disable-line selector-max-class */
opacity: .5;
}
}
}
}
}
}
}
@media (max-width: 768px) {
.tb-entities-hierarchy {
.tb-entities-nav-tree-panel {
.tb-nav-tree-container {
&.jstree-proton-responsive {
.jstree-anchor {
div.node-icon {
width: 40px;
height: 40px;
margin: 0;
background-size: 24px 24px;
}
md-icon.node-icon {
width: 40px;
min-width: 40px;
height: 40px;
min-height: 40px;
margin: 0;
&.material-icons { /* stylelint-disable-line selector-max-class */
font-size: 24px;
line-height: 40px;
}
}
}
}
}
}
}
}

51
ui/src/app/widget/lib/entities-hierarchy-widget.tpl.html

@ -0,0 +1,51 @@
<!--
Copyright © 2016-2019 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.
-->
<div class="tb-absolute-fill tb-entities-hierarchy" layout="column">
<div ng-show="vm.showData" flex class="tb-absolute-fill" layout="column">
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search != null">
<div class="md-toolbar-tools">
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
<md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
{{'entity.search' | translate}}
</md-tooltip>
</md-button>
<md-input-container flex>
<label>&nbsp;</label>
<input ng-model="vm.query.search" class="searchInput" placeholder="{{'entity.search' | translate}}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
<md-tooltip md-direction="{{vm.ctx.dashboard.isWidgetExpanded ? 'bottom' : 'top'}}">
{{ 'action.close' | translate }}
</md-tooltip>
</md-button>
</div>
</md-toolbar>
<div flex class="tb-entities-nav-tree-panel">
<tb-nav-tree
load-nodes="vm.loadNodes"
on-node-selected="vm.onNodeSelected(node, event)"
on-nodes-inserted="vm.onNodesInserted(nodes, parent)"
edit-callbacks="vm.nodeEditCallbacks"
enable-search="true"
search-callback="vm.searchCallback(searchText, node)"
></tb-nav-tree>
</div>
</div>
</div>

14
ui/src/app/widget/lib/google-map.js

@ -338,15 +338,21 @@ export default class TbGoogleMap {
locationSettings: settings,
dsIndex: location.dsIndex
});
let map = this;
if (onClickListener) {
google.maps.event.addListener(polygon, 'click', function (event) {
if (settings.displayTooltip) {
if (settings.displayTooltip ) {
if (settings.autocloseTooltip) {
map.tooltips.forEach((tooltip) => {
tooltip.popup.close();
});
}
if (!polygon.anchor) {
polygon.anchor = new google.maps.MVCObject();
}
polygon.anchor.set("position", event.latLng);
popup.open(this.map, polygon.anchor);
}
onClickListener();
});
@ -367,8 +373,8 @@ export default class TbGoogleMap {
strokeColor: color,
fillColor: color,
strokeWeight: settings.polygonStrokeWeight
}
};
polygon.setOptions(options);
}
/* eslint-disable no-undef ,no-unused-vars*/

8
ui/src/app/widget/lib/map-widget2.js

@ -546,7 +546,13 @@ export default class TbMapWidgetV2 {
function mapPolygonArray (rawArray) {
let latLngArray = rawArray.map(function (el) {
if (el.length === 2) {
return tbMap.map.createLatLng(el[0], el[1]);
if (!angular.isNumber(el[0]) && !angular.isNumber(el[1])) {
return el.map(function (subEl) {
return mapPolygonArray(subEl);
})
} else {
return tbMap.map.createLatLng(el[0], el[1]);
}
} else if (el.length > 2) {
return mapPolygonArray(el);
} else {

9
ui/src/app/widget/lib/tencent-map.js

@ -287,7 +287,7 @@ export default class TbTencentMap {
popup.open();
popup.setPosition(marker);
});
this.tooltips.push({
map.tooltips.push({
markerArgs: markerArgs,
popup: popup,
locationSettings: settings,
@ -353,6 +353,11 @@ export default class TbTencentMap {
if (onClickListener) {
qq.maps.event.addListener(polygon, 'click', function (event) {
if (settings.autocloseTooltip) {
map.tooltips.forEach((tooltip) => {
tooltip.popup.close();
});
}
if (settings.displayTooltip) {
popup.setMap(this.map);
popup.setPosition(event.latLng);
@ -379,7 +384,7 @@ export default class TbTencentMap {
fillColor: color,
strokeWeight: settings.polygonStrokeWeight
}
polygon.setOptions(options);
}
/* eslint-disable no-undef ,no-unused-vars*/

700
ui/src/app/widget/lib/tripAnimation/trip-animation-widget.js

File diff suppressed because one or more lines are too long

106
ui/src/app/widget/lib/tripAnimation/trip-animation-widget.scss

@ -0,0 +1,106 @@
/**
* Copyright © 2016-2019 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.
*/
.heat-map-widget {
position: relative;
width: 100%;
height: 100%;
font-size: 16px;
line-height: 24px;
}
.heat-map-info-panel {
position: absolute;
top: 0;
right: 0;
z-index: 2;
background-color: rgba(0, 0, 0, .3);
border-bottom-left-radius: 5px;
.md-button {
min-width: auto;
}
}
.heat-map-tooltip {
position: absolute;
top: 47px;
right: 0;
z-index: 2;
padding: 10px;
background-color: #fff;
border-top-left-radius: 10px;
border-bottom-left-radius: 10px;
transition: .3s ease-in-out;
&-hidden {
transform: translateX(100%);
}
}
.heat-map-title {
padding: 10px;
}
.heat-map-control-panel {
position: absolute;
bottom: 0;
z-index: 2;
box-sizing: border-box;
width: 100%;
padding-right: 70px;
padding-left: 20px;
background: rgba(0, 0, 0, .3);
md-slider-container {
button {
max-width: none;
ng-md-icon {
width: 36px;
height: 36px;
svg {
width: inherit;
height: inherit;
}
}
}
.panel-timer {
max-width: none;
font-size: 20px;
font-weight: 600;
}
}
}
.heat-map-container {
position: relative;
z-index: 1;
flex: 1;
width: 100%;
}
#heat-map {
z-index: 1;
width: 100%;
height: 100%;
.pointsLayerMarkerIcon {
border-radius: 50%;
}
}

54
ui/src/app/widget/lib/tripAnimation/trip-animation-widget.tpl.html

@ -0,0 +1,54 @@
<!--
Copyright © 2016-2019 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.
-->
<div class="heat-map-widget tracking-widget" layout="column" layout-align="center start"
ng-style="{'padding-top' : (!vm.widgetConfig.showTitle && vm.ctx.settings.showLabel) ? '5px' : '0px'}">
<div ng-show="vm.ctx.settings.showLabel">
{{vm.trips[vm.activeTripIndex].settings.labelText}}
</div>
<div class="heat-map-control-panel" lang="row" layout-align="center start">
<md-slider-container>
<md-slider ng-model="vm.index" min="{{vm.minTime}}" max="{{vm.maxTime}}" ng-change="vm.recalculateTrips()"></md-slider>
<md-button aria-label="Play" ng-click="vm.playMove(true)" ng-disabled="vm.isPlaying">
<ng-md-icon icon="play_circle_outline"></ng-md-icon>
</md-button>
<md-select ng-model="vm.speed" aria-label="Speed selector">
<md-option ng-value="speed" flex="1" ng-repeat="speed in vm.speeds track by $index">{{ speed}}
</md-option>
</md-select>
<md-button aria-label="Stop playing" ng-click="vm.stopPlay()">
<ng-md-icon icon="pause_circle_outline"></ng-md-icon>
</md-button>
<div class="panel-timer">{{vm.trips[vm.activeTripIndex].timeRange[vm.index].ts | date:'medium'}}
</div>
</md-slider-container>
</div>
<div class="heat-map-container" layout="column">
<div flex id='heat-map'></div>
<div class="heat-map-info-panel" layout="row">
<md-button aria-label="tooltip" ng-show="vm.staticSettings.displayTooltip" ng-click="vm.showHideTooltip()">
<ng-md-icon icon="info"></ng-md-icon>
</md-button>
<!--<md-button aria-label="settings" ng-click="vm.openHideSettings()">-->
<!--<ng-md-icon icon="settings_applications"></ng-md-icon>-->
<!--</md-button>-->
</div>
<div class="heat-map-tooltip" class="heat-map-title" layout="column" ng-class="!vm.staticSettings.showTooltip ? 'heat-map-tooltip-hidden':''" ng-bind-html="vm.trips[vm.activeTripIndex].settings.tooltipText"
ng-style="{'background-color': vm.staticSettings.tooltipColor, 'opacity': vm.staticSettings.tooltipOpacity, 'color': vm.staticSettings.tooltipFontColor}">
</div>
</div>
</div>

BIN
ui/src/png/jstree/32px.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

BIN
ui/src/png/jstree/40px.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Loading…
Cancel
Save