Browse Source

Merge with master

pull/951/head
Igor Kulikov 8 years ago
parent
commit
2db354424c
  1. 2
      application/pom.xml
  2. 2
      application/src/main/conf/thingsboard.conf
  3. 1
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  4. 18
      application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
  5. 48
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java
  6. 1
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
  7. 38
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  8. 8
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
  9. 2
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java
  10. 12
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  11. 17
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  12. 11
      application/src/main/java/org/thingsboard/server/controller/BaseController.java
  13. 31
      application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
  14. 1
      application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
  15. 2
      application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
  16. 2
      application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
  17. 2
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  18. 10
      application/src/main/resources/thingsboard.yml
  19. 4
      application/src/main/scripts/install/install_dev_db.sh
  20. 2
      common/data/pom.xml
  21. 7
      common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
  22. 1
      common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
  23. 6
      common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
  24. 2
      common/message/pom.xml
  25. 11
      common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
  26. 24
      common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleChainAwareMsg.java
  27. 2
      common/pom.xml
  28. 2
      common/transport/pom.xml
  29. 2
      dao/pom.xml
  30. 19
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
  31. 14
      dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
  32. 2
      dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
  33. 5
      dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java
  34. 18
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  35. 2
      docker/cassandra-setup/Makefile
  36. 24
      docker/cassandra-upgrade/Dockerfile
  37. 12
      docker/cassandra-upgrade/Makefile
  38. 28
      docker/cassandra-upgrade/upgrade.sh
  39. 2
      docker/cassandra/Makefile
  40. 2
      docker/docker-compose.yml
  41. 2
      docker/k8s/cassandra-setup.yaml
  42. 43
      docker/k8s/cassandra-upgrade.yaml
  43. 2
      docker/k8s/cassandra.yaml
  44. 2
      docker/k8s/tb.yaml
  45. 2
      docker/k8s/zookeeper.yaml
  46. 2
      docker/tb/Makefile
  47. 2
      docker/zookeeper/Makefile
  48. 4
      netty-mqtt/pom.xml
  49. 2
      pom.xml
  50. 2
      rule-engine/pom.xml
  51. 2
      rule-engine/rule-engine-api/pom.xml
  52. 2
      rule-engine/rule-engine-components/pom.xml
  53. 27
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
  54. 86
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java
  55. 35
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeConfiguration.java
  56. 54
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java
  57. 38
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java
  58. 6
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  59. 2
      tools/pom.xml
  60. 2
      transport/coap/pom.xml
  61. 2
      transport/http/pom.xml
  62. 2
      transport/mqtt/pom.xml
  63. 3
      transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
  64. 2
      transport/pom.xml
  65. 22
      ui/package.json
  66. 2
      ui/pom.xml
  67. 2
      ui/src/app/api/alias-controller.js
  68. 2
      ui/src/app/api/dashboard.service.js
  69. 2
      ui/src/app/api/entity.service.js
  70. 26
      ui/src/app/api/rule-chain.service.js
  71. 163
      ui/src/app/api/time.service.js
  72. 7
      ui/src/app/api/user.service.js
  73. 76
      ui/src/app/app.config.js
  74. 4
      ui/src/app/app.js
  75. 7
      ui/src/app/app.run.js
  76. 106
      ui/src/app/common/types.constant.js
  77. 8
      ui/src/app/components/dashboard.scss
  78. 6
      ui/src/app/components/datasource-entity.tpl.html
  79. 4
      ui/src/app/components/datasource-func.tpl.html
  80. 10
      ui/src/app/components/details-sidenav.scss
  81. 4
      ui/src/app/components/json-content.directive.js
  82. 2
      ui/src/app/components/json-object-edit.directive.js
  83. 9
      ui/src/app/components/widget/action/manage-widget-actions.directive.js
  84. 6
      ui/src/app/components/widget/action/manage-widget-actions.tpl.html
  85. 6
      ui/src/app/components/widget/widget-config.tpl.html
  86. 6
      ui/src/app/components/widget/widget.controller.js
  87. 11
      ui/src/app/dashboard/dashboard.controller.js
  88. 18
      ui/src/app/dashboard/dashboard.routes.js
  89. 2
      ui/src/app/dashboard/dashboard.tpl.html
  90. 4
      ui/src/app/dashboard/states/select-target-state.tpl.html
  91. 7
      ui/src/app/device/device-card.tpl.html
  92. 4
      ui/src/app/entity/alias/entity-aliases.tpl.html
  93. 11
      ui/src/app/entity/attribute/attribute-table.directive.js
  94. 6
      ui/src/app/entity/attribute/attribute-table.tpl.html
  95. 21
      ui/src/app/entity/entity-select.directive.js
  96. 2
      ui/src/app/entity/entity-select.tpl.html
  97. 57
      ui/src/app/entity/entity-type-list.directive.js
  98. 15
      ui/src/app/entity/relation/relation-filters.directive.js
  99. 11
      ui/src/app/entity/relation/relation-table.directive.js
  100. 6
      ui/src/app/entity/relation/relation-table.tpl.html

2
application/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>

2
application/src/main/conf/thingsboard.conf

@ -15,7 +15,7 @@
#
export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@/data"
export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -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"

1
application/src/main/java/org/thingsboard/server/actors/app/AppActor.java

@ -111,6 +111,7 @@ public class AppActor extends RuleChainManagerActor {
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg);
break;
case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:

18
application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java

@ -29,11 +29,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Queue;
import java.util.UUID;
import java.util.*;
/**
* @author Andrew Shvayka
@ -88,7 +84,17 @@ public class RpcManagerActor extends ContextAwareActor {
private void onMsg(RpcBroadcastMsg msg) {
log.debug("Forwarding msg to session actors {}", msg);
sessionActors.keySet().forEach(address -> onMsg(msg.getMsg()));
sessionActors.keySet().forEach(address -> {
ClusterAPIProtos.ClusterMessage msgWithServerAddress = msg.getMsg()
.toBuilder()
.setServerAddress(ClusterAPIProtos.ServerAddress
.newBuilder()
.setHost(address.getHost())
.setPort(address.getPort())
.build())
.build();
onMsg(msgWithServerAddress);
});
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg()));
}

48
application/src/main/java/org/thingsboard/server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java

@ -0,0 +1,48 @@
/**
* Copyright © 2016-2018 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.actors.ruleChain;
import lombok.Data;
import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
import java.io.Serializable;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
final class RemoteToRuleChainTellNextMsg extends RuleNodeToRuleChainTellNextMsg implements TenantAwareMsg, RuleChainAwareMsg, Serializable {
private static final long serialVersionUID = 2459605482321657447L;
private final TenantId tenantId;
private final RuleChainId ruleChainId;
public RemoteToRuleChainTellNextMsg(RuleNodeToRuleChainTellNextMsg original, TenantId tenantId, RuleChainId ruleChainId) {
super(original.getOriginator(), original.getRelationTypes(), original.getMsg());
this.tenantId = tenantId;
this.ruleChainId = ruleChainId;
}
@Override
public MsgType getMsgType() {
return MsgType.REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG;
}
}

1
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java

@ -49,6 +49,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
processor.onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
break;
case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG:
case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);
break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG:

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

@ -20,6 +20,9 @@ import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.LoggingAdapter;
import com.datastax.driver.core.utils.UUIDs;
import java.util.Optional;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
import org.thingsboard.server.actors.device.RuleEngineQueuePutAckMsg;
@ -37,6 +40,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.rule.RuleChainService;
@ -217,16 +221,36 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
checkActive();
RuleNodeId originator = envelope.getOriginator();
List<RuleNodeRelation> relations = nodeRoutes.get(originator).stream()
.filter(r -> contains(envelope.getRelationTypes(), r.getType()))
.collect(Collectors.toList());
TbMsg msg = envelope.getMsg();
EntityId originatorEntityId = msg.getOriginator();
Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(originatorEntityId);
if (address.isPresent()) {
onRemoteTellNext(address.get(), envelope);
} else {
onLocalTellNext(envelope);
}
}
private void onRemoteTellNext(ServerAddress serverAddress, RuleNodeToRuleChainTellNextMsg envelope) {
TbMsg msg = envelope.getMsg();
logger.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator());
envelope = new RemoteToRuleChainTellNextMsg(envelope, tenantId, entityId);
systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(serverAddress, envelope));
}
private void onLocalTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
TbMsg msg = envelope.getMsg();
RuleNodeId originatorNodeId = envelope.getOriginator();
List<RuleNodeRelation> relations = nodeRoutes.get(originatorNodeId).stream()
.filter(r -> contains(envelope.getRelationTypes(), r.getType()))
.collect(Collectors.toList());
int relationsCount = relations.size();
EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
if (relationsCount == 0) {
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
if (ackId != null) {
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
}
} else if (relationsCount == 1) {
for (RuleNodeRelation relation : relations) {
pushToTarget(msg, relation.getOut(), relation.getType());
@ -244,7 +268,9 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
}
//TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
if (ackId != null) {
queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
}
}
}

8
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java

@ -20,12 +20,13 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
public final class RuleChainToRuleChainMsg implements TbActorMsg {
public final class RuleChainToRuleChainMsg implements TbActorMsg, RuleChainAwareMsg {
private final RuleChainId target;
private final RuleChainId source;
@ -33,6 +34,11 @@ public final class RuleChainToRuleChainMsg implements TbActorMsg {
private final String fromRelationType;
private final boolean enqueue;
@Override
public RuleChainId getRuleChainId() {
return target;
}
@Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;

2
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java

@ -27,7 +27,7 @@ import java.util.Set;
* Created by ashvayka on 19.03.18.
*/
@Data
final class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
private final RuleNodeId originator;
private final Set<String> relationTypes;

12
application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java

@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import scala.concurrent.duration.Duration;
@ -94,7 +95,8 @@ public class TenantActor extends RuleChainManagerActor {
onToDeviceActorMsg((DeviceAwareMsg) msg);
break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
onRuleChainMsg((RuleChainToRuleChainMsg) msg);
case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
onRuleChainMsg((RuleChainAwareMsg) msg);
break;
default:
return false;
@ -109,15 +111,19 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
if (ruleChainManager.getRootChainActor()!=null)
ruleChainManager.getRootChainActor().tell(msg, self());
else logger.info("[{}] No Root Chain", msg);
}
private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) {
if (ruleChainManager.getRootChainActor()!=null)
ruleChainManager.getRootChainActor().tell(msg, self());
else logger.info("[{}] No Root Chain", msg);
}
private void onRuleChainMsg(RuleChainToRuleChainMsg msg) {
ruleChainManager.getOrCreateActor(context(), msg.getTarget()).tell(msg, self());
private void onRuleChainMsg(RuleChainAwareMsg msg) {
ruleChainManager.getOrCreateActor(context(), msg.getRuleChainId()).tell(msg, self());
}

17
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
@ -53,7 +55,6 @@ public class AlarmController extends BaseController {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
return checkAlarmId(alarmId);
} catch (Exception e) {
throw handleException(e);
@ -79,8 +80,14 @@ public class AlarmController extends BaseController {
public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
try {
alarm.setTenantId(getCurrentUser().getTenantId());
return checkNotNull(alarmService.createOrUpdateAlarm(alarm));
Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
logEntityAction(savedAlarm.getId(), savedAlarm,
getCurrentUser().getCustomerId(),
savedAlarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedAlarm;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ASSET), alarm,
null, alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
@ -92,8 +99,9 @@ public class AlarmController extends BaseController {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
checkAlarmId(alarmId);
Alarm alarm = checkAlarmId(alarmId);
alarmService.ackAlarm(alarmId, System.currentTimeMillis()).get();
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
} catch (Exception e) {
throw handleException(e);
}
@ -106,8 +114,9 @@ public class AlarmController extends BaseController {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
checkAlarmId(alarmId);
Alarm alarm = checkAlarmId(alarmId);
alarmService.clearAlarm(alarmId, null, System.currentTimeMillis()).get();
logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
} catch (Exception e) {
throw handleException(e);
}

11
application/src/main/java/org/thingsboard/server/controller/BaseController.java

@ -529,18 +529,16 @@ public abstract class BaseController {
return baseUrl;
}
protected <I extends UUIDBased & EntityId> I emptyId(EntityType entityType) {
protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}
protected <E extends BaseData<I> & HasName,
I extends UUIDBased & EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
protected <E extends HasName, I extends EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
logEntityAction(getCurrentUser(), entityId, entity, customerId, actionType, e, additionalInfo);
}
protected <E extends BaseData<I> & HasName,
I extends UUIDBased & EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
protected <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
if (customerId == null || customerId.isNullUid()) {
customerId = user.getCustomerId();
@ -556,8 +554,7 @@ public abstract class BaseController {
return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null;
}
private <E extends BaseData<I> & HasName,
I extends UUIDBased & EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
private <E extends HasName, I extends EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
ActionType actionType, Object... additionalInfo) {
String msgType = null;
switch (actionType) {

31
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java

@ -24,10 +24,13 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
@ -58,7 +61,15 @@ public class EntityRelationController extends BaseController {
relation.setTypeGroup(RelationTypeGroup.COMMON);
}
relationService.saveRelation(relation);
logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_ADD_OR_UPDATE, null, relation);
logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_ADD_OR_UPDATE, null, relation);
} catch (Exception e) {
logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_ADD_OR_UPDATE, e, relation);
logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_ADD_OR_UPDATE, e, relation);
throw handleException(e);
}
}
@ -81,12 +92,21 @@ public class EntityRelationController extends BaseController {
checkEntityId(fromId);
checkEntityId(toId);
RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
EntityRelation relation = new EntityRelation(fromId, toId, strRelationType, relationTypeGroup);
try {
Boolean found = relationService.deleteRelation(fromId, toId, strRelationType, relationTypeGroup);
if (!found) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
}
logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_DELETED, null, relation);
logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_DELETED, null, relation);
} catch (Exception e) {
logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_DELETED, e, relation);
logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
ActionType.RELATION_DELETED, e, relation);
throw handleException(e);
}
}
@ -102,7 +122,9 @@ public class EntityRelationController extends BaseController {
checkEntityId(entityId);
try {
relationService.deleteEntityRelations(entityId);
logEntityAction(entityId, null, getCurrentUser().getCustomerId(), ActionType.RELATIONS_DELETED, null);
} catch (Exception e) {
logEntityAction(entityId, null, getCurrentUser().getCustomerId(), ActionType.RELATIONS_DELETED, e);
throw handleException(e);
}
}
@ -210,8 +232,8 @@ public class EntityRelationController extends BaseController {
@RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {TO_ID, TO_TYPE})
@ResponseBody
public List<EntityRelationInfo> findInfoByTo(@RequestParam(TO_ID) String strToId,
@RequestParam(TO_TYPE) String strToType,
@RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
@RequestParam(TO_TYPE) String strToType,
@RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
checkParameter(TO_ID, strToId);
checkParameter(TO_TYPE, strToType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId);
@ -276,10 +298,11 @@ public class EntityRelationController extends BaseController {
private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
RelationTypeGroup result = defaultValue;
if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length() > 0) {
try {
result = RelationTypeGroup.valueOf(strRelationTypeGroup);
} catch (IllegalArgumentException e) { }
} catch (IllegalArgumentException e) {
}
}
return result;
}

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

@ -46,7 +46,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
@Autowired
@Lazy
private TelemetryWebSocketService webSocketService;
@Override

2
application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java

@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
@ -67,6 +68,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
private ClusterRpcService rpcService;
@Autowired
@Lazy
private ActorService actorService;
private ScheduledExecutorService rpcCallBackExecutor;

2
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java

@ -28,6 +28,7 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.actors.service.ActorService;
@ -102,6 +103,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
private AttributesService attributesService;
@Autowired
@Lazy
private ActorService actorService;
@Autowired

2
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java

@ -432,7 +432,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
deviceSubscriptions.stream().filter(filter).forEach(s -> {
String sessionId = s.getWsSessionId();
List<TsKvEntry> subscriptionUpdate = f.apply(s);
if (subscriptionUpdate == null || !subscriptionUpdate.isEmpty()) {
if (subscriptionUpdate != null && !subscriptionUpdate.isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(s.getSubscriptionId(), subscriptionUpdate);
if (s.isLocal()) {
updateSubscriptionState(sessionId, s, update);

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

@ -225,7 +225,7 @@ sql:
# Actor system parameters
actors:
tenant:
create_components_on_init: true
create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}"
session:
max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:1}"
sync:
@ -328,6 +328,13 @@ spring.mvc.cors:
max-age: "1800"
allow-credentials: "true"
# spring serve gzip compressed static resources
spring.resources.chain:
gzipped: "true"
strategy:
content:
enabled: "true"
# HSQLDB DAO Configuration
spring:
data:
@ -378,6 +385,7 @@ audit_log:
"customer": "${AUDIT_LOG_MASK_CUSTOMER:W}"
"user": "${AUDIT_LOG_MASK_USER:W}"
"rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"
"alarm": "${AUDIT_LOG_MASK_ALARM:W}"
sink:
# Type of external sink. possible options: none, elasticsearch
type: "${AUDIT_LOG_SINK_TYPE:none}"

4
application/src/main/scripts/install/install_dev_db.sh

@ -30,13 +30,13 @@ export SQL_DATA_FOLDER=${SQL_DATA_FOLDER:-/tmp}
run_user=thingsboard
su -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
sudo -u "$run_user" -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dinstall.data_dir=${installDir} \
-Dinstall.load_demo=${loadDemo} \
-Dspring.jpa.hibernate.ddl-auto=none \
-Dinstall.upgrade=false \
-Dlogging.config=logback.xml \
org.springframework.boot.loader.PropertiesLauncher" "$run_user"
org.springframework.boot.loader.PropertiesLauncher"
if [ $? -ne 0 ]; then
echo "ThingsBoard DB installation failed!"

2
common/data/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

7
common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java

@ -31,7 +31,12 @@ public enum ActionType {
ACTIVATED(false), // log string id
SUSPENDED(false), // log string id
CREDENTIALS_READ(true), // log device id
ATTRIBUTES_READ(true); // log attributes
ATTRIBUTES_READ(true), // log attributes
RELATION_ADD_OR_UPDATE (false),
RELATION_DELETED (false),
RELATIONS_DELETED (false),
ALARM_ACK (false),
ALARM_CLEAR (false);
private final boolean isRead;

1
common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java

@ -60,4 +60,5 @@ public class EntityIdFactory {
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}
}

6
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java

@ -33,13 +33,19 @@ public class RelationsSearchParameters {
private UUID rootId;
private EntityType rootType;
private EntitySearchDirection direction;
private RelationTypeGroup relationTypeGroup;
private int maxLevel = 1;
public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel) {
this(entityId, direction, maxLevel, RelationTypeGroup.COMMON);
}
public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel, RelationTypeGroup relationTypeGroup) {
this.rootId = entityId.getId();
this.rootType = entityId.getEntityType();
this.direction = direction;
this.maxLevel = maxLevel;
this.relationTypeGroup = relationTypeGroup;
}
public EntityId getEntityId() {

2
common/message/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

11
common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java

@ -62,6 +62,11 @@ public enum MsgType {
*/
RULE_TO_RULE_CHAIN_TELL_NEXT_MSG,
/**
* Message forwarded from original rule chain to remote rule chain due to change in the cluster structure or originator entity of the TbMsg.
*/
REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG,
/**
* Message that is sent by RuleActor implementation to RuleActor itself to log the error.
*/
@ -101,6 +106,10 @@ public enum MsgType {
/**
* Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue.
*/
RULE_ENGINE_QUEUE_PUT_ACK_MSG, ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG, TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG, SESSION_TIMEOUT_MSG, SESSION_CTRL_MSG;
RULE_ENGINE_QUEUE_PUT_ACK_MSG,
ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG,
TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG,
SESSION_TIMEOUT_MSG,
SESSION_CTRL_MSG;
}

24
common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleChainAwareMsg.java

@ -0,0 +1,24 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.msg.aware;
import org.thingsboard.server.common.data.id.RuleChainId;
public interface RuleChainAwareMsg {
RuleChainId getRuleChainId();
}

2
common/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

2
common/transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>

2
dao/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>dao</artifactId>

19
dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java

@ -40,15 +40,14 @@ public interface AuditLogService {
TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink);
<E extends BaseData<I> & HasName,
I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(
TenantId tenantId,
CustomerId customerId,
UserId userId,
String userName,
I entityId,
E entity,
ActionType actionType,
Exception e, Object... additionalInfo);
<E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(
TenantId tenantId,
CustomerId customerId,
UserId userId,
String userName,
I entityId,
E entity,
ActionType actionType,
Exception e, Object... additionalInfo);
}

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

@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.audit.sink.AuditLogSink;
@ -115,7 +116,7 @@ public class AuditLogServiceImpl implements AuditLogService {
}
@Override
public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>>
public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>>
logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
ActionType actionType, Exception e, Object... additionalInfo) {
if (canLog(entityId.getEntityType(), actionType)) {
@ -156,14 +157,16 @@ public class AuditLogServiceImpl implements AuditLogService {
}
}
private <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> JsonNode constructActionData(I entityId,
E entity,
private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity,
ActionType actionType,
Object... additionalInfo) {
ObjectNode actionData = objectMapper.createObjectNode();
switch(actionType) {
case ADDED:
case UPDATED:
case ALARM_ACK:
case ALARM_CLEAR:
case RELATIONS_DELETED:
if (entity != null) {
ObjectNode entityNode = objectMapper.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) {
@ -240,6 +243,11 @@ public class AuditLogServiceImpl implements AuditLogService {
actionData.put("unassignedCustomerId", strCustomerId);
actionData.put("unassignedCustomerName", strCustomerName);
break;
case RELATION_ADD_OR_UPDATE:
case RELATION_DELETED:
EntityRelation relation = extractParameter(EntityRelation.class, 0, additionalInfo);
actionData.set("relation", objectMapper.valueToTree(relation));
break;
}
return actionData;
}

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

@ -57,7 +57,7 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
}
@Override
public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) {
public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) {
return null;
}

5
dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java

@ -16,8 +16,10 @@
package org.thingsboard.server.dao.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.Ticker;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
@ -28,6 +30,7 @@ import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -38,12 +41,14 @@ import java.util.stream.Collectors;
@ConfigurationProperties(prefix = "caffeine")
@EnableCaching
@Data
@Slf4j
public class CaffeineCacheConfiguration {
private Map<String, CacheSpecs> specs;
@Bean
public CacheManager cacheManager() {
log.trace("Initializing cache: {}", Arrays.toString(RemovalCause.values()));
SimpleCacheManager manager = new SimpleCacheManager();
if (specs != null) {
List<CaffeineCache> caches =

18
dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java

@ -402,7 +402,7 @@ public class BaseRelationService implements RelationService {
int maxLvl = params.getMaxLevel() > 0 ? params.getMaxLevel() : Integer.MAX_VALUE;
try {
ListenableFuture<Set<EntityRelation>> relationSet = findRelationsRecursively(params.getEntityId(), params.getDirection(), maxLvl, new ConcurrentHashMap<>());
ListenableFuture<Set<EntityRelation>> relationSet = findRelationsRecursively(params.getEntityId(), params.getDirection(), params.getRelationTypeGroup(), maxLvl, new ConcurrentHashMap<>());
return Futures.transform(relationSet, input -> {
List<EntityRelation> relations = new ArrayList<>();
if (filters == null || filters.isEmpty()) {
@ -518,14 +518,15 @@ public class BaseRelationService implements RelationService {
}
}
private ListenableFuture<Set<EntityRelation>> findRelationsRecursively(final EntityId rootId, final EntitySearchDirection direction, int lvl,
private ListenableFuture<Set<EntityRelation>> findRelationsRecursively(final EntityId rootId, final EntitySearchDirection direction,
RelationTypeGroup relationTypeGroup, int lvl,
final ConcurrentHashMap<EntityId, Boolean> uniqueMap) throws Exception {
if (lvl == 0) {
return Futures.immediateFuture(Collections.emptySet());
}
lvl--;
//TODO: try to remove this blocking operation
Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction).get());
Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction, relationTypeGroup).get());
Set<EntityId> childrenIds = new HashSet<>();
for (EntityRelation childRelation : children) {
log.trace("Found Relation: {}", childRelation);
@ -544,7 +545,7 @@ public class BaseRelationService implements RelationService {
}
List<ListenableFuture<Set<EntityRelation>>> futures = new ArrayList<>();
for (EntityId entityId : childrenIds) {
futures.add(findRelationsRecursively(entityId, direction, lvl, uniqueMap));
futures.add(findRelationsRecursively(entityId, direction, relationTypeGroup, lvl, uniqueMap));
}
//TODO: try to remove this blocking operation
List<Set<EntityRelation>> relations = Futures.successfulAsList(futures).get();
@ -552,12 +553,15 @@ public class BaseRelationService implements RelationService {
return Futures.immediateFuture(children);
}
private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction) {
private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction, RelationTypeGroup relationTypeGroup) {
ListenableFuture<List<EntityRelation>> relations;
if (relationTypeGroup == null) {
relationTypeGroup = RelationTypeGroup.COMMON;
}
if (direction == EntitySearchDirection.FROM) {
relations = findByFromAsync(rootId, RelationTypeGroup.COMMON);
relations = findByFromAsync(rootId, relationTypeGroup);
} else {
relations = findByToAsync(rootId, RelationTypeGroup.COMMON);
relations = findByToAsync(rootId, relationTypeGroup);
}
return relations;
}

2
docker/cassandra-setup/Makefile

@ -1,4 +1,4 @@
VERSION=2.0.3
VERSION=2.1.0
PROJECT=thingsboard
APP=cassandra-setup

24
docker/cassandra-upgrade/Dockerfile

@ -0,0 +1,24 @@
#
# Copyright © 2016-2018 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.
#
FROM openjdk:8-jre
ADD upgrade.sh /upgrade.sh
ADD thingsboard.deb /thingsboard.deb
RUN apt-get update \
&& apt-get install -y nmap \
&& chmod +x /upgrade.sh

12
docker/cassandra-upgrade/Makefile

@ -0,0 +1,12 @@
VERSION=2.1.0
PROJECT=thingsboard
APP=cassandra-upgrade
build:
cp ../../application/target/thingsboard.deb .
docker build --pull -t ${PROJECT}/${APP}:${VERSION} -t ${PROJECT}/${APP}:latest .
rm thingsboard.deb
push: build
docker push ${PROJECT}/${APP}:${VERSION}
docker push ${PROJECT}/${APP}:latest

28
docker/cassandra-upgrade/upgrade.sh

@ -0,0 +1,28 @@
#!/bin/bash
#
# Copyright © 2016-2018 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.
#
dpkg -i /thingsboard.deb
until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open"
do
echo "Wait for cassandra db to start..."
sleep 10
done
echo "Upgrading 'Thingsboard' schema..."
/usr/share/thingsboard/bin/install/upgrade.sh --fromVersion=$UPGRADE_FROM_VERSION

2
docker/cassandra/Makefile

@ -1,4 +1,4 @@
VERSION=2.0.3
VERSION=2.1.0
PROJECT=thingsboard
APP=cassandra

2
docker/docker-compose.yml

@ -18,7 +18,7 @@ version: '2'
services:
tb:
image: "thingsboard/application:2.0.3"
image: "thingsboard/application:2.1.0"
ports:
- "8080:8080"
- "1883:1883"

2
docker/k8s/cassandra-setup.yaml

@ -22,7 +22,7 @@ spec:
containers:
- name: cassandra-setup
imagePullPolicy: Always
image: thingsboard/cassandra-setup:2.0.3
image: thingsboard/cassandra-setup:2.1.0
env:
- name: ADD_DEMO_DATA
value: "true"

43
docker/k8s/cassandra-upgrade.yaml

@ -0,0 +1,43 @@
#
# Copyright © 2016-2018 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: cassandra-upgrade
spec:
containers:
- name: cassandra-upgrade
imagePullPolicy: Always
image: thingsboard/cassandra-upgrade:2.1.0
env:
- name: ADD_DEMO_DATA
value: "true"
- name : CASSANDRA_HOST
value: "cassandra-headless"
- name : CASSANDRA_PORT
value: "9042"
- name : DATABASE_TYPE
value: "cassandra"
- name : CASSANDRA_URL
value: "cassandra-headless:9042"
- name : UPGRADE_FROM_VERSION
value: "1.4.0"
command:
- sh
- -c
- /upgrade.sh
restartPolicy: Never

2
docker/k8s/cassandra.yaml

@ -54,7 +54,7 @@ spec:
topologyKey: "kubernetes.io/hostname"
containers:
- name: cassandra
image: thingsboard/cassandra:2.0.3
image: thingsboard/cassandra:2.1.0
imagePullPolicy: Always
ports:
- containerPort: 7000

2
docker/k8s/tb.yaml

@ -84,7 +84,7 @@ spec:
containers:
- name: tb
imagePullPolicy: Always
image: thingsboard/application:2.0.3
image: thingsboard/application:2.1.0
ports:
- containerPort: 8080
name: ui

2
docker/k8s/zookeeper.yaml

@ -87,7 +87,7 @@ spec:
containers:
- name: zk
imagePullPolicy: Always
image: thingsboard/zk:2.0.3
image: thingsboard/zk:2.1.0
ports:
- containerPort: 2181
name: client

2
docker/tb/Makefile

@ -1,4 +1,4 @@
VERSION=2.0.3
VERSION=2.1.0
PROJECT=thingsboard
APP=application

2
docker/zookeeper/Makefile

@ -1,4 +1,4 @@
VERSION=2.0.3
VERSION=2.1.0
PROJECT=thingsboard
APP=zk

4
netty-mqtt/pom.xml

@ -19,12 +19,12 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
<artifactId>netty-mqtt</artifactId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Netty MQTT Client</name>

2
pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Thingsboard</name>

2
rule-engine/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rule-engine</artifactId>

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

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

2
rule-engine/rule-engine-components/pom.xml

@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>

27
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java

@ -47,11 +47,12 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
public class TbMsgGeneratorNode implements TbNode {
public static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg";
private static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg";
private TbMsgGeneratorNodeConfiguration config;
private ScriptEngine jsEngine;
private long delay;
private long lastScheduledTs;
private EntityId originatorId;
private UUID nextTickId;
private TbMsg prevMsg;
@ -66,28 +67,40 @@ public class TbMsgGeneratorNode implements TbNode {
originatorId = ctx.getSelfId();
}
this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType");
sentTickMsg(ctx);
scheduleTickMsg(ctx);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) {
withCallback(generate(ctx),
m -> {ctx.tellNext(m, SUCCESS); sentTickMsg(ctx);},
t -> {ctx.tellFailure(msg, t); sentTickMsg(ctx);});
m -> {
ctx.tellNext(m, SUCCESS);
scheduleTickMsg(ctx);
},
t -> {
ctx.tellFailure(msg, t);
scheduleTickMsg(ctx);
});
}
}
private void sentTickMsg(TbContext ctx) {
private void scheduleTickMsg(TbContext ctx) {
long curTs = System.currentTimeMillis();
if (lastScheduledTs == 0L) {
lastScheduledTs = curTs;
}
lastScheduledTs = lastScheduledTs + delay;
long curDelay = Math.max(0L, (lastScheduledTs - curTs));
TbMsg tickMsg = ctx.newMsg(TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), "");
nextTickId = tickMsg.getId();
ctx.tellSelf(tickMsg, delay);
ctx.tellSelf(tickMsg, curDelay);
}
private ListenableFuture<TbMsg> generate(TbContext ctx) {
return ctx.getJsExecutor().executeAsync(() -> {
if (prevMsg == null) {
prevMsg = ctx.newMsg( "", originatorId, new TbMsgMetaData(), "{}");
prevMsg = ctx.newMsg("", originatorId, new TbMsgMetaData(), "{}");
}
TbMsg generated = jsEngine.executeGenerate(prevMsg);
prevMsg = ctx.newMsg(generated.getType(), originatorId, generated.getMetaData(), generated.getData());

86
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java

@ -0,0 +1,86 @@
/**
* Copyright © 2016-2018 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.rule.engine.delay;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
name = "delay",
configClazz = TbMsgDelayNodeConfiguration.class,
nodeDescription = "Delays incoming message",
nodeDetails = "Delays messages for configurable period.",
icon = "pause",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeMsgDelayConfig"
)
public class TbMsgDelayNode implements TbNode {
private static final String TB_MSG_DELAY_NODE_MSG = "TbMsgDelayNodeMsg";
private TbMsgDelayNodeConfiguration config;
private long delay;
private Map<UUID, TbMsg> pendingMsgs;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbMsgDelayNodeConfiguration.class);
this.delay = TimeUnit.SECONDS.toMillis(config.getPeriodInSeconds());
this.pendingMsgs = new HashMap<>();
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
if (msg.getType().equals(TB_MSG_DELAY_NODE_MSG)) {
TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData()));
if (pendingMsg != null) {
ctx.tellNext(pendingMsg, SUCCESS);
}
} else {
if(pendingMsgs.size() < config.getMaxPendingMsgs()) {
pendingMsgs.put(msg.getId(), msg);
TbMsg tickMsg = ctx.newMsg(TB_MSG_DELAY_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), msg.getId().toString());
ctx.tellSelf(tickMsg, delay);
} else {
ctx.tellNext(msg, FAILURE, new RuntimeException("Max limit of pending messages reached!"));
}
}
}
@Override
public void destroy() {
pendingMsgs.clear();
}
}

35
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeConfiguration.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2018 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.rule.engine.delay;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.EntityType;
@Data
public class TbMsgDelayNodeConfiguration implements NodeConfiguration<TbMsgDelayNodeConfiguration> {
private int periodInSeconds;
private int maxPendingMsgs;
@Override
public TbMsgDelayNodeConfiguration defaultConfiguration() {
TbMsgDelayNodeConfiguration configuration = new TbMsgDelayNodeConfiguration();
configuration.setPeriodInSeconds(60);
configuration.setMaxPendingMsgs(1000);
return configuration;
}
}

54
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java

@ -0,0 +1,54 @@
/**
* Copyright © 2016-2018 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.rule.engine.filter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
@Slf4j
@RuleNode(
type = ComponentType.FILTER,
name = "originator type",
configClazz = TbOriginatorTypeFilterNodeConfiguration.class,
relationTypes = {"True", "False"},
nodeDescription = "Filter incoming messages by message Originator Type",
nodeDetails = "If Originator Type of incoming message is expected - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
configDirective = "tbFilterNodeOriginatorTypeConfig")
public class TbOriginatorTypeFilterNode implements TbNode {
TbOriginatorTypeFilterNodeConfiguration config;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbOriginatorTypeFilterNodeConfiguration.class);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
EntityType originatorType = msg.getOriginator().getEntityType();
ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? "True" : "False");
}
@Override
public void destroy() {
}
}

38
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java

@ -0,0 +1,38 @@
/**
* Copyright © 2016-2018 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.rule.engine.filter;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.EntityType;
import java.util.Arrays;
import java.util.List;
@Data
public class TbOriginatorTypeFilterNodeConfiguration implements NodeConfiguration<TbOriginatorTypeFilterNodeConfiguration> {
private List<EntityType> originatorTypes;
@Override
public TbOriginatorTypeFilterNodeConfiguration defaultConfiguration() {
TbOriginatorTypeFilterNodeConfiguration configuration = new TbOriginatorTypeFilterNodeConfiguration();
configuration.setOriginatorTypes(Arrays.asList(
EntityType.DEVICE
));
return configuration;
}
}

6
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

2
tools/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

2
transport/coap/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/http/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

2
transport/mqtt/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>

3
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java

@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import io.netty.channel.ChannelHandlerContext;
@ -240,7 +241,7 @@ public class GatewaySessionCtx {
private String getDeviceType(JsonElement json) throws AdaptorException {
JsonElement type = json.getAsJsonObject().get("type");
return type == null ? DEFAULT_DEVICE_TYPE : type.getAsString();
return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString();
}
private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException {

2
transport/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

22
ui/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard",
"private": true,
"version": "2.0.3",
"version": "2.1.0",
"description": "Thingsboard UI",
"licenses": [
{
@ -15,7 +15,6 @@
},
"dependencies": {
"@flowjs/ng-flow": "^2.7.1",
"ace-builds": "1.3.1",
"angular": "1.5.8",
"angular-animate": "1.5.8",
"angular-aria": "1.5.8",
@ -37,17 +36,17 @@
"angular-socialshare": "^2.3.8",
"angular-storage": "0.0.15",
"angular-touch": "1.5.8",
"angular-translate": "2.13.1",
"angular-translate-handler-log": "2.13.1",
"angular-translate-interpolation-messageformat": "2.13.1",
"angular-translate-loader-static-files": "2.13.1",
"angular-translate-storage-cookie": "2.13.1",
"angular-translate-storage-local": "2.13.1",
"angular-translate": "2.18.1",
"angular-translate-handler-log": "2.18.1",
"angular-translate-interpolation-messageformat": "2.18.1",
"angular-translate-loader-static-files": "2.18.1",
"angular-translate-storage-cookie": "2.18.1",
"angular-translate-storage-local": "2.18.1",
"angular-ui-ace": "^0.2.3",
"angular-ui-router": "^0.3.1",
"angular-websocket": "^2.0.1",
"base64-js": "^1.2.1",
"brace": "^0.8.0",
"brace": "^0.10.0",
"canvas-gauges": "^2.0.9",
"clipboard": "^1.5.15",
"compass-sass-mixins": "^0.12.7",
@ -96,6 +95,7 @@
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.16.0",
"compression-webpack-plugin": "^1.1.11",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^3.0.1",
"cross-env": "^3.2.4",
@ -127,7 +127,9 @@
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.15.1",
"webpack-hot-middleware": "^2.12.2",
"webpack-material-design-icons": "^0.1.0"
"webpack-material-design-icons": "^0.1.0",
"directory-tree": "^2.1.0",
"jsonminify": "^0.4.1"
},
"engine": "node >= 5.9.0",
"nyc": {

2
ui/pom.xml

@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
<version>2.0.3</version>
<version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>

2
ui/src/app/api/alias-controller.js

@ -146,6 +146,7 @@ export default class AliasController {
newDatasource.entityId = resolvedEntity.id;
newDatasource.entityType = resolvedEntity.entityType;
newDatasource.entityName = resolvedEntity.name;
newDatasource.entityDescription = resolvedEntity.entityDescription
newDatasource.name = resolvedEntity.name;
newDatasource.generated = i > 0 ? true : false;
datasources.push(newDatasource);
@ -167,6 +168,7 @@ export default class AliasController {
datasource.entityType = entity.entityType;
datasource.entityName = entity.name;
datasource.name = entity.name;
datasource.entityDescription = entity.entityDescription;
deferred.resolve([datasource]);
} else {
if (aliasInfo.stateEntity) {

2
ui/src/app/api/dashboard.service.js

@ -252,7 +252,7 @@ function DashboardService($rootScope, $http, $q, $location, $filter) {
if (port != 80 && port != 443) {
url += ":" + port;
}
url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
url += "/dashboard/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
return url;
}

2
ui/src/app/api/entity.service.js

@ -329,7 +329,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
function entityToEntityInfo(entity) {
return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id, entityDescription: entity.additionalInfo?entity.additionalInfo.description:"" };
}
function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {

26
ui/src/app/api/rule-chain.service.js

@ -32,6 +32,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
getRuleNodeComponents: getRuleNodeComponents,
getRuleNodeComponentByClazz: getRuleNodeComponentByClazz,
getRuleNodeSupportedLinks: getRuleNodeSupportedLinks,
ruleNodeAllowCustomLinks: ruleNodeAllowCustomLinks,
resolveTargetRuleChains: resolveTargetRuleChains,
testScript: testScript,
getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput
@ -127,21 +128,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
function getRuleNodeSupportedLinks(component) {
var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes;
var customRelations = component.configurationDescriptor.nodeDefinition.customRelations;
var linkLabels = [];
var linkLabels = {};
for (var i=0;i<relationTypes.length;i++) {
linkLabels.push({
name: relationTypes[i], custom: false
});
}
if (customRelations) {
linkLabels.push(
{ name: 'Custom', custom: true }
);
var label = relationTypes[i];
linkLabels[label] = {
name: label,
value: label
};
}
return linkLabels;
}
function ruleNodeAllowCustomLinks(component) {
return component.configurationDescriptor.nodeDefinition.customRelations;
}
function getRuleNodeComponents() {
var deferred = $q.defer();
if (ruleNodeComponents) {
@ -226,7 +227,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
if (res && res.length) {
return res[0];
}
return null;
var unknownComponent = angular.copy(types.unknownNodeComponent);
unknownComponent.clazz = clazz;
unknownComponent.configurationDescriptor.nodeDefinition.details = "Unknown Rule Node class: " + clazz;
return unknownComponent;
}
function resolveTargetRuleChains(ruleChainConnections) {

163
ui/src/app/api/time.service.js

@ -32,84 +32,7 @@ const MAX_LIMIT = 500;
/*@ngInject*/
function TimeService($translate, types) {
var predefIntervals = [
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 1}, 'messageformat'),
value: 1 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 5}, 'messageformat'),
value: 5 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
value: 10 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 15}, 'messageformat'),
value: 15 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
value: 30 * SECOND
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
value: 1 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
value: 2 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
value: 5 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
value: 10 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 15}, 'messageformat'),
value: 15 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
value: 30 * MINUTE
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
value: 1 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
value: 2 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 5}, 'messageformat'),
value: 5 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
value: 10 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 12}, 'messageformat'),
value: 12 * HOUR
},
{
name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
value: 1 * DAY
},
{
name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
value: 7 * DAY
},
{
name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
value: 30 * DAY
}
];
var predefIntervals;
var service = {
minIntervalLimit: minIntervalLimit,
@ -166,6 +89,7 @@ function TimeService($translate, types) {
min = boundMinInterval(min);
max = boundMaxInterval(max);
var intervals = [];
initPredefIntervals();
for (var i in predefIntervals) {
var interval = predefIntervals[i];
if (interval.value >= min && interval.value <= max) {
@ -175,6 +99,89 @@ function TimeService($translate, types) {
return intervals;
}
function initPredefIntervals() {
if (!predefIntervals) {
predefIntervals = [
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 1}, 'messageformat'),
value: 1 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 5}, 'messageformat'),
value: 5 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
value: 10 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 15}, 'messageformat'),
value: 15 * SECOND
},
{
name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
value: 30 * SECOND
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
value: 1 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
value: 2 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
value: 5 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
value: 10 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 15}, 'messageformat'),
value: 15 * MINUTE
},
{
name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
value: 30 * MINUTE
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
value: 1 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
value: 2 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 5}, 'messageformat'),
value: 5 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
value: 10 * HOUR
},
{
name: $translate.instant('timeinterval.hours-interval', {hours: 12}, 'messageformat'),
value: 12 * HOUR
},
{
name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
value: 1 * DAY
},
{
name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
value: 7 * DAY
},
{
name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
value: 30 * DAY
}
];
}
}
function matchesExistingInterval(min, max, intervalMs) {
var intervals = getIntervals(min, max);
for (var i in intervals) {

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

@ -488,7 +488,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
} else {
return true;
}
} else if (to.name === 'home.dashboards.dashboard' && allowedDashboardIds.indexOf(params.dashboardId) > -1) {
} else if ((to.name === 'home.dashboards.dashboard' || to.name === 'dashboard')
&& allowedDashboardIds.indexOf(params.dashboardId) > -1) {
return false;
} else {
return true;
@ -504,10 +505,10 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
var place = 'home.links';
if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
if (userHasDefaultDashboard()) {
place = 'home.dashboards.dashboard';
place = $rootScope.forceFullscreen ? 'dashboard' : 'home.dashboards.dashboard';
params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId};
} else if (isPublic()) {
place = 'home.dashboards.dashboard';
place = 'dashboard';
params = {dashboardId: lastPublicDashboardId};
}
} else if (currentUser.authority === 'SYS_ADMIN') {

76
ui/src/app/app.config.js

@ -15,10 +15,6 @@
*/
import injectTapEventPlugin from 'react-tap-event-plugin';
import UrlHandler from './url.handler';
import addLocaleKorean from './locale/locale.constant-ko';
import addLocaleChinese from './locale/locale.constant-zh';
import addLocaleRussian from './locale/locale.constant-ru';
import addLocaleSpanish from './locale/locale.constant-es';
/* eslint-disable import/no-unresolved, import/default */
@ -38,46 +34,28 @@ export default function AppConfig($provide,
$mdThemingProvider,
$httpProvider,
$translateProvider,
storeProvider,
locales) {
storeProvider) {
injectTapEventPlugin();
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise(UrlHandler);
storeProvider.setCaching(false);
$translateProvider.useSanitizeValueStrategy(null);
$translateProvider.useMissingTranslationHandler('tbMissingTranslationHandler');
$translateProvider.addInterpolation('$translateMessageFormatInterpolation');
$translateProvider.fallbackLanguage('en_US');
addLocaleKorean(locales);
addLocaleChinese(locales);
addLocaleRussian(locales);
addLocaleSpanish(locales);
for (var langKey in locales) {
var translationTable = locales[langKey];
$translateProvider.translations(langKey, translationTable);
}
var lang = $translateProvider.resolveClientLocale();
if (lang) {
lang = lang.toLowerCase();
if (lang.startsWith('ko')) {
$translateProvider.preferredLanguage('ko_KR');
} else if (lang.startsWith('zh')) {
$translateProvider.preferredLanguage('zh_CN');
} else if (lang.startsWith('es')) {
$translateProvider.preferredLanguage('es_ES');
} else if (lang.startsWith('ru')) {
$translateProvider.preferredLanguage('ru_RU');
} else {
$translateProvider.preferredLanguage('en_US');
}
} else {
$translateProvider.preferredLanguage('en_US');
}
$translateProvider.useSanitizeValueStrategy(null)
.useMissingTranslationHandler('tbMissingTranslationHandler')
.addInterpolation('$translateMessageFormatInterpolation')
.useStaticFilesLoader({
files: [
{
prefix: PUBLIC_PATH + 'locale/locale.constant-', //eslint-disable-line
suffix: '.json'
}
]
})
.registerAvailableLanguageKeys(SUPPORTED_LANGS, getLanguageAliases(SUPPORTED_LANGS)) //eslint-disable-line
.fallbackLanguage('en_US') // must be before determinePreferredLanguage
.uniformLanguageTag('java') // must be before determinePreferredLanguage
.determinePreferredLanguage();
$httpProvider.interceptors.push('globalInterceptor');
@ -168,4 +146,24 @@ export default function AppConfig($provide,
//$mdThemingProvider.alwaysWatchTheme(true);
}
function getLanguageAliases(supportedLangs) {
var aliases = {};
supportedLangs.sort().forEach(function(item, index, array) {
if (item.length === 2) {
aliases[item] = item;
aliases[item + '_*'] = item;
} else {
var key = item.slice(0, 2);
if (index === 0 || key !== array[index - 1].slice(0, 2)) {
aliases[key] = item;
aliases[key + '_*'] = item;
} else {
aliases[item] = item;
}
}
});
return aliases;
}
}

4
ui/src/app/app.js

@ -51,7 +51,7 @@ import react from 'ngreact';
import '@flowjs/ng-flow/dist/ng-flow-standalone.min';
import 'ngFlowchart/dist/ngFlowchart';
import thingsboardLocales from './locale/locale.constant';
import thingsboardTranslateHandler from './locale/translate-handler';
import thingsboardLogin from './login';
import thingsboardDialogs from './components/datakey-config-dialog.controller';
import thingsboardMenu from './services/menu.service';
@ -117,7 +117,7 @@ angular.module('thingsboard', [
react.name,
'flow',
'flowchart',
thingsboardLocales,
thingsboardTranslateHandler,
thingsboardLogin,
thingsboardDialogs,
thingsboardMenu,

7
ui/src/app/app.run.js

@ -113,7 +113,10 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
showForbiddenDialog();
} else if (to.redirectTo) {
evt.preventDefault();
$state.go(to.redirectTo, params)
$state.go(to.redirectTo, params);
} else if (to.name === 'home.dashboards.dashboard' && $rootScope.forceFullscreen) {
evt.preventDefault();
$state.go('dashboard', params);
}
}
} else {
@ -138,7 +141,7 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
$rootScope.pageTitle = 'ThingsBoard';
$rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
if (userService.isPublic() && to.name === 'home.dashboards.dashboard') {
if (userService.isPublic() && to.name === 'dashboard') {
$location.search('publicId', userService.getPublicId());
userService.updateLastPublicDashboardId(params.dashboardId);
}

106
ui/src/app/common/types.constant.js

@ -195,6 +195,21 @@ export default angular.module('thingsboard.types', [])
},
"ATTRIBUTES_READ": {
name: "audit-log.type-attributes-read"
},
"RELATION_ADD_OR_UPDATE": {
name: "audit-log.type-relation-add-or-update"
},
"RELATION_DELETED": {
name: "audit-log.type-relation-delete"
},
"RELATIONS_DELETED": {
name: "audit-log.type-relations-delete"
},
"ALARM_ACK": {
name: "audit-log.type-alarm-ack"
},
"ALARM_CLEAR": {
name: "audit-log.type-alarm-clear"
}
},
auditLogActionStatus: {
@ -366,6 +381,12 @@ export default angular.module('thingsboard.types', [])
list: 'entity.list-of-rulechains',
nameStartsWith: 'entity.rulechain-name-starts-with'
},
"RULE_NODE": {
type: 'entity.type-rulenode',
typePlural: 'entity.type-rulenodes',
list: 'entity.list-of-rulenodes',
nameStartsWith: 'entity.rulenode-name-starts-with'
},
"CURRENT_CUSTOMER": {
type: 'entity.type-current-customer',
list: 'entity.type-current-customer'
@ -510,6 +531,22 @@ export default angular.module('thingsboard.types', [])
}
}
},
unknownNodeComponent: {
type: 'UNKNOWN',
name: 'unknown',
clazz: 'tb.internal.Unknown',
configurationDescriptor: {
nodeDefinition: {
description: "",
details: "",
inEnabled: true,
outEnabled: true,
relationTypes: [],
customRelations: false,
defaultConfiguration: {}
}
}
},
inputNodeComponent: {
type: 'INPUT',
name: 'Input',
@ -565,6 +602,75 @@ export default angular.module('thingsboard.types', [])
nodeClass: "tb-input-type",
icon: "input",
special: true
},
UNKNOWN: {
value: "UNKNOWN",
name: "rulenode.type-unknown",
details: "rulenode.type-unknown-details",
nodeClass: "tb-unknown-type",
icon: "help_outline"
}
},
messageType: {
'POST_ATTRIBUTES_REQUEST': {
name: 'Post attributes',
value: 'POST_ATTRIBUTES_REQUEST'
},
'POST_TELEMETRY_REQUEST': {
name: 'Post telemetry',
value: 'POST_TELEMETRY_REQUEST'
},
'TO_SERVER_RPC_REQUEST': {
name: 'RPC Request from Device',
value: 'TO_SERVER_RPC_REQUEST'
},
'RPC_CALL_FROM_SERVER_TO_DEVICE': {
name: 'RPC Request to Device',
value: 'RPC_CALL_FROM_SERVER_TO_DEVICE'
},
'ACTIVITY_EVENT': {
name: 'Activity Event',
value: 'ACTIVITY_EVENT'
},
'INACTIVITY_EVENT': {
name: 'Inactivity Event',
value: 'INACTIVITY_EVENT'
},
'CONNECT_EVENT': {
name: 'Connect Event',
value: 'CONNECT_EVENT'
},
'DISCONNECT_EVENT': {
name: 'Disconnect Event',
value: 'DISCONNECT_EVENT'
},
'ENTITY_CREATED': {
name: 'Entity Created',
value: 'ENTITY_CREATED'
},
'ENTITY_UPDATED': {
name: 'Entity Updated',
value: 'ENTITY_UPDATED'
},
'ENTITY_DELETED': {
name: 'Entity Deleted',
value: 'ENTITY_DELETED'
},
'ENTITY_ASSIGNED': {
name: 'Entity Assigned',
value: 'ENTITY_ASSIGNED'
},
'ENTITY_UNASSIGNED': {
name: 'Entity Unassigned',
value: 'ENTITY_UNASSIGNED'
},
'ATTRIBUTES_UPDATED': {
name: 'Attributes Updated',
value: 'ATTRIBUTES_UPDATED'
},
'ATTRIBUTES_DELETED': {
name: 'Attributes Deleted',
value: 'ATTRIBUTES_DELETED'
}
},
valueType: {

8
ui/src/app/components/dashboard.scss

@ -40,12 +40,12 @@ div.tb-widget {
position: absolute;
top: 8px;
right: 8px;
z-index: 1;
margin: 0px;
z-index: 19;
margin: 0;
.md-button.md-icon-button {
margin: 0px !important;
padding: 0px !important;
margin: 0 !important;
padding: 0 !important;
line-height: 20px;
width: 32px;
height: 32px;

6
ui/src/app/components/datasource-entity.tpl.html

@ -60,7 +60,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
<div layout="row" flex>
<div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
@ -112,7 +112,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
<div layout="row" flex>
<div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
@ -164,7 +164,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
<div layout="row" flex>
<div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>

4
ui/src/app/components/datasource-func.tpl.html

@ -61,7 +61,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
<div layout="row" flex>
<div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
@ -112,7 +112,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
<div layout="row" flex>
<div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>

10
ui/src/app/components/details-sidenav.scss

@ -59,14 +59,4 @@ md-sidenav.tb-sidenav-details {
background-color: $primary-hue-3;
}
}
md-tab-content.md-active > div {
height: 100%;
& > *:first-child {
height: 100%;
}
md-content {
height: 100%;
}
}
}

4
ui/src/app/components/json-content.directive.js

@ -18,8 +18,8 @@ import './json-content.scss';
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/mode/text';
import 'ace-builds/src-min-noconflict/snippets/json';
import 'ace-builds/src-min-noconflict/snippets/text';
import 'brace/snippets/json';
import 'brace/snippets/text';
import fixAceEditor from './ace-editor-fix';

2
ui/src/app/components/json-object-edit.directive.js

@ -17,7 +17,7 @@ import './json-object-edit.scss';
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'ace-builds/src-min-noconflict/snippets/json';
import 'brace/snippets/json';
import fixAceEditor from './ace-editor-fix';

9
ui/src/app/components/widget/action/manage-widget-actions.directive.js

@ -111,8 +111,15 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
}
});
function enterFilterMode () {
function enterFilterMode (event) {
let $button = angular.element(event.currentTarget);
let $toolbarsContainer = $button.closest('.toolbarsContainer');
vm.query.search = '';
$timeout(()=>{
$toolbarsContainer.find('.searchInput').focus();
})
}
function exitFilterMode () {

6
ui/src/app/components/widget/action/manage-widget-actions.tpl.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1" layout="column">
<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1 toolbarsContainer" layout="column">
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null">
<div class="md-toolbar-tools">
<span translate>widget-config.actions</span>
@ -26,7 +26,7 @@
{{ 'widget-config.add-action' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
<md-button class="md-icon-button" ng-click="vm.enterFilterMode($event)">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
@ -44,7 +44,7 @@
</md-button>
<md-input-container flex>
<label>&nbsp;</label>
<input ng-model="vm.query.search" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | translate }}"/>
<input ng-model="vm.query.search" class="searchInput" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | 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>

6
ui/src/app/components/widget/widget-config.tpl.html

@ -187,17 +187,17 @@
</div>
<div layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<div layout="row" layout-padding>
<md-checkbox flex aria-label="{{ 'widget-config.display-title' | translate }}"
<md-checkbox aria-label="{{ 'widget-config.display-title' | translate }}"
ng-model="showTitle">{{ 'widget-config.display-title' | translate }}
</md-checkbox>
</div>
<div layout="row" layout-padding>
<md-checkbox flex aria-label="{{ 'widget-config.drop-shadow' | translate }}"
<md-checkbox aria-label="{{ 'widget-config.drop-shadow' | translate }}"
ng-model="dropShadow">{{ 'widget-config.drop-shadow' | translate }}
</md-checkbox>
</div>
<div layout="row" layout-padding>
<md-checkbox flex aria-label="{{ 'widget-config.enable-fullscreen' | translate }}"
<md-checkbox aria-label="{{ 'widget-config.enable-fullscreen' | translate }}"
ng-model="enableFullscreen">{{ 'widget-config.enable-fullscreen' | translate }}
</md-checkbox>
</div>

6
ui/src/app/components/widget/widget.controller.js

@ -479,7 +479,11 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
dashboardId: targetDashboardId,
state: utils.objToBase64([ stateObject ])
}
$state.go('home.dashboards.dashboard', stateParams);
if ($state.current.name === 'dashboard') {
$state.go('dashboard', stateParams);
} else {
$state.go('home.dashboards.dashboard', stateParams);
}
break;
case types.widgetActionTypes.custom.value:
var customFunction = descriptor.customFunction;

11
ui/src/app/dashboard/dashboard.controller.js

@ -196,6 +196,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.displayDashboardTimewindow = displayDashboardTimewindow;
vm.displayDashboardsSelect = displayDashboardsSelect;
vm.displayEntitiesSelect = displayEntitiesSelect;
vm.hideFullscreenButton = hideFullscreenButton;
vm.widgetsBundle;
@ -258,7 +259,11 @@ export default function DashboardController(types, utils, dashboardUtils, widget
dashboardId: vm.currentDashboardId
});
} else {
$state.go('home.dashboards.dashboard', {dashboardId: vm.currentDashboardId});
if ($state.current.name === 'dashboard') {
$state.go('dashboard', {dashboardId: vm.currentDashboardId});
} else {
$state.go('home.dashboards.dashboard', {dashboardId: vm.currentDashboardId});
}
}
}
});
@ -805,6 +810,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget
}
}
function hideFullscreenButton() {
return vm.widgetEditMode || vm.iframeMode || $rootScope.forceFullscreen || $state.current.name === 'dashboard';
}
function onRevertWidgetEdit(widgetForm) {
if (widgetForm.$dirty) {
widgetForm.$setPristine();

18
ui/src/app/dashboard/dashboard.routes.js

@ -86,6 +86,24 @@ export default function DashboardRoutes($stateProvider) {
label: '{"icon": "dashboard", "label": "{{ vm.dashboard.title }}", "translate": "false"}'
}
})
.state('dashboard', {
url: '/dashboard/:dashboardId?state',
reloadOnSearch: false,
module: 'private',
auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
views: {
"@": {
templateUrl: dashboardTemplate,
controller: 'DashboardController',
controllerAs: 'vm'
}
},
data: {
widgetEditMode: false,
searchEnabled: false,
pageTitle: 'dashboard.dashboard'
}
})
.state('home.customers.dashboards.dashboard', {
url: '/:dashboardId?state',
reloadOnSearch: false,

2
ui/src/app/dashboard/dashboard.tpl.html

@ -16,7 +16,7 @@
-->
<md-content style="padding-top: 150px;" flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-button-id="dashboard-expand-button"
hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom" ng-if="vm.dashboard">
hide-expand-button="vm.hideFullscreenButton()" expand-tooltip-direction="bottom" ng-if="vm.dashboard">
<section class="tb-dashboard-toolbar" ng-show="vm.showDashboardToolbar()"
ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
<tb-dashboard-toolbar ng-show="!vm.widgetEditMode" force-fullscreen="forceFullscreen"

4
ui/src/app/dashboard/states/select-target-state.tpl.html

@ -41,8 +41,8 @@
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
<md-button ng-disabled="$root.loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary">
{{ 'action.save' | translate }}
<md-button ng-disabled="$root.loading || !theForm.$valid" type="submit" class="md-raised md-primary">
{{ 'action.select' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>

7
ui/src/app/device/device-card.tpl.html

@ -16,7 +16,8 @@
-->
<div flex layout="column" style="margin-top: -10px;">
<div style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
<div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
<div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
<div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
<div style="padding-top: 5px;" class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
</div>

4
ui/src/app/entity/alias/entity-aliases.tpl.html

@ -43,7 +43,7 @@
<fieldset ng-disabled="$root.loading">
<div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">
<span flex="5">{{$index + 1}}.</span>
<di class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
<div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
<md-input-container flex="20" style="min-width: 150px;" md-no-float class="md-block">
<input required name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">
<div ng-messages="aliasForm.alias.$error">
@ -81,7 +81,7 @@
close
</md-icon>
</md-button>
</di>
</div>
</div>
</fieldset>
</div>

11
ui/src/app/entity/attribute/attribute-table.directive.js

@ -30,7 +30,7 @@ import AliasController from '../../api/alias-controller';
/*@ngInject*/
export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
$mdUtil, $document, $translate, $filter, utils, types, dashboardUtils,
$mdUtil, $document, $translate, $filter, $timeout, utils, types, dashboardUtils,
entityService, attributeService, widgetService) {
var linker = function (scope, element, attrs) {
@ -110,8 +110,15 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope);
}
scope.enterFilterMode = function() {
scope.enterFilterMode = function(event) {
let $button = angular.element(event.currentTarget);
let $toolbarsContainer = $button.closest('.toolbarsContainer');
scope.query.search = '';
$timeout(()=>{
$toolbarsContainer.find('.searchInput').focus();
})
}
scope.exitFilterMode = function() {

6
ui/src/app/entity/attribute/attribute-table.tpl.html

@ -26,7 +26,7 @@
</md-select>
</md-input-container>
</section>
<div class="md-whiteframe-z1" ng-class="{flex: mode==='widget'}">
<div class="md-whiteframe-z1 toolbarsContainer" ng-class="{flex: mode==='widget'}">
<md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default'
&& !selectedAttributes.length
&& query.search === null">
@ -39,7 +39,7 @@
{{ 'action.add' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" ng-click="enterFilterMode()">
<md-button class="md-icon-button" ng-click="enterFilterMode($event)">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
@ -65,7 +65,7 @@
</md-button>
<md-input-container flex>
<label>&nbsp;</label>
<input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
<input ng-model="query.search" class="searchInput" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitFilterMode()">
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>

21
ui/src/app/entity/entity-select.directive.js

@ -22,14 +22,28 @@ import entitySelectTemplate from './entity-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function EntitySelect($compile, $templateCache) {
export default function EntitySelect($compile, $templateCache, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
scope.model = {};
var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
var entityTypeKeys = Object.keys(entityTypes);
if (entityTypeKeys.length === 1) {
scope.displayEntityTypeSelect = false;
scope.defaultEntityType = entityTypes[entityTypeKeys[0]];
} else {
scope.displayEntityTypeSelect = true;
}
scope.model = {
entityType: scope.defaultEntityType
};
scope.updateView = function () {
if (!scope.disabled) {
@ -54,7 +68,7 @@ export default function EntitySelect($compile, $templateCache) {
scope.model.entityType = value.entityType;
scope.model.entityId = value.id;
} else {
scope.model.entityType = null;
scope.model.entityType = scope.defaultEntityType;
scope.model.entityId = null;
}
initWatchers();
@ -106,6 +120,7 @@ export default function EntitySelect($compile, $templateCache) {
theForm: '=?',
tbRequired: '=?',
disabled:'=ngDisabled',
allowedEntityTypes: "=?",
useAliasEntityTypes: "=?"
}
};

2
ui/src/app/entity/entity-select.tpl.html

@ -17,10 +17,12 @@
-->
<div layout='row' class="tb-entity-select">
<tb-entity-type-select style="min-width: 100px;"
ng-if="displayEntityTypeSelect"
the-form="theForm"
ng-disabled="disabled"
tb-required="tbRequired"
use-alias-entity-types="useAliasEntityTypes"
allowed-entity-types="allowedEntityTypes"
ng-model="model.entityType">
</tb-entity-type-select>
<tb-entity-autocomplete flex ng-if="model.entityType"

57
ui/src/app/entity/entity-type-list.directive.js

@ -35,7 +35,30 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
: $translate.instant('entity.any-entity');
scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type');
var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
var entityTypes;
if (scope.ignoreAuthorityFilter && scope.allowedEntityTypes
&& scope.allowedEntityTypes.length) {
entityTypes = {};
scope.allowedEntityTypes.forEach((entityTypeValue) => {
var entityType = entityTypeFromValue(entityTypeValue);
if (entityType) {
entityTypes[entityType] = entityTypeValue;
}
});
} else {
entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
}
function entityTypeFromValue(entityTypeValue) {
for (var entityType in types.entityType) {
if (types.entityType[entityType] === entityTypeValue) {
return entityType;
}
}
return null;
}
scope.entityTypesList = [];
for (var type in entityTypes) {
var entityTypeInfo = {};
@ -62,28 +85,43 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
}
ngModelCtrl.$render = function () {
scope.entityTypeList = [];
if (scope.entityTypeListWatch) {
scope.entityTypeListWatch();
scope.entityTypeListWatch = null;
}
var entityTypeList = [];
var value = ngModelCtrl.$viewValue;
if (value && value.length) {
value.forEach(function(type) {
var entityTypeInfo = {};
entityTypeInfo.value = type;
entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
scope.entityTypeList.push(entityTypeInfo);
entityTypeList.push(entityTypeInfo);
});
}
scope.entityTypeList = entityTypeList;
scope.entityTypeListWatch = scope.$watch('entityTypeList', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
updateEntityTypeList();
}
}, true);
}
scope.$watch('entityTypeList', function () {
var values = [];
function updateEntityTypeList() {
var values = ngModelCtrl.$viewValue;
if (!values) {
values = [];
ngModelCtrl.$setViewValue(values);
} else {
values.length = 0;
}
if (scope.entityTypeList && scope.entityTypeList.length) {
scope.entityTypeList.forEach(function(entityType) {
scope.entityTypeList.forEach(function (entityType) {
values.push(entityType.value);
});
}
ngModelCtrl.$setViewValue(values);
scope.updateValidity();
}, true);
}
$compile(element.contents())(scope);
@ -103,7 +141,8 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
scope: {
disabled:'=ngDisabled',
tbRequired: '=?',
allowedEntityTypes: '=?'
allowedEntityTypes: '=?',
ignoreAuthorityFilter: '=?'
}
};

15
ui/src/app/entity/relation/relation-filters.directive.js

@ -44,6 +44,10 @@ export default function RelationFilters($compile, $templateCache) {
scope.removeFilter = removeFilter;
ngModelCtrl.$render = function () {
if (scope.relationFiltersWatch) {
scope.relationFiltersWatch();
scope.relationFiltersWatch = null;
}
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
scope.relationFilters.length = 0;
@ -51,7 +55,7 @@ export default function RelationFilters($compile, $templateCache) {
scope.relationFilters.push(filter);
});
}
scope.$watch('relationFilters', function (newVal, prevVal) {
scope.relationFiltersWatch = scope.$watch('relationFilters', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
updateValue();
}
@ -74,11 +78,16 @@ export default function RelationFilters($compile, $templateCache) {
}
function updateValue() {
var value = [];
var value = ngModelCtrl.$viewValue;
if (!value) {
value = [];
ngModelCtrl.$setViewValue(value);
} else {
value.length = 0;
}
scope.relationFilters.forEach(function (filter) {
value.push(filter);
});
ngModelCtrl.$setViewValue(value);
}
$compile(element.contents())(scope);
}

11
ui/src/app/entity/relation/relation-table.directive.js

@ -41,7 +41,7 @@ export default function RelationTable() {
}
/*@ngInject*/
function RelationTableController($scope, $q, $mdDialog, $document, $translate, $filter, utils, types, entityRelationService) {
function RelationTableController($scope, $q, $mdDialog, $document, $translate, $filter, $timeout, utils, types, entityRelationService) {
let vm = this;
@ -90,8 +90,15 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
}
});
function enterFilterMode () {
function enterFilterMode (event) {
let $button = angular.element(event.currentTarget);
let $toolbarsContainer = $button.closest('.toolbarsContainer');
vm.query.search = '';
$timeout(()=>{
$toolbarsContainer.find('.searchInput').focus();
})
}
function exitFilterMode () {

6
ui/src/app/entity/relation/relation-table.tpl.html

@ -26,7 +26,7 @@
</md-select>
</md-input-container>
</section>
<div layout="column" class="md-whiteframe-z1">
<div layout="column" class="md-whiteframe-z1 toolbarsContainer">
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
&& vm.query.search === null">
<div class="md-toolbar-tools">
@ -39,7 +39,7 @@
{{ 'action.add' | translate }}
</md-tooltip>
</md-button>
<md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
<md-button class="md-icon-button" ng-click="vm.enterFilterMode($event)">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
@ -64,7 +64,7 @@
</md-button>
<md-input-container flex>
<label>&nbsp;</label>
<input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
<input ng-model="vm.query.search" class="searchInput" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save