Browse Source

Merge remote-tracking branch 'upstream/master'

# Conflicts:
#	dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java
pull/1441/head
vparomskiy 8 years ago
parent
commit
c646096d40
  1. 8
      application/pom.xml
  2. 7
      application/src/main/data/upgrade/2.1.1/schema_update.cql
  3. 4
      application/src/main/data/upgrade/2.1.1/schema_update.sql
  4. 59
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  5. 1
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java
  6. 13
      application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java
  7. 3
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  8. 13
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  9. 10
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  10. 9
      application/src/main/java/org/thingsboard/server/service/encoding/ProtoWithFSTService.java
  11. 12
      application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java
  12. 2
      application/src/main/scripts/install/install_dev_db.sh
  13. 12
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java
  14. 1
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java
  15. 3
      common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java
  16. 2
      common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java
  17. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  18. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  19. 26
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  20. 12
      dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java
  21. 35
      dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java
  22. 34
      dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java
  23. 5
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  24. 6
      dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
  25. 24
      dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java
  26. 31
      dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java
  27. 4
      dao/src/main/resources/sql/schema-entities.sql
  28. 4
      dao/src/main/resources/sql/schema-ts.sql
  29. 4
      dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java
  30. 83
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
  31. 104
      dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
  32. 42
      docker/README.md
  33. 9
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java
  34. 66
      msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java
  35. 3
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java
  36. 2
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java
  37. 18
      pom.xml
  38. 51
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java
  39. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbClearAlarmNode.java
  40. 62
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
  41. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
  42. 45
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java
  43. 57
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java
  44. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java
  45. 43
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java
  46. 6
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java
  47. 111
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
  48. 9
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
  49. 8
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  50. 1
      tools/pom.xml
  51. 53
      tools/src/main/java/org/thingsboard/client/tools/RestClient.java
  52. 6
      ui/src/app/api/subscription.js
  53. 1
      ui/src/app/entity/entity-subtype-autocomplete.directive.js
  54. 2
      ui/src/app/entity/entity-subtype-autocomplete.tpl.html
  55. 45
      ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js
  56. 104
      ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html
  57. 6
      ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html
  58. 1581
      ui/src/app/locale/locale.constant-de_DE.json
  59. 9
      ui/src/app/locale/locale.constant-en_US.json
  60. 667
      ui/src/app/locale/locale.constant-es_ES.json
  61. 3
      ui/src/app/locale/locale.constant-fr_FR.json
  62. 21
      ui/src/app/locale/locale.constant-it_IT.json
  63. 21
      ui/src/app/locale/locale.constant-ja_JA.json
  64. 8
      ui/src/app/locale/locale.constant-ko_KR.json
  65. 1533
      ui/src/app/locale/locale.constant-ru_RU.json
  66. 3
      ui/src/app/locale/locale.constant-tr_TR.json
  67. 7
      ui/src/app/locale/locale.constant-zh_CN.json
  68. 42
      ui/src/app/widget/lib/google-map.js
  69. 15
      ui/src/app/widget/lib/image-map.js
  70. 2241
      ui/src/app/widget/lib/map-widget2.js
  71. 494
      ui/src/app/widget/lib/openstreet-map.js
  72. 786
      ui/src/app/widget/lib/tencent-map.js

8
application/pom.xml

@ -41,6 +41,10 @@
</properties>
<dependencies>
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
@ -264,6 +268,10 @@
<groupId>org.javadelight</groupId>
<artifactId>delight-nashorn-sandbox</artifactId>
</dependency>
<dependency>
<groupId>io.springfox.ui</groupId>
<artifactId>springfox-swagger-ui-rfc6570</artifactId>
</dependency>
</dependencies>
<build>

7
application/src/main/data/upgrade/2.1.1/schema_update.cql

@ -14,13 +14,6 @@
-- limitations under the License.
--
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_name;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_search_text;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_customer;
DROP MATERIALIZED VIEW IF EXISTS thingsboard.entity_view_by_tenant_and_entity_id;
DROP TABLE IF EXISTS thingsboard.entity_views;
CREATE TABLE IF NOT EXISTS thingsboard.entity_views (
id timeuuid,
entity_id timeuuid,

4
application/src/main/data/upgrade/2.1.1/schema_update.sql

@ -14,10 +14,8 @@
-- limitations under the License.
--
DROP TABLE IF EXISTS entity_views;
CREATE TABLE IF NOT EXISTS entity_views (
id varchar(31) NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
id varchar(31) NOT NULL CONSTRAINT entity_views_pkey PRIMARY KEY,
entity_id varchar(31),
entity_type varchar(255),
tenant_id varchar(31),

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

@ -16,7 +16,6 @@
package org.thingsboard.server.actors.device;
import akka.actor.ActorContext;
import akka.event.LoggingAdapter;
import com.datastax.driver.core.utils.UUIDs;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
@ -26,12 +25,12 @@ import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.msg.DeviceNameOrTypeUpdateMsg;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.AbstractContextAwareMsgProcessor;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -42,7 +41,6 @@ import org.thingsboard.server.common.data.rpc.ToDeviceRpcRequestBody;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgDataType;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import org.thingsboard.server.common.msg.timeout.DeviceActorClientSideRpcTimeoutMsg;
@ -81,12 +79,14 @@ import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
/**
* @author Andrew Shvayka
*/
@ -263,10 +263,8 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
}
private void handleGetAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, GetAttributeRequestMsg request) {
ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.CLIENT_SCOPE, toOptionalSet(request.getClientAttributeNamesList()));
ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture = getAttributeKvEntries(deviceId, DataConstants.SHARED_SCOPE, toOptionalSet(request.getSharedAttributeNamesList()));
int requestId = request.getRequestId();
Futures.addCallback(Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture)), new FutureCallback<List<List<AttributeKvEntry>>>() {
Futures.addCallback(getAttributesKvEntries(request), new FutureCallback<List<List<AttributeKvEntry>>>() {
@Override
public void onSuccess(@Nullable List<List<AttributeKvEntry>> result) {
GetAttributeResponseMsg responseMsg = GetAttributeResponseMsg.newBuilder()
@ -287,16 +285,35 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
});
}
private ListenableFuture<List<AttributeKvEntry>> getAttributeKvEntries(DeviceId deviceId, String scope, Optional<Set<String>> names) {
if (names.isPresent()) {
if (!names.get().isEmpty()) {
return systemContext.getAttributesService().find(tenantId, deviceId, scope, names.get());
} else {
return systemContext.getAttributesService().findAll(tenantId, deviceId, scope);
}
private ListenableFuture<List<List<AttributeKvEntry>>> getAttributesKvEntries(GetAttributeRequestMsg request) {
ListenableFuture<List<AttributeKvEntry>> clientAttributesFuture;
ListenableFuture<List<AttributeKvEntry>> sharedAttributesFuture;
if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = findAllAttributesByScope(CLIENT_SCOPE);
sharedAttributesFuture = findAllAttributesByScope(SHARED_SCOPE);
} else if (!CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
} else if (CollectionUtils.isEmpty(request.getClientAttributeNamesList()) && !CollectionUtils.isEmpty(request.getSharedAttributeNamesList())) {
clientAttributesFuture = Futures.immediateFuture(Collections.emptyList());
sharedAttributesFuture = findAttributesByScope(toSet(request.getSharedAttributeNamesList()), SHARED_SCOPE);
} else {
return Futures.immediateFuture(Collections.emptyList());
sharedAttributesFuture = Futures.immediateFuture(Collections.emptyList());
clientAttributesFuture = findAttributesByScope(toSet(request.getClientAttributeNamesList()), CLIENT_SCOPE);
}
return Futures.allAsList(Arrays.asList(clientAttributesFuture, sharedAttributesFuture));
}
private ListenableFuture<List<AttributeKvEntry>> findAllAttributesByScope(String scope) {
return systemContext.getAttributesService().findAll(tenantId, deviceId, scope);
}
private ListenableFuture<List<AttributeKvEntry>> findAttributesByScope(Set<String> attributesSet, String scope) {
return systemContext.getAttributesService().find(tenantId, deviceId, scope, attributesSet);
}
private Set<String> toSet(List<String> strings) {
return new HashSet<>(strings);
}
private void handlePostAttributesRequest(ActorContext context, SessionInfoProto sessionInfo, PostAttributeMsg postAttributes) {
@ -368,7 +385,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
AttributeUpdateNotificationMsg.Builder notification = AttributeUpdateNotificationMsg.newBuilder();
if (msg.isDeleted()) {
List<String> sharedKeys = msg.getDeletedKeys().stream()
.filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
.filter(key -> SHARED_SCOPE.equals(key.getScope()))
.map(AttributeKey::getAttributeKey)
.collect(Collectors.toList());
if (!sharedKeys.isEmpty()) {
@ -376,7 +393,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
hasNotificationData = true;
}
} else {
if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
if (SHARED_SCOPE.equals(msg.getScope())) {
List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
if (attributes.size() > 0) {
List<TsKvProto> sharedUpdated = msg.getValues().stream().map(this::toTsKvProto)
@ -545,14 +562,6 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor {
return json;
}
private Optional<Set<String>> toOptionalSet(List<String> strings) {
if (strings == null || strings.isEmpty()) {
return Optional.empty();
} else {
return Optional.of(new HashSet<>(strings));
}
}
private void sendToTransport(GetAttributeResponseMsg responseMsg, SessionInfoProto sessionInfo) {
DeviceActorToTransportMsg msg = DeviceActorToTransportMsg.newBuilder()
.setSessionIdMSB(sessionInfo.getSessionIdMSB())

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

@ -17,7 +17,6 @@ package org.thingsboard.server.actors.ruleChain;
import akka.actor.ActorContext;
import akka.actor.ActorRef;
import akka.event.LoggingAdapter;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;

13
application/src/main/java/org/thingsboard/server/actors/service/ComponentActor.java

@ -16,9 +16,6 @@
package org.thingsboard.server.actors.service;
import akka.actor.ActorRef;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.shared.ComponentMsgProcessor;
import org.thingsboard.server.actors.stats.StatsPersistMsg;
@ -60,7 +57,8 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
scheduleStatsPersistTick();
}
} catch (Exception e) {
log.warn("[{}][{}] Failed to start {} processor: {}", tenantId, id, id.getEntityType(), e);
log.warn("[{}][{}] Failed to start {} processor.", tenantId, id, id.getEntityType());
log.warn("Error:", e);
logAndPersist("OnStart", e, true);
logLifecycleEvent(ComponentLifecycleEvent.STARTED, e);
}
@ -149,10 +147,13 @@ public abstract class ComponentActor<T extends EntityId, P extends ComponentMsgP
private void logAndPersist(String method, Exception e, boolean critical) {
errorsOccurred++;
String componentName = processor != null ? processor.getComponentName() : "Unknown";
if (critical) {
log.warn("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e);
log.warn("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, componentName, method);
log.warn("Critical Error: ", e);
} else {
log.debug("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, processor.getComponentName(), method, e);
log.debug("[{}][{}][{}] Failed to process {} msg: {}", id, tenantId, componentName, method);
log.debug("Debug Error: ", e);
}
long ts = System.currentTimeMillis();
if (ts - lastPersistedErrorTs > getErrorPersistFrequency()) {

3
application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java

@ -64,7 +64,8 @@ public class SwaggerConfiguration {
.paths(apiPaths())
.build()
.securitySchemes(newArrayList(jwtTokenKey()))
.securityContexts(newArrayList(securityContext()));
.securityContexts(newArrayList(securityContext()))
.enableUrlTemplating(true);
}
private ApiKey jwtTokenKey() {

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

@ -92,6 +92,19 @@ public class AlarmController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}", method = RequestMethod.DELETE)
@ResponseBody
public Boolean deleteAlarm(@PathVariable(ALARM_ID) String strAlarmId) throws ThingsboardException {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
return alarmService.deleteAlarm(getTenantId(), alarmId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/alarm/{alarmId}/ack", method = RequestMethod.POST)
@ResponseStatus(value = HttpStatus.OK)

10
application/src/main/java/org/thingsboard/server/controller/TelemetryController.java

@ -279,8 +279,14 @@ public class TelemetryController extends BaseController {
deleteFromTs = 0L;
deleteToTs = System.currentTimeMillis();
} else {
deleteFromTs = startTs;
deleteToTs = endTs;
if (startTs == null || endTs == null) {
deleteToTs = endTs;
return getImmediateDeferredResult("When deleteAllDataForKeys is false, start and end timestamp values shouldn't be empty", HttpStatus.BAD_REQUEST);
}
else{
deleteFromTs = startTs;
deleteToTs = endTs;
}
}
return accessValidator.validateEntityAndCallback(user, entityIdStr, (result, tenantId, entityId) -> {

9
application/src/main/java/org/thingsboard/server/service/encoding/ProtoWithJavaSerializationDecodingEncodingService.java → application/src/main/java/org/thingsboard/server/service/encoding/ProtoWithFSTService.java

@ -17,8 +17,8 @@ package org.thingsboard.server.service.encoding;
import com.google.protobuf.ByteString;
import lombok.extern.slf4j.Slf4j;
import org.nustaq.serialization.FSTConfiguration;
import org.springframework.stereotype.Service;
import org.springframework.util.SerializationUtils;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
@ -30,13 +30,14 @@ import static org.thingsboard.server.gen.cluster.ClusterAPIProtos.MessageType.CL
@Slf4j
@Service
public class ProtoWithJavaSerializationDecodingEncodingService implements DataDecodingEncodingService {
public class ProtoWithFSTService implements DataDecodingEncodingService {
private final FSTConfiguration config = FSTConfiguration.createDefaultConfiguration();
@Override
public Optional<TbActorMsg> decode(byte[] byteArray) {
try {
TbActorMsg msg = (TbActorMsg) SerializationUtils.deserialize(byteArray);
TbActorMsg msg = (TbActorMsg) config.asObject(byteArray);
return Optional.of(msg);
} catch (IllegalArgumentException e) {
@ -47,7 +48,7 @@ public class ProtoWithJavaSerializationDecodingEncodingService implements DataDe
@Override
public byte[] encode(TbActorMsg msq) {
return SerializationUtils.serialize(msq);
return config.asByteArray(msq);
}
@Override

12
application/src/main/java/org/thingsboard/server/service/transport/LocalTransportApiService.java

@ -24,6 +24,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@ -100,7 +101,7 @@ public class LocalTransportApiService implements TransportApiService {
//TODO: Make async and enable caching
DeviceCredentials credentials = deviceCredentialsService.findDeviceCredentialsByCredentialsId(credentialsId);
if (credentials != null && credentials.getCredentialsType() == credentialsType) {
return getDeviceInfo(credentials.getDeviceId());
return getDeviceInfo(credentials.getDeviceId(), credentials);
} else {
return getEmptyTransportApiResponseFuture();
}
@ -135,15 +136,20 @@ public class LocalTransportApiService implements TransportApiService {
}
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId) {
private ListenableFuture<TransportApiResponseMsg> getDeviceInfo(DeviceId deviceId, DeviceCredentials credentials) {
return Futures.transform(deviceService.findDeviceByIdAsync(TenantId.SYS_TENANT_ID, deviceId), device -> {
if (device == null) {
log.trace("[{}] Failed to lookup device by id", deviceId);
return getEmptyTransportApiResponse();
}
try {
ValidateDeviceCredentialsResponseMsg.Builder builder = ValidateDeviceCredentialsResponseMsg.newBuilder();
builder.setDeviceInfo(getDeviceInfoProto(device));
if(!StringUtils.isEmpty(credentials.getCredentialsValue())){
builder.setCredentialsBody(credentials.getCredentialsValue());
}
return TransportApiResponseMsg.newBuilder()
.setValidateTokenResponseMsg(ValidateDeviceCredentialsResponseMsg.newBuilder().setDeviceInfo(getDeviceInfoProto(device)).build()).build();
.setValidateTokenResponseMsg(builder.build()).build();
} catch (JsonProcessingException e) {
log.warn("[{}] Failed to lookup device by id", deviceId, e);
return getEmptyTransportApiResponse();

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

@ -28,7 +28,7 @@ export LOADER_PATH=${BASE}/conf,${BASE}/extensions
export SQL_DATA_FOLDER=${SQL_DATA_FOLDER:-/tmp}
run_user=thingsboard
run_user="$USER"
sudo -u "$run_user" -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dinstall.data_dir=${installDir} \

12
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttSslHandlerProvider.java

@ -54,18 +54,18 @@ import java.util.concurrent.TimeUnit;
@Slf4j
@Component("MqttSslHandlerProvider")
@ConditionalOnExpression("'${transport.type:null}'=='null' || ('${transport.type}'=='local' && '${transport.http.enabled}'=='true')")
@ConditionalOnProperty(prefix = "mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
@ConditionalOnProperty(prefix = "transport.mqtt.ssl", value = "enabled", havingValue = "true", matchIfMissing = false)
public class MqttSslHandlerProvider {
@Value("${mqtt.ssl.protocol}")
@Value("${transport.mqtt.ssl.protocol}")
private String sslProtocol;
@Value("${mqtt.ssl.key_store}")
@Value("${transport.mqtt.ssl.key_store}")
private String keyStoreFile;
@Value("${mqtt.ssl.key_store_password}")
@Value("${transport.mqtt.ssl.key_store_password}")
private String keyStorePassword;
@Value("${mqtt.ssl.key_password}")
@Value("${transport.mqtt.ssl.key_password}")
private String keyPassword;
@Value("${mqtt.ssl.key_store_type}")
@Value("${transport.mqtt.ssl.key_store_type}")
private String keyStoreType;
@Autowired

1
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java

@ -271,6 +271,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
case MqttTopics.DEVICE_RPC_RESPONSE_SUB_TOPIC:
case MqttTopics.GATEWAY_ATTRIBUTES_TOPIC:
case MqttTopics.GATEWAY_RPC_TOPIC:
case MqttTopics.GATEWAY_ATTRIBUTES_RESPONSE_TOPIC:
case MqttTopics.DEVICE_ATTRIBUTES_RESPONSES_TOPIC:
registerSubQoS(topic, grantedQoSList, reqQoS);
break;

3
common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportServerInitializer.java

@ -36,9 +36,8 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
@Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
SslHandler sslHandler = null;
if (context.getSslHandlerProvider() != null) {
sslHandler = context.getSslHandlerProvider().getSslHandler();
SslHandler sslHandler = context.getSslHandlerProvider().getSslHandler();
pipeline.addLast(sslHandler);
context.setSslHandler(sslHandler);
}

2
common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/AbstractTransportService.java

@ -40,7 +40,7 @@ public abstract class AbstractTransportService implements TransportService {
private boolean rateLimitEnabled;
@Value("${transport.rate_limits.tenant}")
private String perTenantLimitsConf;
@Value("${transport.rate_limits.tenant}")
@Value("${transport.rate_limits.device}")
private String perDevicesLimitsConf;
@Value("${transport.sessions.inactivity_timeout}")
private long sessionInactivityTimeout;

2
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -31,6 +31,8 @@ import java.util.UUID;
*/
public interface AlarmDao extends Dao<Alarm> {
Boolean deleteAlarm(TenantId tenantId, Alarm alarm);
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
ListenableFuture<Alarm> findAlarmByIdAsync(TenantId tenantId, UUID key);

2
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -35,6 +35,8 @@ public interface AlarmService {
Alarm createOrUpdateAlarm(Alarm alarm);
Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId);
ListenableFuture<Boolean> ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTs);
ListenableFuture<Boolean> clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long ackTs);

26
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
@ -118,6 +119,21 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
return alarmDao.findLatestByOriginatorAndType(tenantId, originator, type);
}
@Override
public Boolean deleteAlarm(TenantId tenantId, AlarmId alarmId) {
try {
log.debug("Deleting Alarm Id: {}", alarmId);
Alarm alarm = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()).get();
if (alarm == null) {
return false;
}
deleteEntityRelations(tenantId, alarm.getId());
return alarmDao.deleteAlarm(tenantId, alarm);
} catch (ExecutionException | InterruptedException e) {
throw new RuntimeException(e);
}
}
private Alarm createAlarm(Alarm alarm) throws InterruptedException, ExecutionException {
log.debug("New Alarm : {}", alarm);
Alarm saved = alarmDao.save(alarm.getTenantId(), alarm);
@ -127,9 +143,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
private void createAlarmRelations(Alarm alarm) throws InterruptedException, ExecutionException {
if (alarm.isPropagate()) {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
List<EntityId> parentEntities = relationService.findByQuery(alarm.getTenantId(), query).get().stream().map(EntityRelation::getFrom).collect(Collectors.toList());
List<EntityId> parentEntities = getParentEntities(alarm);
for (EntityId parentId : parentEntities) {
createAlarmRelation(alarm.getTenantId(), parentId, alarm.getId(), alarm.getStatus(), true);
}
@ -137,6 +151,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
createAlarmRelation(alarm.getTenantId(), alarm.getOriginator(), alarm.getId(), alarm.getStatus(), true);
}
private List<EntityId> getParentEntities(Alarm alarm) throws InterruptedException, ExecutionException {
EntityRelationsQuery query = new EntityRelationsQuery();
query.setParameters(new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE));
return relationService.findByQuery(alarm.getTenantId(), query).get().stream().map(EntityRelation::getFrom).collect(Collectors.toList());
}
private ListenableFuture<Alarm> updateAlarm(Alarm update) {
alarmDataValidator.validate(update, Alarm::getTenantId);
return getAndUpdate(update.getTenantId(), update.getId(), new Function<Alarm, Alarm>() {

12
dao/src/main/java/org/thingsboard/server/dao/alarm/CassandraAlarmDao.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.alarm;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.google.common.util.concurrent.Futures;
@ -78,6 +79,17 @@ public class CassandraAlarmDao extends CassandraAbstractModelDao<AlarmEntity, Al
return super.save(tenantId, alarm);
}
@Override
public Boolean deleteAlarm(TenantId tenantId, Alarm alarm) {
Statement delete = QueryBuilder.delete().all().from(getColumnFamilyName()).where(eq(ModelConstants.ID_PROPERTY, alarm.getId().getId()))
.and(eq(ALARM_TENANT_ID_PROPERTY, tenantId.getId()))
.and(eq(ALARM_ORIGINATOR_ID_PROPERTY, alarm.getOriginator().getId()))
.and(eq(ALARM_ORIGINATOR_TYPE_PROPERTY, alarm.getOriginator().getEntityType()))
.and(eq(ALARM_TYPE_PROPERTY, alarm.getType()));
log.debug("Remove request: {}", delete.toString());
return executeWrite(tenantId, delete).wasApplied();
}
@Override
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
Select select = select().from(ALARM_COLUMN_FAMILY_NAME);

35
dao/src/main/java/org/thingsboard/server/dao/model/sql/TsKvEntity.java

@ -52,22 +52,33 @@ public final class TsKvEntity implements ToData<TsKvEntry> {
public TsKvEntity() {
}
public TsKvEntity(Double avgLongValue, Double avgDoubleValue) {
if(avgLongValue != null) {
this.longValue = avgLongValue.longValue();
public TsKvEntity(Long longSumValue, Double doubleSumValue, Long longCountValue, Long doubleCountValue) {
double sum = 0.0;
if (longSumValue != null) {
sum += longSumValue;
}
this.doubleValue = avgDoubleValue;
if (doubleSumValue != null) {
sum += doubleSumValue;
}
this.doubleValue = sum / (longCountValue + doubleCountValue);
}
public TsKvEntity(Long sumLongValue, Double sumDoubleValue) {
this.longValue = sumLongValue;
this.doubleValue = sumDoubleValue;
if (sumDoubleValue != null) {
this.doubleValue = sumDoubleValue + (sumLongValue != null ? sumLongValue.doubleValue() : 0.0);
} else {
this.longValue = sumLongValue;
}
}
public TsKvEntity(String strValue, Long longValue, Double doubleValue) {
public TsKvEntity(String strValue, Long longValue, Double doubleValue, boolean max) {
this.strValue = strValue;
this.longValue = longValue;
this.doubleValue = doubleValue;
if (longValue != null && doubleValue != null) {
this.doubleValue = max ? Math.max(doubleValue, longValue.doubleValue()) : Math.min(doubleValue, longValue.doubleValue());
} else {
this.longValue = longValue;
this.doubleValue = doubleValue;
}
}
public TsKvEntity(Long booleanValueCount, Long strValueCount, Long longValueCount, Long doubleValueCount) {
@ -75,10 +86,8 @@ public final class TsKvEntity implements ToData<TsKvEntry> {
this.longValue = booleanValueCount;
} else if (strValueCount != 0) {
this.longValue = strValueCount;
} else if (longValueCount != 0) {
this.longValue = longValueCount;
} else if (doubleValueCount != 0) {
this.longValue = doubleValueCount;
} else {
this.longValue = longValueCount + doubleValueCount;
}
}

34
dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java

@ -20,6 +20,7 @@ import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.Tenant;
import org.thingsboard.server.common.data.id.EntityId;
@ -274,14 +275,43 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC
public List<RuleNode> getRuleChainNodes(TenantId tenantId, RuleChainId ruleChainId) {
Validator.validateId(ruleChainId, "Incorrect rule chain id for search request.");
List<EntityRelation> relations = getRuleChainToNodeRelations(tenantId, ruleChainId);
List<RuleNode> ruleNodes = relations.stream().map(relation -> ruleNodeDao.findById(tenantId, relation.getTo().getId())).collect(Collectors.toList());
List<RuleNode> ruleNodes = new ArrayList<>();
for (EntityRelation relation : relations) {
RuleNode ruleNode = ruleNodeDao.findById(tenantId, relation.getTo().getId());
if (ruleNode != null) {
ruleNodes.add(ruleNode);
} else {
relationService.deleteRelation(tenantId, relation);
}
}
return ruleNodes;
}
@Override
public List<EntityRelation> getRuleNodeRelations(TenantId tenantId, RuleNodeId ruleNodeId) {
Validator.validateId(ruleNodeId, "Incorrect rule node id for search request.");
return relationService.findByFrom(tenantId, ruleNodeId, RelationTypeGroup.RULE_NODE);
List<EntityRelation> relations = relationService.findByFrom(tenantId, ruleNodeId, RelationTypeGroup.RULE_NODE);
List<EntityRelation> validRelations = new ArrayList<>();
for (EntityRelation relation : relations) {
boolean valid = true;
EntityType toType = relation.getTo().getEntityType();
if (toType == EntityType.RULE_NODE || toType == EntityType.RULE_CHAIN) {
BaseData entity;
if (relation.getTo().getEntityType() == EntityType.RULE_NODE) {
entity = ruleNodeDao.findById(tenantId, relation.getTo().getId());
} else {
entity = ruleChainDao.findById(tenantId, relation.getTo().getId());
}
if (entity == null) {
relationService.deleteRelation(tenantId, relation);
valid = false;
}
}
if (valid) {
validRelations.add(relation);
}
}
return validRelations;
}
@Override

5
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -68,6 +68,11 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
return alarmRepository;
}
@Override
public Boolean deleteAlarm(TenantId tenantId, Alarm alarm) {
return removeById(tenantId, alarm.getUuidId());
}
@Override
public ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) {
return service.submit(() -> {

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

@ -55,7 +55,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(MAX(tskv.strValue), MAX(tskv.longValue), MAX(tskv.doubleValue)) FROM TsKvEntity tskv " +
@Query("SELECT new TsKvEntity(MAX(tskv.strValue), MAX(tskv.longValue), MAX(tskv.doubleValue), true) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findMax(@Param("entityId") String entityId,
@ -65,7 +65,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(MIN(tskv.strValue), MIN(tskv.longValue), MIN(tskv.doubleValue)) FROM TsKvEntity tskv " +
@Query("SELECT new TsKvEntity(MIN(tskv.strValue), MIN(tskv.longValue), MIN(tskv.doubleValue), false) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findMin(@Param("entityId") String entityId,
@ -85,7 +85,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(AVG(tskv.longValue), AVG(tskv.doubleValue)) FROM TsKvEntity tskv " +
@Query("SELECT new TsKvEntity(SUM(tskv.longValue), SUM(tskv.doubleValue), COUNT(tskv.longValue), COUNT(tskv.doubleValue)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findAvg(@Param("entityId") String entityId,

24
dao/src/main/java/org/thingsboard/server/dao/timeseries/AggregatePartitionsFunction.java

@ -98,6 +98,10 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
curLValue = getLongValue(row);
}
if (doubleCount > 0) {
<<<<<<< HEAD
=======
aggResult.hasDouble = true;
>>>>>>> upstream/master
aggResult.dataType = DataType.DOUBLE;
curCount += doubleCount;
curDValue = getDoubleValue(row);
@ -222,17 +226,36 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
if (aggResult.count == 0 || (aggResult.dataType == DataType.DOUBLE && aggResult.dValue == null) || (aggResult.dataType == DataType.LONG && aggResult.lValue == null)) {
return Optional.empty();
} else if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) {
<<<<<<< HEAD
double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L);
return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count))));
=======
if(aggregation == Aggregation.AVG || aggResult.hasDouble) {
double sum = Optional.ofNullable(aggResult.dValue).orElse(0.0d) + Optional.ofNullable(aggResult.lValue).orElse(0L);
return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.SUM ? sum : (sum / aggResult.count))));
} else {
return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggregation == Aggregation.SUM ? aggResult.lValue : (aggResult.lValue / aggResult.count))));
}
>>>>>>> upstream/master
}
return Optional.empty();
}
private Optional<TsKvEntry> processMinOrMaxResult(AggregationResult aggResult) {
if (aggResult.dataType == DataType.DOUBLE || aggResult.dataType == DataType.LONG) {
<<<<<<< HEAD
double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE);
double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE);
return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL))));
=======
if(aggResult.hasDouble) {
double currentD = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.dValue).orElse(Double.MAX_VALUE) : Optional.ofNullable(aggResult.dValue).orElse(Double.MIN_VALUE);
double currentL = aggregation == Aggregation.MIN ? Optional.ofNullable(aggResult.lValue).orElse(Long.MAX_VALUE) : Optional.ofNullable(aggResult.lValue).orElse(Long.MIN_VALUE);
return Optional.of(new BasicTsKvEntry(ts, new DoubleDataEntry(key, aggregation == Aggregation.MIN ? Math.min(currentD, currentL) : Math.max(currentD, currentL))));
} else {
return Optional.of(new BasicTsKvEntry(ts, new LongDataEntry(key, aggResult.lValue)));
}
>>>>>>> upstream/master
} else if (aggResult.dataType == DataType.STRING) {
return Optional.of(new BasicTsKvEntry(ts, new StringDataEntry(key, aggResult.sValue)));
} else {
@ -247,5 +270,6 @@ public class AggregatePartitionsFunction implements com.google.common.base.Funct
Double dValue = null;
Long lValue = null;
long count = 0;
boolean hasDouble = false;
}
}

31
dao/src/main/java/org/thingsboard/server/dao/util/AbstractBufferedRateExecutor.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.dao.util;
import com.datastax.driver.core.*;
import com.google.common.util.concurrent.FutureCallback;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -22,6 +23,7 @@ import com.google.common.util.concurrent.SettableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.msg.tools.TbRateLimits;
import org.thingsboard.server.dao.nosql.CassandraStatementTask;
import javax.annotation.Nullable;
import java.util.UUID;
@ -183,12 +185,39 @@ public abstract class AbstractBufferedRateExecutor<T extends AsyncTask, F extend
private void logTask(String action, AsyncTaskContext<T, V> taskCtx) {
if (log.isTraceEnabled()) {
log.trace("[{}] {} task: {}", taskCtx.getId(), action, taskCtx);
if (taskCtx.getTask() instanceof CassandraStatementTask) {
CassandraStatementTask cassStmtTask = (CassandraStatementTask) taskCtx.getTask();
if (cassStmtTask.getStatement() instanceof BoundStatement) {
BoundStatement stmt = (BoundStatement) cassStmtTask.getStatement();
String query = toStringWithValues(stmt, ProtocolVersion.V5);
log.trace("[{}] {} task: {}, BoundStatement query: {}", taskCtx.getId(), action, taskCtx, query);
}
} else {
log.trace("[{}] {} task: {}", taskCtx.getId(), action, taskCtx);
}
} else {
log.debug("[{}] {} task", taskCtx.getId(), action);
}
}
private static String toStringWithValues(BoundStatement boundStatement, ProtocolVersion protocolVersion) {
CodecRegistry codecRegistry = boundStatement.preparedStatement().getCodecRegistry();
PreparedStatement preparedStatement = boundStatement.preparedStatement();
String query = preparedStatement.getQueryString();
ColumnDefinitions defs = preparedStatement.getVariables();
int index = 0;
for (ColumnDefinitions.Definition def : defs) {
DataType type = def.getType();
TypeCodec<Object> codec = codecRegistry.codecFor(type);
if (boundStatement.getBytesUnsafe(index) != null) {
Object value = codec.deserialize(boundStatement.getBytesUnsafe(index), protocolVersion);
query = query.replaceFirst("\\?", codec.format(value));
}
index++;
}
return query;
}
protected int getQueueSize() {
return queue.size();
}

4
dao/src/main/resources/sql/schema-entities.sql

@ -72,7 +72,7 @@ CREATE TABLE IF NOT EXISTS attribute_kv (
long_v bigint,
dbl_v double precision,
last_update_ts bigint,
CONSTRAINT attribute_kv_unq_key UNIQUE (entity_type, entity_id, attribute_type, attribute_key)
CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key)
);
CREATE TABLE IF NOT EXISTS component_descriptor (
@ -148,7 +148,7 @@ CREATE TABLE IF NOT EXISTS relation (
relation_type_group varchar(255),
relation_type varchar(255),
additional_info varchar,
CONSTRAINT relation_unq_key UNIQUE (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
);
CREATE TABLE IF NOT EXISTS tb_user (

4
dao/src/main/resources/sql/schema-ts.sql

@ -23,7 +23,7 @@ CREATE TABLE IF NOT EXISTS ts_kv (
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
CONSTRAINT ts_kv_unq_key UNIQUE (entity_type, entity_id, key, ts)
CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_type, entity_id, key, ts)
);
CREATE TABLE IF NOT EXISTS ts_kv_latest (
@ -35,5 +35,5 @@ CREATE TABLE IF NOT EXISTS ts_kv_latest (
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
CONSTRAINT ts_kv_latest_unq_key UNIQUE (entity_type, entity_id, key)
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_type, entity_id, key)
);

4
dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java

@ -25,9 +25,7 @@ import java.util.Arrays;
@RunWith(ClasspathSuite.class)
@ClassnameFilters({
"org.thingsboard.server.dao.service.*ServiceNoSqlTest",
"org.thingsboard.server.dao.service.queue.cassandra.*.*.*Test",
"org.thingsboard.server.dao.service.queue.cassandra.*Test"
"org.thingsboard.server.dao.service.*ServiceNoSqlTest"
})
public class NoSqlDaoServiceTestSuite {

83
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java

@ -31,7 +31,9 @@ import org.thingsboard.server.common.data.id.TenantId;
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.relation.RelationTypeGroup;
import java.util.List;
import java.util.concurrent.ExecutionException;
public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
@ -184,4 +186,85 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
}
@Test
public void testDeleteAlarm() throws ExecutionException, InterruptedException {
AssetId parentId = new AssetId(UUIDs.timeBased());
AssetId childId = new AssetId(UUIDs.timeBased());
EntityRelation relation = new EntityRelation(parentId, childId, EntityRelation.CONTAINS_TYPE);
Assert.assertTrue(relationService.saveRelationAsync(tenantId, relation).get());
long ts = System.currentTimeMillis();
Alarm alarm = Alarm.builder().tenantId(tenantId).originator(childId)
.type(TEST_ALARM)
.propagate(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
Alarm created = alarmService.createOrUpdateAlarm(alarm);
TimePageData<AlarmInfo> alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(parentId)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));
List<EntityRelation> toAlarmRelations = relationService.findByTo(tenantId, created.getId(), RelationTypeGroup.ALARM);
Assert.assertEquals(8, toAlarmRelations.size());
List<EntityRelation> fromChildRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
Assert.assertEquals(4, fromChildRelations.size());
List<EntityRelation> fromParentRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
Assert.assertEquals(4, fromParentRelations.size());
Assert.assertTrue("Alarm was not deleted when expected", alarmService.deleteAlarm(tenantId, created.getId()));
Alarm fetched = alarmService.findAlarmByIdAsync(tenantId, created.getId()).get();
Assert.assertNull("Alarm was returned when it was expected to be null", fetched);
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(childId)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(0, alarms.getData().size());
// Check parent relation
alarms = alarmService.findAlarms(tenantId, AlarmQuery.builder()
.affectedEntityId(parentId)
.status(AlarmStatus.ACTIVE_UNACK).pageLink(
new TimePageLink(1, 0L, System.currentTimeMillis(), false)
).build()).get();
Assert.assertNotNull(alarms.getData());
Assert.assertEquals(0, alarms.getData().size());
toAlarmRelations = relationService.findByTo(tenantId, created.getId(), RelationTypeGroup.ALARM);
Assert.assertEquals(0, toAlarmRelations.size());
fromChildRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
Assert.assertEquals(0, fromChildRelations.size());
fromParentRelations = relationService.findByFrom(tenantId, childId, RelationTypeGroup.ALARM);
Assert.assertEquals(0, fromParentRelations.size());
}
}

104
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java

@ -221,13 +221,13 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
60000, 20000, 3, Aggregation.AVG))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
assertEquals(java.util.Optional.of(150L), list.get(0).getLongValue());
assertEquals(java.util.Optional.of(150.0), list.get(0).getDoubleValue());
assertEquals(30000, list.get(1).getTs());
assertEquals(java.util.Optional.of(350L), list.get(1).getLongValue());
assertEquals(java.util.Optional.of(350.0), list.get(1).getDoubleValue());
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
assertEquals(java.util.Optional.of(550.0), list.get(2).getDoubleValue());
list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.SUM))).get();
@ -282,12 +282,110 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue());
}
@Test
public void testFindDeviceLongAndDoubleTsData() throws Exception {
DeviceId deviceId = new DeviceId(UUIDs.timeBased());
List<TsKvEntry> entries = new ArrayList<>();
entries.add(save(deviceId, 5000, 100));
entries.add(save(deviceId, 15000, 200.0));
entries.add(save(deviceId, 25000, 300));
entries.add(save(deviceId, 35000, 400.0));
entries.add(save(deviceId, 45000, 500));
entries.add(save(deviceId, 55000, 600.0));
List<TsKvEntry> list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.NONE))).get();
assertEquals(3, list.size());
assertEquals(55000, list.get(0).getTs());
assertEquals(java.util.Optional.of(600.0), list.get(0).getDoubleValue());
assertEquals(45000, list.get(1).getTs());
assertEquals(java.util.Optional.of(500L), list.get(1).getLongValue());
assertEquals(35000, list.get(2).getTs());
assertEquals(java.util.Optional.of(400.0), list.get(2).getDoubleValue());
list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.AVG))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
assertEquals(java.util.Optional.of(150.0), list.get(0).getDoubleValue());
assertEquals(30000, list.get(1).getTs());
assertEquals(java.util.Optional.of(350.0), list.get(1).getDoubleValue());
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(550.0), list.get(2).getDoubleValue());
list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.SUM))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
assertEquals(java.util.Optional.of(300.0), list.get(0).getDoubleValue());
assertEquals(30000, list.get(1).getTs());
assertEquals(java.util.Optional.of(700.0), list.get(1).getDoubleValue());
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(1100.0), list.get(2).getDoubleValue());
list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MIN))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
assertEquals(java.util.Optional.of(100.0), list.get(0).getDoubleValue());
assertEquals(30000, list.get(1).getTs());
assertEquals(java.util.Optional.of(300.0), list.get(1).getDoubleValue());
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(500.0), list.get(2).getDoubleValue());
list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MAX))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
assertEquals(java.util.Optional.of(200.0), list.get(0).getDoubleValue());
assertEquals(30000, list.get(1).getTs());
assertEquals(java.util.Optional.of(400.0), list.get(1).getDoubleValue());
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(600.0), list.get(2).getDoubleValue());
list = tsService.findAll(tenantId, deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.COUNT))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
assertEquals(java.util.Optional.of(2L), list.get(0).getLongValue());
assertEquals(30000, list.get(1).getTs());
assertEquals(java.util.Optional.of(2L), list.get(1).getLongValue());
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(2L), list.get(2).getLongValue());
}
private TsKvEntry save(DeviceId deviceId, long ts, long value) throws Exception {
TsKvEntry entry = new BasicTsKvEntry(ts, new LongDataEntry(LONG_KEY, value));
tsService.save(tenantId, deviceId, entry).get();
return entry;
}
private TsKvEntry save(DeviceId deviceId, long ts, double value) throws Exception {
TsKvEntry entry = new BasicTsKvEntry(ts, new DoubleDataEntry(LONG_KEY, value));
tsService.save(tenantId, deviceId, entry).get();
return entry;
}
private void saveEntries(DeviceId deviceId, long ts) throws ExecutionException, InterruptedException {
tsService.save(tenantId, deviceId, toTsEntry(ts, stringKvEntry)).get();
tsService.save(tenantId, deviceId, toTsEntry(ts, longKvEntry)).get();

42
docker/README.md

@ -1,4 +1,4 @@
# Docker configuration for ThingsBoard Microservices
# Docker configuration for ThingsBoard Microservices
This folder containing scripts and Docker Compose configurations to run ThingsBoard in Microservices mode.
@ -9,19 +9,19 @@ Before starting please make sure [Docker CE](https://docs.docker.com/install/) a
## Installation
Before performing initial installation you can configure the type of database to be used with ThinsBoard.
Before performing initial installation you can configure the type of database to be used with ThingsBoard.
In order to set database type change the value of `DATABASE` variable in `.env` file to one of the following:
- `postgres` - use PostgreSQL database;
- `cassandra` - use Cassandra database;
**NOTE**: According to the database type corresponding docker service will be deployed (see `docker-compose.postgres.yml`, `docker-compose.cassandra.yml` for details).
**NOTE**: According to the database type corresponding docker service will be deployed (see `docker-compose.postgres.yml`, `docker-compose.cassandra.yml` for details).
Execute the following command to run installation:
`
`
$ ./docker-install-tb.sh --loadDemo
`
`
Where:
@ -31,56 +31,56 @@ Where:
Execute the following command to start services:
`
`
$ ./docker-start-services.sh
`
`
After a while when all services will be successfully started you can open `http://{your-host-ip}` in you browser (for ex. `http://localhost`).
You should see ThingsBoard login page.
Use the following default credentials:
- **Systen Administrator**: sysadmin@thingsboard.org / sysadmin
- **System Administrator**: sysadmin@thingsboard.org / sysadmin
If you installed DataBase with demo data (using `--loadDemo` flag) you can also use the following credentials:
- **Tenant Administrator**: tenant@thingsboard.org / tenant
- **Customer User**: customer@thingsboard.org / customer
In case of any issues you can examine service logs for errors.
In case of any issues you can examine service logs for errors.
For example to see ThingsBoard node logs execute the following command:
`
`
$ docker-compose logs -f tb1
`
`
Or use `docker-compose ps` to see the state of all the containers.
Use `docker-compose logs --f` to inspect the logs of all running services.
See [docker-compose logs](https://docs.docker.com/compose/reference/logs/) command reference for details.
See [docker-compose logs](https://docs.docker.com/compose/reference/logs/) command reference for details.
Execute the following command to stop services:
`
`
$ ./docker-stop-services.sh
`
`
Execute the following command to stop and completely remove deployed docker containers:
`
`
$ ./docker-remove-services.sh
`
`
Execute the following command to update particular or all services (pull newer docker image and rebuild container):
`
`
$ ./docker-update-service.sh [SERVICE...]
`
`
Where:
- `[SERVICE...]` - list of services to update (defined in docker-compose configurations). If not specified all services will be updated.
- `[SERVICE...]` - list of services to update (defined in docker-compose configurations). If not specified all services will be updated.
## Upgrading
## Upgrading
In case when database upgrade is needed, execute the following commands:

9
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java

@ -32,7 +32,8 @@ import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.ssl.SSLContexts;
import org.junit.*;
import org.junit.BeforeClass;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
@ -43,7 +44,10 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
import javax.net.ssl.*;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;
import java.net.URI;
import java.security.cert.X509Certificate;
import java.util.List;
@ -54,6 +58,7 @@ import java.util.Random;
public abstract class AbstractContainerTest {
protected static final String HTTPS_URL = "https://localhost";
protected static final String WSS_URL = "wss://localhost";
protected static String TB_TOKEN;
protected static RestClient restClient;
protected ObjectMapper mapper = new ObjectMapper();

66
msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.msa.connectivity;
import com.fasterxml.jackson.databind.JsonNode;
import com.google.common.collect.Sets;
import org.junit.Assert;
import org.junit.Test;
@ -25,6 +26,17 @@ import org.thingsboard.server.msa.AbstractContainerTest;
import org.thingsboard.server.msa.WsClient;
import org.thingsboard.server.msa.mapper.WsTelemetryResponse;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.thingsboard.server.common.data.DataConstants.DEVICE;
import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE;
public class HttpClientTest extends AbstractContainerTest {
@Test
@ -52,6 +64,58 @@ public class HttpClientTest extends AbstractContainerTest {
Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0)));
Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73)));
restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId());
restClient.deleteDevice(device.getId());
}
@Test
public void getAttributes() throws Exception {
restClient.login("tenant@thingsboard.org", "tenant");
TB_TOKEN = restClient.getToken();
Device device = createDevice("test");
String accessToken = restClient.getCredentials(device.getId()).getCredentialsId();
assertNotNull(accessToken);
ResponseEntity deviceSharedAttributes = restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/plugins/telemetry/" + DEVICE + "/" + device.getId().toString() + "/attributes/" + SHARED_SCOPE, mapper.readTree(createPayload().toString()),
ResponseEntity.class,
accessToken);
Assert.assertTrue(deviceSharedAttributes.getStatusCode().is2xxSuccessful());
ResponseEntity deviceClientsAttributes = restClient.getRestTemplate()
.postForEntity(HTTPS_URL + "/api/v1/" + accessToken + "/attributes/", mapper.readTree(createPayload().toString()),
ResponseEntity.class,
accessToken);
Assert.assertTrue(deviceClientsAttributes.getStatusCode().is2xxSuccessful());
TimeUnit.SECONDS.sleep(3);
Optional<JsonNode> allOptional = restClient.getAttributes(accessToken, null, null);
assertTrue(allOptional.isPresent());
JsonNode all = allOptional.get();
assertEquals(2, all.size());
assertEquals(mapper.readTree(createPayload().toString()), all.get("shared"));
assertEquals(mapper.readTree(createPayload().toString()), all.get("client"));
Optional<JsonNode> sharedOptional = restClient.getAttributes(accessToken, null, "stringKey");
assertTrue(sharedOptional.isPresent());
JsonNode shared = sharedOptional.get();
assertEquals(shared.get("shared").get("stringKey"), mapper.readTree(createPayload().get("stringKey").toString()));
assertFalse(shared.has("client"));
Optional<JsonNode> clientOptional = restClient.getAttributes(accessToken, "longKey,stringKey", null);
assertTrue(clientOptional.isPresent());
JsonNode client = clientOptional.get();
assertFalse(client.has("shared"));
assertEquals(mapper.readTree(createPayload().get("longKey").toString()), client.get("client").get("longKey"));
assertEquals(client.get("client").get("stringKey"), mapper.readTree(createPayload().get("stringKey").toString()));
restClient.deleteDevice(device.getId());
}
}

3
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttChannelHandler.java

@ -222,6 +222,9 @@ final class MqttChannelHandler extends SimpleChannelInboundHandler<MqttMessage>
private void handlePuback(MqttPubAckMessage message) {
MqttPendingPublish pendingPublish = this.client.getPendingPublishes().get(message.variableHeader().messageId());
if (pendingPublish == null) {
return;
}
pendingPublish.getFuture().setSuccess(null);
pendingPublish.onPubackReceived();
this.client.getPendingPublishes().remove(message.variableHeader().messageId());

2
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientCallback.java

@ -15,8 +15,6 @@
*/
package org.thingsboard.mqtt;
import io.netty.channel.ChannelId;
/**
* Created by Valerii Sosliuk on 12/30/2017.
*/

18
pom.xml

@ -70,6 +70,7 @@
<surfire.version>2.19.1</surfire.version>
<jar-plugin.version>3.0.2</jar-plugin.version>
<springfox-swagger.version>2.6.1</springfox-swagger.version>
<springfox-swagger-ui-rfc6570.version>1.0.0</springfox-swagger-ui-rfc6570.version>
<bouncycastle.version>1.56</bouncycastle.version>
<winsw.version>2.0.1</winsw.version>
<hsqldb.version>2.4.0</hsqldb.version>
@ -83,6 +84,7 @@
<delight-nashorn-sandbox.version>0.1.14</delight-nashorn-sandbox.version>
<kafka.version>2.0.0</kafka.version>
<bucket4j.version>4.1.1</bucket4j.version>
<fst.version>2.57</fst.version>
</properties>
<modules>
@ -755,12 +757,6 @@
<artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version>
</dependency>
<dependency>
<groupId>ru.yandex.qatools.embed</groupId>
<artifactId>postgresql-embedded</artifactId>
<version>2.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
@ -794,6 +790,16 @@
<artifactId>bucket4j-core</artifactId>
<version>${bucket4j.version}</version>
</dependency>
<dependency>
<groupId>de.ruedigermoeller</groupId>
<artifactId>fst</artifactId>
<version>${fst.version}</version>
</dependency>
<dependency>
<groupId>io.springfox.ui</groupId>
<artifactId>springfox-swagger-ui-rfc6570</artifactId>
<version>${springfox-swagger-ui-rfc6570.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

51
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractRelationActionNode.java

@ -39,7 +39,9 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.customer.CustomerService;
@ -47,6 +49,7 @@ import org.thingsboard.server.dao.dashboard.DashboardService;
import org.thingsboard.server.dao.device.DeviceService;
import org.thingsboard.server.dao.entityview.EntityViewService;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
@ -58,10 +61,8 @@ import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback;
public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationActionNodeConfiguration> implements TbNode {
protected C config;
protected EntityId fromId;
protected EntityId toId;
private LoadingCache<Entitykey, EntityContainer> entityIdCache;
private LoadingCache<EntityKey, EntityContainer> entityIdCache;
@Override
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
@ -84,7 +85,7 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
public void destroy() {
}
private ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
protected ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
}
@ -96,40 +97,58 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
protected ListenableFuture<EntityContainer> getEntity(TbContext ctx, TbMsg msg) {
String entityName = TbNodeUtils.processPattern(this.config.getEntityNamePattern(), msg.getMetaData());
String type = null;
String type;
if (this.config.getEntityTypePattern() != null) {
type = TbNodeUtils.processPattern(this.config.getEntityTypePattern(), msg.getMetaData());
} else {
type = null;
}
EntityType entityType = EntityType.valueOf(this.config.getEntityType());
Entitykey key = new Entitykey(entityName, type, entityType);
EntityKey key = new EntityKey(entityName, type, entityType);
return ctx.getDbCallbackExecutor().executeAsync(() -> {
EntityContainer entityContainer = entityIdCache.get(key);
if (entityContainer.getEntityId() == null) {
throw new RuntimeException("No entity found with type '" + key.getEntityType() + " ' and name '" + key.getEntityName() + "'.");
throw new RuntimeException("No entity found with type '" + key.getEntityType() + "' and name '" + key.getEntityName() + "'.");
}
return entityContainer;
});
}
protected void processSearchDirection(TbMsg msg, EntityContainer entityContainer) {
protected SearchDirectionIds processSingleSearchDirection(TbMsg msg, EntityContainer entityContainer) {
SearchDirectionIds searchDirectionIds = new SearchDirectionIds();
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
fromId = EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString());
toId = msg.getOriginator();
searchDirectionIds.setFromId(EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString()));
searchDirectionIds.setToId(msg.getOriginator());
} else {
toId = EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString());
fromId = msg.getOriginator();
searchDirectionIds.setToId(EntityIdFactory.getByTypeAndId(entityContainer.getEntityType().name(), entityContainer.getEntityId().toString()));
searchDirectionIds.setFromId(msg.getOriginator());
}
return searchDirectionIds;
}
protected ListenableFuture<List<EntityRelation>> processListSearchDirection(TbContext ctx, TbMsg msg) {
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
return ctx.getRelationService().findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
} else {
return ctx.getRelationService().findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON);
}
}
@Data
@AllArgsConstructor
private static class Entitykey {
private static class EntityKey {
private String entityName;
private String type;
private EntityType entityType;
}
private static class EntityCacheLoader extends CacheLoader<Entitykey, EntityContainer> {
@Data
protected static class SearchDirectionIds {
private EntityId fromId;
private EntityId toId;
}
private static class EntityCacheLoader extends CacheLoader<EntityKey, EntityContainer> {
private final TbContext ctx;
private final boolean createIfNotExists;
@ -140,11 +159,11 @@ public abstract class TbAbstractRelationActionNode<C extends TbAbstractRelationA
}
@Override
public EntityContainer load(Entitykey key) {
public EntityContainer load(EntityKey key) {
return loadEntity(key);
}
private EntityContainer loadEntity(Entitykey entitykey) {
private EntityContainer loadEntity(EntityKey entitykey) {
EntityType type = entitykey.getEntityType();
EntityContainer targetEntity = new EntityContainer();
targetEntity.setEntityType(type);

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

@ -54,7 +54,8 @@ public class TbClearAlarmNode extends TbAbstractAlarmNode<TbClearAlarmNodeConfig
@Override
protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
String alarmType = TbNodeUtils.processPattern(this.config.getAlarmType(), msg.getMetaData());
ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
return Futures.transformAsync(latest, a -> {
if (a != null && !a.getStatus().isCleared()) {
return clearAlarm(ctx, msg, a);

62
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java

@ -16,6 +16,7 @@
package org.thingsboard.rule.engine.action;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.base.Function;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
@ -31,6 +32,8 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import java.io.IOException;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
@ -49,6 +52,8 @@ import org.thingsboard.server.common.msg.TbMsg;
)
public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConfiguration> {
private static ObjectMapper mapper = new ObjectMapper();
@Override
protected TbCreateAlarmNodeConfiguration loadAlarmNodeConfig(TbNodeConfiguration configuration) throws TbNodeException {
return TbNodeUtils.convert(configuration, TbCreateAlarmNodeConfiguration.class);
@ -56,32 +61,59 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
@Override
protected ListenableFuture<AlarmResult> processAlarm(TbContext ctx, TbMsg msg) {
ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), config.getAlarmType());
return Futures.transformAsync(latest, a -> {
if (a == null || a.getStatus().isCleared()) {
return createNewAlarm(ctx, msg);
String alarmType;
final Alarm msgAlarm;
if (!config.isUseMessageAlarmData()) {
alarmType = config.getAlarmType();
msgAlarm = null;
} else {
try {
msgAlarm = mapper.readValue(msg.getData(), Alarm.class);
msgAlarm.setTenantId(ctx.getTenantId());
alarmType = msgAlarm.getType();
} catch (IOException e) {
ctx.tellFailure(msg, e);
return null;
}
}
ListenableFuture<Alarm> latest = ctx.getAlarmService().findLatestByOriginatorAndType(ctx.getTenantId(), msg.getOriginator(), alarmType);
return Futures.transformAsync(latest, existingAlarm -> {
if (existingAlarm == null || existingAlarm.getStatus().isCleared()) {
return createNewAlarm(ctx, msg, msgAlarm);
} else {
return updateAlarm(ctx, msg, a);
return updateAlarm(ctx, msg, existingAlarm, msgAlarm);
}
}, ctx.getDbCallbackExecutor());
}
private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg) {
ListenableFuture<Alarm> asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null),
details -> buildAlarm(msg, details, ctx.getTenantId()));
private ListenableFuture<AlarmResult> createNewAlarm(TbContext ctx, TbMsg msg, Alarm msgAlarm) {
ListenableFuture<Alarm> asyncAlarm;
if (msgAlarm != null ) {
asyncAlarm = Futures.immediateCheckedFuture(msgAlarm);
} else {
asyncAlarm = Futures.transform(buildAlarmDetails(ctx, msg, null),
details -> buildAlarm(msg, details, ctx.getTenantId()));
}
ListenableFuture<Alarm> asyncCreated = Futures.transform(asyncAlarm,
alarm -> ctx.getAlarmService().createOrUpdateAlarm(alarm), ctx.getDbCallbackExecutor());
return Futures.transform(asyncCreated, alarm -> new AlarmResult(true, false, false, alarm));
}
private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm alarm) {
ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, alarm.getDetails()), (Function<JsonNode, Alarm>) details -> {
alarm.setSeverity(config.getSeverity());
alarm.setPropagate(config.isPropagate());
alarm.setDetails(details);
alarm.setEndTs(System.currentTimeMillis());
return ctx.getAlarmService().createOrUpdateAlarm(alarm);
private ListenableFuture<AlarmResult> updateAlarm(TbContext ctx, TbMsg msg, Alarm existingAlarm, Alarm msgAlarm) {
ListenableFuture<Alarm> asyncUpdated = Futures.transform(buildAlarmDetails(ctx, msg, existingAlarm.getDetails()), (Function<JsonNode, Alarm>) details -> {
if (msgAlarm != null) {
existingAlarm.setSeverity(msgAlarm.getSeverity());
existingAlarm.setPropagate(msgAlarm.isPropagate());
} else {
existingAlarm.setSeverity(config.getSeverity());
existingAlarm.setPropagate(config.isPropagate());
}
existingAlarm.setDetails(details);
existingAlarm.setEndTs(System.currentTimeMillis());
return ctx.getAlarmService().createOrUpdateAlarm(existingAlarm);
}, ctx.getDbCallbackExecutor());
return Futures.transform(asyncUpdated, a -> new AlarmResult(false, true, false, a));

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java

@ -24,6 +24,7 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
private AlarmSeverity severity;
private boolean propagate;
private boolean useMessageAlarmData;
@Override
public TbCreateAlarmNodeConfiguration defaultConfiguration() {
@ -36,6 +37,7 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
configuration.setAlarmType("General Alarm");
configuration.setSeverity(AlarmSeverity.CRITICAL);
configuration.setPropagate(false);
configuration.setUseMessageAlarmData(false);
return configuration;
}
}

45
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java

@ -65,92 +65,91 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode<TbCreateR
}
private ListenableFuture<Boolean> createIfAbsent(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
processSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON),
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON),
result -> {
if (!result) {
return processCreateRelation(ctx, entityContainer);
return processCreateRelation(ctx, entityContainer, sdId);
}
return Futures.immediateFuture(true);
});
}
private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer) {
private ListenableFuture<Boolean> processCreateRelation(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
switch (entityContainer.getEntityType()) {
case ASSET:
return processAsset(ctx, entityContainer);
return processAsset(ctx, entityContainer, sdId);
case DEVICE:
return processDevice(ctx, entityContainer);
return processDevice(ctx, entityContainer, sdId);
case CUSTOMER:
return processCustomer(ctx, entityContainer);
return processCustomer(ctx, entityContainer, sdId);
case DASHBOARD:
return processDashboard(ctx, entityContainer);
return processDashboard(ctx, entityContainer, sdId);
case ENTITY_VIEW:
return processView(ctx, entityContainer);
return processView(ctx, entityContainer, sdId);
case TENANT:
return processTenant(ctx, entityContainer);
return processTenant(ctx, entityContainer, sdId);
}
return Futures.immediateFuture(true);
}
private ListenableFuture<Boolean> processView(TbContext ctx, EntityContainer entityContainer) {
private ListenableFuture<Boolean> processView(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getEntityViewService().findEntityViewByIdAsync(ctx.getTenantId(), new EntityViewId(entityContainer.getEntityId().getId())), entityView -> {
if (entityView != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
} else {
return Futures.immediateFuture(true);
}
});
}
private ListenableFuture<Boolean> processDevice(TbContext ctx, EntityContainer entityContainer) {
private ListenableFuture<Boolean> processDevice(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), new DeviceId(entityContainer.getEntityId().getId())), device -> {
if (device != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
} else {
return Futures.immediateFuture(true);
}
});
}
private ListenableFuture<Boolean> processAsset(TbContext ctx, EntityContainer entityContainer) {
private ListenableFuture<Boolean> processAsset(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), new AssetId(entityContainer.getEntityId().getId())), asset -> {
if (asset != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
} else {
return Futures.immediateFuture(true);
}
});
}
private ListenableFuture<Boolean> processCustomer(TbContext ctx, EntityContainer entityContainer) {
private ListenableFuture<Boolean> processCustomer(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getCustomerService().findCustomerByIdAsync(ctx.getTenantId(), new CustomerId(entityContainer.getEntityId().getId())), customer -> {
if (customer != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
} else {
return Futures.immediateFuture(true);
}
});
}
private ListenableFuture<Boolean> processDashboard(TbContext ctx, EntityContainer entityContainer) {
private ListenableFuture<Boolean> processDashboard(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getDashboardService().findDashboardByIdAsync(ctx.getTenantId(), new DashboardId(entityContainer.getEntityId().getId())), dashboard -> {
if (dashboard != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
} else {
return Futures.immediateFuture(true);
}
});
}
private ListenableFuture<Boolean> processTenant(TbContext ctx, EntityContainer entityContainer) {
private ListenableFuture<Boolean> processTenant(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId) {
return Futures.transformAsync(ctx.getTenantService().findTenantByIdAsync(ctx.getTenantId(), new TenantId(entityContainer.getEntityId().getId())), tenant -> {
if (tenant != null) {
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON));
return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON));
} else {
return Futures.immediateFuture(true);
}
});
}
}

57
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNode.java

@ -25,18 +25,24 @@ import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.util.EntityContainer;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.ArrayList;
import java.util.List;
@Slf4j
@RuleNode(
type = ComponentType.ACTION,
name = "delete relation",
configClazz = TbDeleteRelationNodeConfiguration.class,
nodeDescription = "Finds target Entity by entity name pattern and then delete a relation to Originator Entity by type and direction.",
nodeDetails = "If the relation successfully deleted - Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
nodeDescription = "Finds target Entity by entity name pattern and then delete a relation to Originator Entity by type and direction" +
" if 'Delete single entity' is set to true, otherwise rule node will delete all relations to the originator of the message by type and direction.",
nodeDetails = "If the relation(s) successfully deleted - Message send via <b>Success</b> chain, otherwise <b>Failure</b> chain will be used.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbActionNodeDeleteRelationConfig",
configDirective ="tbActionNodeDeleteRelationConfig",
icon = "remove_circle"
)
public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteRelationNodeConfiguration> {
@ -51,24 +57,55 @@ public class TbDeleteRelationNode extends TbAbstractRelationActionNode<TbDeleteR
return false;
}
@Override
protected ListenableFuture<Boolean> processEntityRelationAction(TbContext ctx, TbMsg msg) {
if(config.isDeleteForSingleEntity()){
return Futures.transformAsync(getEntity(ctx, msg), entityContainer -> doProcessEntityRelationAction(ctx, msg, entityContainer));
} else {
return processList(ctx, msg);
}
}
@Override
protected ListenableFuture<Boolean> doProcessEntityRelationAction(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
return deleteIfExist(ctx, msg, entityContainer);
return processSingle(ctx, msg, entityContainer);
}
private ListenableFuture<Boolean> deleteIfExist(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
processSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON),
private ListenableFuture<Boolean> processList(TbContext ctx, TbMsg msg) {
return Futures.transformAsync(processListSearchDirection(ctx, msg), entityRelations -> {
if(entityRelations.isEmpty()){
return Futures.immediateFuture(true);
} else {
List<ListenableFuture<Boolean>> listenableFutureList = new ArrayList<>();
for (EntityRelation entityRelation: entityRelations) {
listenableFutureList.add(ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), entityRelation));
}
return Futures.transformAsync(Futures.allAsList(listenableFutureList), booleans -> {
for (Boolean bool : booleans) {
if (!bool) {
return Futures.immediateFuture(false);
}
}
return Futures.immediateFuture(true);
});
}
});
}
private ListenableFuture<Boolean> processSingle(TbContext ctx, TbMsg msg, EntityContainer entityContainer) {
SearchDirectionIds sdId = processSingleSearchDirection(msg, entityContainer);
return Futures.transformAsync(ctx.getRelationService().checkRelation(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON),
result -> {
if (result) {
return processDeleteRelation(ctx);
return processSingleDeleteRelation(ctx, sdId);
}
return Futures.immediateFuture(true);
});
}
private ListenableFuture<Boolean> processDeleteRelation(TbContext ctx) {
return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), fromId, toId, config.getRelationType(), RelationTypeGroup.COMMON);
private ListenableFuture<Boolean> processSingleDeleteRelation(TbContext ctx, SearchDirectionIds sdId) {
return ctx.getRelationService().deleteRelationAsync(ctx.getTenantId(), sdId.getFromId(), sdId.getToId(), config.getRelationType(), RelationTypeGroup.COMMON);
}
}

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbDeleteRelationNodeConfiguration.java

@ -22,10 +22,12 @@ import org.thingsboard.server.common.data.relation.EntitySearchDirection;
@Data
public class TbDeleteRelationNodeConfiguration extends TbAbstractRelationActionNodeConfiguration implements NodeConfiguration<TbDeleteRelationNodeConfiguration> {
private boolean deleteForSingleEntity;
@Override
public TbDeleteRelationNodeConfiguration defaultConfiguration() {
TbDeleteRelationNodeConfiguration configuration = new TbDeleteRelationNodeConfiguration();
configuration.setDeleteForSingleEntity(true);
configuration.setDirection(EntitySearchDirection.FROM.name());
configuration.setRelationType("Contains");
configuration.setEntityNamePattern("");

43
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNode.java

@ -15,6 +15,8 @@
*/
package org.thingsboard.rule.engine.filter;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
@ -25,13 +27,13 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.data.relation.RelationTypeGroup;
import org.thingsboard.server.common.msg.TbMsg;
import javax.management.relation.RelationType;
import java.util.List;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback;
/**
@ -43,8 +45,10 @@ import static org.thingsboard.rule.engine.api.util.DonAsynchron.withCallback;
name = "check relation",
configClazz = TbCheckRelationNodeConfiguration.class,
relationTypes = {"True", "False"},
nodeDescription = "Checks the relation from the selected entity to originator of the message by type and direction",
nodeDetails = "If relation exists - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
nodeDescription = "Checks the relation from the selected entity to the originator of the message by type and direction" +
" if 'Check for single entity' is set to true, otherwise rule node will check if exist" +
" any relation to the originator of the message by type and direction.",
nodeDetails = "If at least one relation exists - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbFilterNodeCheckRelationConfig")
public class TbCheckRelationNode implements TbNode {
@ -58,6 +62,16 @@ public class TbCheckRelationNode implements TbNode {
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
ListenableFuture<Boolean> checkRelationFuture;
if (config.isCheckForSingleEntity()) {
checkRelationFuture = processSingle(ctx, msg);
} else {
checkRelationFuture = processList(ctx, msg);
}
withCallback(checkRelationFuture, filterResult -> ctx.tellNext(msg, filterResult ? "True" : "False"), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
}
private ListenableFuture<Boolean> processSingle(TbContext ctx, TbMsg msg) {
EntityId from;
EntityId to;
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
@ -67,8 +81,25 @@ public class TbCheckRelationNode implements TbNode {
to = EntityIdFactory.getByTypeAndId(config.getEntityType(), config.getEntityId());
from = msg.getOriginator();
}
withCallback(ctx.getRelationService().checkRelation(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON),
filterResult -> ctx.tellNext(msg, filterResult ? "True" : "False"), t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor());
return ctx.getRelationService().checkRelation(ctx.getTenantId(), from, to, config.getRelationType(), RelationTypeGroup.COMMON);
}
private ListenableFuture<Boolean> processList(TbContext ctx, TbMsg msg) {
if (EntitySearchDirection.FROM.name().equals(config.getDirection())) {
return Futures.transformAsync(ctx.getRelationService()
.findByToAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList);
} else {
return Futures.transformAsync(ctx.getRelationService()
.findByFromAndTypeAsync(ctx.getTenantId(), msg.getOriginator(), config.getRelationType(), RelationTypeGroup.COMMON), this::isEmptyList);
}
}
private ListenableFuture<Boolean> isEmptyList(List<EntityRelation> entityRelations) {
if (entityRelations.isEmpty()) {
return Futures.immediateFuture(false);
} else {
return Futures.immediateFuture(true);
}
}
@Override

6
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbCheckRelationNodeConfiguration.java

@ -18,10 +18,6 @@ package org.thingsboard.rule.engine.filter;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import org.thingsboard.server.common.data.relation.EntitySearchDirection;
import org.thingsboard.server.common.msg.session.SessionMsgType;
import java.util.Arrays;
import java.util.List;
/**
* Created by ashvayka on 19.01.18.
@ -33,12 +29,14 @@ public class TbCheckRelationNodeConfiguration implements NodeConfiguration<TbChe
private String entityId;
private String entityType;
private String relationType;
private boolean checkForSingleEntity;
@Override
public TbCheckRelationNodeConfiguration defaultConfiguration() {
TbCheckRelationNodeConfiguration configuration = new TbCheckRelationNodeConfiguration();
configuration.setDirection(EntitySearchDirection.FROM.name());
configuration.setRelationType("Contains");
configuration.setCheckForSingleEntity(true);
return configuration;
}
}

111
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java

@ -21,8 +21,15 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.*;
import org.apache.commons.lang3.math.NumberUtils;
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.DonAsynchron;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
@ -37,7 +44,9 @@ import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.*;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_FIRST;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE;
import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
/**
@ -50,6 +59,7 @@ import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
nodeDescription = "Add Message Originator Telemetry for selected time range into Message Metadata\n",
nodeDetails = "The node allows you to select fetch mode <b>FIRST/LAST/ALL</b> to fetch telemetry of certain time range that are added into Message metadata without any prefix. " +
"If selected fetch mode <b>ALL</b> Telemetry will be added like array into Message Metadata where <b>key</b> is Timestamp and <b>value</b> is value of Telemetry. " +
"<b>Note</b>: The maximum size of the fetched array is 1000 records. " +
"If selected fetch mode <b>FIRST</b> or <b>LAST</b> Telemetry will be added like string without Timestamp",
uiResources = {"static/rulenode/rulenode-core-config.js"},
configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase")
@ -57,8 +67,6 @@ public class TbGetTelemetryNode implements TbNode {
private TbGetTelemetryNodeConfiguration config;
private List<String> tsKeyNames;
private long startTsOffset;
private long endTsOffset;
private int limit;
private ObjectMapper mapper;
private String fetchMode;
@ -67,8 +75,6 @@ public class TbGetTelemetryNode implements TbNode {
public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
this.config = TbNodeUtils.convert(configuration, TbGetTelemetryNodeConfiguration.class);
tsKeyNames = config.getLatestTsKeyNames();
startTsOffset = TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval());
endTsOffset = TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval());
limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1;
fetchMode = config.getFetchMode();
mapper = new ObjectMapper();
@ -82,8 +88,10 @@ public class TbGetTelemetryNode implements TbNode {
ctx.tellFailure(msg, new IllegalStateException("Telemetry is not selected!"));
} else {
try {
List<ReadTsKvQuery> queries = buildQueries();
ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), queries);
if (config.isUseMetadataIntervalPatterns()) {
checkMetadataKeyPatterns(msg);
}
ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(ctx.getTenantId(), msg.getOriginator(), buildQueries(msg));
DonAsynchron.withCallback(list, data -> {
process(data, msg);
TbMsg newMsg = ctx.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
@ -95,10 +103,12 @@ public class TbGetTelemetryNode implements TbNode {
}
}
private List<ReadTsKvQuery> buildQueries() {
long ts = System.currentTimeMillis();
long startTs = ts - startTsOffset;
long endTs = ts - endTsOffset;
@Override
public void destroy() {
}
private List<ReadTsKvQuery> buildQueries(TbMsg msg) {
String orderBy;
if (fetchMode.equals(FETCH_MODE_FIRST) || fetchMode.equals(FETCH_MODE_ALL)) {
orderBy = "ASC";
@ -106,7 +116,7 @@ public class TbGetTelemetryNode implements TbNode {
orderBy = "DESC";
}
return tsKeyNames.stream()
.map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE, orderBy))
.map(key -> new BaseReadTsKvQuery(key, getInterval(msg).getStartTs(), getInterval(msg).getEndTs(), 1, limit, NONE, orderBy))
.collect(Collectors.toList());
}
@ -162,8 +172,79 @@ public class TbGetTelemetryNode implements TbNode {
return obj;
}
@Override
public void destroy() {
private Interval getInterval(TbMsg msg) {
Interval interval = new Interval();
if (config.isUseMetadataIntervalPatterns()) {
if (isParsable(msg, config.getStartIntervalPattern())) {
interval.setStartTs(Long.parseLong(TbNodeUtils.processPattern(config.getStartIntervalPattern(), msg.getMetaData())));
}
if (isParsable(msg, config.getEndIntervalPattern())) {
interval.setEndTs(Long.parseLong(TbNodeUtils.processPattern(config.getEndIntervalPattern(), msg.getMetaData())));
}
} else {
long ts = System.currentTimeMillis();
interval.setStartTs(ts - TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval()));
interval.setEndTs(ts - TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval()));
}
return interval;
}
private boolean isParsable(TbMsg msg, String pattern) {
return NumberUtils.isParsable(TbNodeUtils.processPattern(pattern, msg.getMetaData()));
}
private void checkMetadataKeyPatterns(TbMsg msg) {
isUndefined(msg, config.getStartIntervalPattern(), config.getEndIntervalPattern());
isInvalid(msg, config.getStartIntervalPattern(), config.getEndIntervalPattern());
}
private void isUndefined(TbMsg msg, String startIntervalPattern, String endIntervalPattern) {
if (getMetadataValue(msg, startIntervalPattern) == null && getMetadataValue(msg, endIntervalPattern) == null) {
throw new IllegalArgumentException("Message metadata values: '" +
replaceRegex(startIntervalPattern) + "' and '" +
replaceRegex(endIntervalPattern) + "' are undefined");
} else {
if (getMetadataValue(msg, startIntervalPattern) == null) {
throw new IllegalArgumentException("Message metadata value: '" +
replaceRegex(startIntervalPattern) + "' is undefined");
}
if (getMetadataValue(msg, endIntervalPattern) == null) {
throw new IllegalArgumentException("Message metadata value: '" +
replaceRegex(endIntervalPattern) + "' is undefined");
}
}
}
private void isInvalid(TbMsg msg, String startIntervalPattern, String endIntervalPattern) {
if (getInterval(msg).getStartTs() == null && getInterval(msg).getEndTs() == null) {
throw new IllegalArgumentException("Message metadata values: '" +
replaceRegex(startIntervalPattern) + "' and '" +
replaceRegex(endIntervalPattern) + "' have invalid format");
} else {
if (getInterval(msg).getStartTs() == null) {
throw new IllegalArgumentException("Message metadata value: '" +
replaceRegex(startIntervalPattern) + "' has invalid format");
}
if (getInterval(msg).getEndTs() == null) {
throw new IllegalArgumentException("Message metadata value: '" +
replaceRegex(endIntervalPattern) + "' has invalid format");
}
}
}
private String getMetadataValue(TbMsg msg, String pattern) {
return msg.getMetaData().getValue(replaceRegex(pattern));
}
private String replaceRegex(String pattern) {
return pattern.replaceAll("[${}]", "");
}
@Data
@NoArgsConstructor
private static class Interval {
private Long startTs;
private Long endTs;
}
}

9
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java

@ -35,6 +35,12 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetT
private int startInterval;
private int endInterval;
private String startIntervalPattern;
private String endIntervalPattern;
private boolean useMetadataIntervalPatterns;
private String startIntervalTimeUnit;
private String endIntervalTimeUnit;
private String fetchMode; //FIRST, LAST, LATEST
@ -52,6 +58,9 @@ public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetT
configuration.setStartInterval(2);
configuration.setEndIntervalTimeUnit(TimeUnit.MINUTES.name());
configuration.setEndInterval(1);
configuration.setUseMetadataIntervalPatterns(false);
configuration.setStartIntervalPattern("");
configuration.setEndIntervalPattern("");
return configuration;
}
}

8
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

1
tools/pom.xml

@ -23,7 +23,6 @@
<version>2.2.1-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
<artifactId>tools</artifactId>
<packaging>jar</packaging>

53
tools/src/main/java/org/thingsboard/client/tools/RestClient.java

@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.common.data.security.DeviceCredentialsType;
import java.io.IOException;
import java.util.Collections;
@ -107,6 +108,27 @@ public class RestClient implements ClientHttpRequestInterceptor {
}
}
public Optional<JsonNode> getAttributes(String accessToken, String clientKeys, String sharedKeys) {
Map<String, String> params = new HashMap<>();
params.put("accessToken", accessToken);
params.put("clientKeys", clientKeys);
params.put("sharedKeys", sharedKeys);
try {
ResponseEntity<JsonNode> telemetryEntity = restTemplate.getForEntity(baseURL + "/api/v1/{accessToken}/attributes?clientKeys={clientKeys}&sharedKeys={sharedKeys}", JsonNode.class, params);
return Optional.of(telemetryEntity.getBody());
} catch (HttpClientErrorException exception) {
if (exception.getStatusCode() == HttpStatus.NOT_FOUND) {
return Optional.empty();
} else {
throw exception;
}
}
}
public Customer createCustomer(Customer customer) {
return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody();
}
public Customer createCustomer(String title) {
Customer customer = new Customer();
customer.setTitle(title);
@ -120,6 +142,25 @@ public class RestClient implements ClientHttpRequestInterceptor {
return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody();
}
public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) {
DeviceCredentials deviceCredentials = getCredentials(deviceId);
deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN);
deviceCredentials.setCredentialsId(token);
return saveDeviceCredentials(deviceCredentials);
}
public DeviceCredentials saveDeviceCredentials(DeviceCredentials deviceCredentials) {
return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody();
}
public Device createDevice(Device device) {
return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody();
}
public Asset createAsset(Asset asset) {
return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody();
}
public Asset createAsset(String name, String type) {
Asset asset = new Asset();
asset.setName(name);
@ -131,6 +172,18 @@ public class RestClient implements ClientHttpRequestInterceptor {
return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody();
}
public void deleteCustomer(CustomerId customerId) {
restTemplate.delete(baseURL + "/api/customer/{customerId}", customerId);
}
public void deleteDevice(DeviceId deviceId) {
restTemplate.delete(baseURL + "/api/device/{deviceId}", deviceId);
}
public void deleteAsset(AssetId assetId) {
restTemplate.delete(baseURL + "/api/asset/{assetId}", assetId);
}
public Device assignDevice(CustomerId customerId, DeviceId deviceId) {
return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class,
customerId.toString(), deviceId.toString()).getBody();

6
ui/src/app/api/subscription.js

@ -648,6 +648,12 @@ export default class Subscription {
}
dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) {
for (var x = 0; x < this.datasourceListeners.length; x++) {
this.datasources[x].dataReceived = this.datasources[x].dataReceived === true;
if (this.datasourceListeners[x].datasourceIndex === datasourceIndex && sourceData.data.length > 0) {
this.datasources[x].dataReceived = true;
}
}
this.notifyDataLoaded();
var update = true;
var currentData;

1
ui/src/app/entity/entity-subtype-autocomplete.directive.js

@ -62,6 +62,7 @@ export default function EntitySubtypeAutocomplete($compile, $templateCache, $q,
}
scope.subTypeSearchTextChanged = function() {
scope.subType = scope.subTypeSearchText;
}
scope.updateView = function () {

2
ui/src/app/entity/entity-subtype-autocomplete.tpl.html

@ -28,7 +28,7 @@
md-min-length="0"
placeholder="{{ selectEntitySubtypeText | translate }}"
md-floating-label="{{ entitySubtypeText | translate }}"
md-select-on-match="true"
md-select-on-match="false"
md-menu-class="tb-entity-subtype-autocomplete">
<md-item-template>
<div class="tb-entity-subtype-item">

45
ui/src/app/extension/extensions-forms/extension-form-modbus.directive.js

@ -31,17 +31,38 @@ export default function ExtensionFormModbusDirective($compile, $templateCache, $
var linker = function(scope, element) {
function TcpTransport() {
this.type = "tcp",
this.host = "localhost",
this.port = 502,
this.timeout = 5000,
this.reconnect = true,
this.rtuOverTcp = false
}
function UdpTransport() {
this.type = "udp",
this.host = "localhost",
this.port = 502,
this.timeout = 5000
}
function RtuTransport() {
this.type = "rtu",
this.portName = "COM1",
this.encoding = "ascii",
this.timeout = 5000,
this.baudRate = 115200,
this.dataBits = 7,
this.stopBits = 1,
this.parity ="even"
}
function Server() {
this.transport = {
"type": "tcp",
"host": "localhost",
"port": 502,
"timeout": 3000
};
this.transport = new TcpTransport();
this.devices = []
}
function Device() {
this.unitId = 1;
this.deviceName = "";
@ -105,9 +126,13 @@ export default function ExtensionFormModbusDirective($compile, $templateCache, $
scope.onTransportChanged = function(server) {
var type = server.transport.type;
server.transport = {};
server.transport.type = type;
server.transport.timeout = 3000;
if (type === "tcp") {
server.transport = new TcpTransport();
} else if (type === "udp") {
server.transport = new UdpTransport();
} else if (type === "rtu") {
server.transport = new RtuTransport();
}
scope.theForm.$setDirty();
};

104
ui/src/app/extension/extensions-forms/extension-form-modbus.tpl.html

@ -73,49 +73,69 @@
</div>
<div layout="row" ng-if="server.transport.type == 'tcp' || server.transport.type == 'udp'">
<md-input-container flex="33" class="md-block">
<label translate>extension.host</label>
<input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
<div ng-messages="theForm['transportHost_' + serverIndex].$error">
<div translate ng-message="required">extension.field-required</div>
</div>
</md-input-container>
<div ng-if="server.transport.type == 'tcp' || server.transport.type == 'udp'">
<div layout="row">
<md-input-container flex="33" class="md-block">
<label translate>extension.host</label>
<input required name="transportHost_{{serverIndex}}" ng-model="server.transport.host">
<div ng-messages="theForm['transportHost_' + serverIndex].$error">
<div translate ng-message="required">extension.field-required</div>
</div>
</md-input-container>
<md-input-container flex="33" class="md-block">
<label translate>extension.port</label>
<input type="number"
required
name="transportPort_{{serverIndex}}"
ng-model="server.transport.port"
min="1"
max="65535"
>
<div ng-messages="theForm['transportPort_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.field-required</div>
<div translate
ng-message="min"
>extension.port-range</div>
<div translate
ng-message="max"
>extension.port-range</div>
</div>
</md-input-container>
<md-input-container flex="33" class="md-block">
<label translate>extension.timeout</label>
<input type="number"
required name="transportTimeout_{{serverIndex}}"
ng-model="server.transport.timeout"
>
<div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.field-required</div>
</div>
</md-input-container>
<md-input-container flex="33" class="md-block">
<label translate>extension.port</label>
<input type="number"
required
name="transportPort_{{serverIndex}}"
ng-model="server.transport.port"
min="1"
max="65535"
>
<div ng-messages="theForm['transportPort_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.field-required</div>
<div translate
ng-message="min"
>extension.port-range</div>
<div translate
ng-message="max"
>extension.port-range</div>
</div>
</md-input-container>
<md-input-container flex="33" class="md-block">
<label translate>extension.timeout</label>
<input type="number"
required name="transportTimeout_{{serverIndex}}"
ng-model="server.transport.timeout"
>
<div ng-messages="theForm['transportTimeout_' + serverIndex].$error">
<div translate
ng-message="required"
>extension.field-required</div>
</div>
</md-input-container>
</div>
<div layout="row" ng-if="server.transport.type == 'tcp'">
<md-input-container flex="50" class="md-block">
<md-checkbox aria-label="{{ 'extension.modbus-tcp-reconnect' | translate }}"
ng-checked="server.transport.reconnect"
name="transportTcpReconnect_{{serverIndex}}"
ng-model="server.transport.reconnect">{{ 'extension.modbus-tcp-reconnect' | translate }}
</md-checkbox>
</md-input-container>
<md-input-container flex="50" class="md-block">
<md-checkbox aria-label="{{ 'extension.modbus-rtu-over-tcp' | translate }}"
ng-checked="server.transport.rtuOverTcp"
name="transportRtuOverTcp_{{serverIndex}}"
ng-model="server.transport.rtuOverTcp">{{ 'extension.modbus-rtu-over-tcp' | translate }}
</md-checkbox>
</md-input-container>
</div>
</div>
<div ng-if="server.transport.type == 'rtu'">

6
ui/src/app/extension/extensions-forms/extension-form-mqtt.tpl.html

@ -241,14 +241,14 @@
</md-input-container>
<md-input-container ng-if="map.converter.typeExp == 'deviceTypeJsonExpression'" flex="60" class="md-block" md-is-error="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$touched && theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$invalid">
<label translate>extension.json-type-expression</label>
<input required name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression">
<input name="mqttJsonTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeJsonExpression">
<div ng-messages="theForm['mqttJsonTypeExp_' + brokerIndex + mapIndex].$error">
<div translate ng-message="required">extension.field-required</div>
</div>
</md-input-container>
<md-input-container ng-if="map.converter.typeExp == 'deviceTypeTopicExpression'" flex="60" class="md-block" md-is-error="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$touched && theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$invalid">
<label translate>extension.topic-type-expression</label>
<input required name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression">
<input name="mqttTopicTypeExp_{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.deviceTypeTopicExpression">
<div ng-messages="theForm['mqttTopicTypeExp_' + brokerIndex + mapIndex].$error">
<div translate ng-message="required">extension.field-required</div>
</div>
@ -261,7 +261,7 @@
</md-input-container>
<md-input-container flex="60" class="md-block" md-is-error="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$touched && theForm['mqttFilterExpression' + brokerIndex + mapIndex].$invalid">
<label translate>extension.filter-expression</label>
<input required name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
<input name="mqttFilterExpression{{brokerIndex}}{{mapIndex}}" ng-model="map.converter.filterExpression">
<div ng-messages="theForm['mqttFilterExpression' + brokerIndex + mapIndex].$error">
<div translate ng-message="required">extension.field-required</div>
</div>

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

File diff suppressed because it is too large

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

@ -1008,6 +1008,8 @@
"modbus-add-server": "Add server/slave",
"modbus-add-server-prompt": "Please add server/slave",
"modbus-transport": "Transport",
"modbus-tcp-reconnect": "Automatically reconnect",
"modbus-rtu-over-tcp": "RTU over TCP",
"modbus-port-name": "Serial port name",
"modbus-encoding": "Encoding",
"modbus-parity": "Parity",
@ -1032,7 +1034,6 @@
"modbus-register-count": "Register count",
"modbus-register-count-range": "Register count should be a positive value.",
"modbus-byte-order": "Byte order",
"sync": {
"status": "Status",
"sync": "Sync",
@ -1040,7 +1041,6 @@
"last-sync-time": "Last sync time",
"not-available": "Not available"
},
"export-extensions-configuration": "Export extensions configuration",
"import-extensions-configuration": "Import extensions configuration",
"import-extensions": "Import extensions",
@ -1566,7 +1566,8 @@
"language": {
"language": "Language",
"locales": {
"fr_FR": "French",
"de_DE": "German",
"fr_FR": "French",
"zh_CN": "Chinese",
"en_US": "English",
"it_IT": "Italian",
@ -1577,4 +1578,4 @@
"tr_TR": "Turkish"
}
}
}
}

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

File diff suppressed because it is too large

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

@ -1003,6 +1003,7 @@
"language": {
"language": "Language",
"locales": {
"de_DE": "Allemand",
"en_US": "Anglais",
"fr_FR": "Français",
"es_ES": "Espagnol",
@ -1459,4 +1460,4 @@
"widgets-bundle-required": "Un groupe de widgets est requis.",
"widgets-bundles": "Groupes de widgets"
}
}
}

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

@ -100,7 +100,7 @@
"UNACK": "Non riconosciuto"
},
"display-status": {
"ACTIVE_UNACK": "Active Unacknowledged",
"ACTIVE_UNACK": "Active Unacknowledged",
"ACTIVE_ACK": "Active Acknowledged",
"CLEARED_UNACK": "Cleared Unacknowledged",
"CLEARED_ACK": "Cleared Acknowledged"
@ -109,8 +109,8 @@
"created-time": "Orario di creazione",
"type": "Tipo",
"severity": "Livello di gravità",
"originator": "Origine",
"originator-type": "Tipo origine",
"originator": "Origine",
"originator-type": "Tipo origine",
"details": "Dettagli",
"status": "Stato",
"alarm-details": "Dettagli allarme",
@ -132,9 +132,9 @@
"polling-interval-required": "Intervallo di polling Allarmi richiesto.",
"min-polling-interval-message": "L'intervallo di polling deve essere di almeno 1 sec.",
"aknowledge-alarms-title": "Conferma { count, plural, 1 {1 allarme} other {# allarmi} }",
"aknowledge-alarms-text": "Sei sicuro di voler confermare { count, plural, 1 {1 allarme} other {# allarmi} }?",
"aknowledge-alarms-text": "Sei sicuro di voler confermare { count, plural, 1 {1 allarme} other {# allarmi} }?",
"clear-alarms-title": "Elimina { count, plural, 1 {1 allarme} other {# allarmi} }",
"clear-alarms-text": "Sei sicuro di voler eliminare { count, plural, 1 {1 allarme} other {# allarmi} }?"
"clear-alarms-text": "Sei sicuro di voler eliminare { count, plural, 1 {1 allarme} other {# allarmi} }?"
},
"alias": {
"add": "Aggiungi alias",
@ -910,7 +910,6 @@
"modbus-register-count": "Register count",
"modbus-register-count-range": "Register count should be a positive value.",
"modbus-byte-order": "Byte order",
"sync": {
"status": "Stato",
"sync": "Sincronizzato",
@ -918,7 +917,6 @@
"last-sync-time": "Ultima sincronizzazione",
"not-available": "Non disponibile"
},
"export-extensions-configuration": "Esporta configurazione estensioni",
"import-extensions-configuration": "Importa configurazione estensioni",
"import-extensions": "Importa estensione",
@ -1414,7 +1412,7 @@
"export": "Esporta un tipo di widget",
"export-failed-error": "Impossibile esportare il tipo di widget: {{error}}",
"create-new-widget-type": "Crea un nuovo tipo di widget",
"widget-type-file": "Widget type file",
"widget-type-file": "Widget type file",
"invalid-widget-type-file-error": "Impossibile importare un tipo di widget: struttura dati del widget non valida."
},
"icon": {
@ -1424,7 +1422,7 @@
"show-all": "Mostra tutte le icone"
},
"custom": {
"widget-action": {
"widget-action": {
"action-cell-button": "Action cell button",
"row-click": "On row click",
"marker-click": "On marker click",
@ -1434,7 +1432,8 @@
"language": {
"language": "Lingua",
"locales": {
"fr_FR": "Francese",
"de_DE": "Tedesco",
"fr_FR": "Francese",
"zh_CN": "Cinese",
"ko_KR": "Coreano",
"en_US": "Inglese",
@ -1445,4 +1444,4 @@
"tr_TR": "Turco"
}
}
}
}

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

@ -288,11 +288,11 @@
"type-suspended": "一時停止中",
"type-credentials-read": "信用証明書を読む",
"type-attributes-read": "読み取られた属性",
"type-relation-add-or-update": "関係が更新されました",
"type-relation-delete": "関係が削除されました",
"type-relations-delete": "すべてのリレーションを削除",
"type-alarm-ack": "承認された",
"type-alarm-clear": "クリアされた",
"type-relation-add-or-update": "関係が更新されました",
"type-relation-delete": "関係が削除されました",
"type-relations-delete": "すべてのリレーションを削除",
"type-alarm-ack": "承認された",
"type-alarm-clear": "クリアされた",
"status-success": "成功",
"status-failure": "失敗",
"audit-log-details": "監査ログの詳細",
@ -322,7 +322,7 @@
"common": {
"username": "ユーザー名",
"password": "パスワード",
"enter-username": "ユーザーネームを入力してください",
"enter-username": "ユーザーネームを入力してください",
"enter-password": "パスワードを入力する",
"enter-search": "検索を入力"
},
@ -919,7 +919,6 @@
"modbus-register-count": "レジスタ数",
"modbus-register-count-range": "レジスタ数は正の値でなければなりません。",
"modbus-byte-order": "バイト順",
"sync": {
"status": "状態",
"sync": "同期",
@ -927,7 +926,6 @@
"last-sync-time": "前回の同期時間",
"not-available": "利用不可"
},
"export-extensions-configuration": "エクステンション設定のエクスポート",
"import-extensions-configuration": "エクステンション設定のインポート",
"import-extensions": "拡張機能のインポート",
@ -1417,7 +1415,7 @@
"action-source-required": "アクションソースが必要です。",
"action-name": "名",
"action-name-required": "アクション名は必須です。",
"action-name-not-unique": "同じ名前の別のアクションがすでに存在します。<br/>アクション名は、同じアクションソース内で一意である必要があります。",
"action-name-not-unique": "同じ名前の別のアクションがすでに存在します。<br/>アクション名は、同じアクションソース内で一意である必要があります。",
"action-icon": "アイコン",
"action-type": "タイプ",
"action-type-required": "アクションタイプが必要です。",
@ -1427,7 +1425,7 @@
"delete-action-text": "'{{actionName}}'?"
},
"widget-type": {
"import": "インポートウィジェットタイプ",
"import": "インポートウィジェットタイプ",
"export": "ウィジェットのタイプをエクスポートする",
"export-failed-error": "{{error}}",
"create-new-widget-type": "新しいウィジェットタイプを作成する",
@ -1451,6 +1449,7 @@
"language": {
"language": "言語",
"locales": {
"de_DE": "ドイツ語",
"en_US": "英語",
"ko_KR": "韓国語",
"it_IT": "イタリアの",
@ -1461,4 +1460,4 @@
"tr_TR": "トルコ語"
}
}
}
}

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

@ -85,7 +85,6 @@
"enable-tls": "TLS 사용",
"send-test-mail": "테스트 메일 보내기"
},
"alarm": {
"alarm": "Alarm",
"alarms": "Alarms",
@ -831,7 +830,6 @@
"modbus-register-count": "Register count",
"modbus-register-count-range": "Register count should be a positive value.",
"modbus-byte-order": "Byte order",
"sync": {
"status": "Status",
"sync": "Sync",
@ -839,7 +837,6 @@
"last-sync-time": "Last sync time",
"not-available": "Not available"
},
"export-extensions-configuration": "Export extensions configuration",
"import-extensions-configuration": "Import extensions configuration",
"import-extensions": "Import extensions",
@ -1328,8 +1325,9 @@
"language": {
"language": "언어",
"locales": {
"de_DE": "독일어",
"en_US": "영어",
"fr_FR": "프랑스의",
"fr_FR": "프랑스의",
"ko_KR": "한글",
"zh_CN": "중국어",
"ru_RU": "러시아어",
@ -1339,4 +1337,4 @@
"tr_TR": "터키어"
}
}
}
}

1533
ui/src/app/locale/locale.constant-ru_RU.json

File diff suppressed because it is too large

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

@ -1175,7 +1175,7 @@
"delete-rulechains-action-title": "{ count, plural, 1 {1 kuralı} other {# kuralı} } sil",
"delete-rulechains-text": "UYARI: Onaylandıktan sonra seçili tüm kurallar ve ilişkili tüm veriler geri yüklenemez şekilde silinecektir.",
"add-rulechain-text": "Yeni kural ekle",
"no-rulechains-text":"Hiçbir kural bulunamadı",
"no-rulechains-text": "Hiçbir kural bulunamadı",
"rulechain-details": "Kural detayları",
"details": "Detaylar",
"events": "Olaylar",
@ -1531,6 +1531,7 @@
"language": {
"language": "Dil",
"locales": {
"de_DE": "Almanca",
"fr_FR": "Fransızca",
"zh_CN": "Çince",
"en_US": "İngilizce",

7
ui/src/app/locale/locale.constant-zh_CN.json

@ -916,7 +916,6 @@
"last-sync-time": "最后同步时间",
"not-available": "无法使用"
},
"export-extensions-configuration": "导出扩展配置",
"import-extensions-configuration": "导入扩展配置",
"import-extensions": "导入扩展",
@ -1299,7 +1298,6 @@
"saveAs": "部件另存为",
"save-widget-type-as": "部件类型另存为",
"save-widget-type-as-text": "请输入新的部件标题或选择目标部件包",
"toggle-fullscreen": "切换全屏",
"run": "运行部件",
"title": "部件标题",
@ -1437,8 +1435,9 @@
"language": {
"language": "语言",
"locales": {
"de_DE": "德语",
"en_US": "英语",
"fr_FR": "法国",
"fr_FR": "法国",
"ko_KR": "韩语",
"zh_CN": "汉语",
"ru_RU": "俄语",
@ -1448,4 +1447,4 @@
"tr_TR": "土耳其"
}
}
}
}

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

@ -314,6 +314,48 @@ export default class TbGoogleMap {
polyline.setMap(null);
}
createPolygon(latLangs, settings) {
let polygon = new google.maps.Polygon({
map: this.map,
paths: latLangs,
strokeColor: settings.polygonStrokeColor,
strokeOpacity: settings.polygonStrokeColor,
fillColor: settings.polygonColor,
fillOpacity: settings.polygonOpacity,
strokeWeight: settings.polygonStrokeWeight
});
return polygon;
}
/* eslint-disable no-undef */
removePolygon (polygon) {
polygon.setMap(null);
}
/* eslint-disable no-undef,no-unused-vars */
updatePolygonColor (polygon, settings, color) {
let options = {
paths: polygon.getPaths(),
map: this.map,
strokeColor: color,
fillColor: color,
strokeWeight: settings.polygonStrokeWeight
}
}
/* eslint-disable no-undef ,no-unused-vars*/
getPolygonLatLngs(polygon) {
return polygon.getPaths().getArray();
}
setPolygonLatLngs(polygon, latLngs) {
polygon.setPaths(latLngs);
}
/* eslint-disable no-undef */
fitBounds(bounds) {
if (this.dontFitMapBounds && this.defaultZoomLevel) {

15
ui/src/app/widget/lib/image-map.js

@ -369,6 +369,21 @@ export default class TbImageMap {
removePolyline(/*polyline*/) {
}
createPolygon(/*latLangs, settings*/) {
}
removePolygon(/*latLangs, settings*/) {
}
updatePolygonColor(/*latLangs, settings*/) {
}
getPolygonLatLngs(/*latLangs, settings*/) {
}
setPolygonLatLngs(/*latLangs, settings*/) {
}
fitBounds() {
}

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

File diff suppressed because it is too large

494
ui/src/app/widget/lib/openstreet-map.js

@ -19,234 +19,270 @@ import 'leaflet-providers';
export default class TbOpenStreetMap {
constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, mapProvider) {
this.utils = utils;
this.defaultZoomLevel = defaultZoomLevel;
this.dontFitMapBounds = dontFitMapBounds;
this.minZoomLevel = minZoomLevel;
this.tooltips = [];
if (!mapProvider) {
mapProvider = "OpenStreetMap.Mapnik";
}
this.map = L.map($containerElement[0]).setView([0, 0], this.defaultZoomLevel || 8);
var tileLayer = L.tileLayer.provider(mapProvider);
tileLayer.addTo(this.map);
if (initCallback) {
setTimeout(initCallback, 0); //eslint-disable-line
}
}
inited() {
return angular.isDefined(this.map);
}
updateMarkerLabel(marker, settings) {
marker.unbindTooltip();
marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
}
updateMarkerColor(marker, color) {
this.createDefaultMarkerIcon(marker, color, (iconInfo) => {
marker.setIcon(iconInfo.icon);
});
}
updateMarkerIcon(marker, settings) {
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
if (settings.showLabel) {
marker.unbindTooltip();
marker.tooltipOffset = [0, -iconInfo.size[1] + 10];
marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
}
});
}
createMarkerIcon(marker, settings, onMarkerIconReady) {
var currentImage = settings.currentImage;
var opMap = this;
if (currentImage && currentImage.url) {
this.utils.loadImageAspect(currentImage.url).then(
(aspect) => {
if (aspect) {
var width;
var height;
if (aspect > 1) {
width = currentImage.size;
height = currentImage.size / aspect;
} else {
width = currentImage.size * aspect;
height = currentImage.size;
}
var icon = L.icon({
iconUrl: currentImage.url,
iconSize: [width, height],
iconAnchor: [width/2, height],
popupAnchor: [0, -height]
});
var iconInfo = {
size: [width, height],
icon: icon
};
onMarkerIconReady(iconInfo);
} else {
opMap.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
);
} else {
this.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
createDefaultMarkerIcon(marker, color, onMarkerIconReady) {
var pinColor = color.substr(1);
var icon = L.icon({
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
iconSize: [21, 34],
iconAnchor: [10, 34],
popupAnchor: [0, -34],
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
shadowSize: [40, 37],
shadowAnchor: [12, 35]
});
var iconInfo = {
size: [21, 34],
icon: icon
};
onMarkerIconReady(iconInfo);
}
createMarker(location, dsIndex, settings, onClickListener, markerArgs) {
var marker = L.marker(location, {});
var opMap = this;
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
if (settings.showLabel) {
marker.tooltipOffset = [0, -iconInfo.size[1] + 10];
marker.bindTooltip('<div style="color: '+ settings.labelColor +';"><b>'+settings.labelText+'</b></div>',
{ className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset });
}
marker.addTo(opMap.map);
});
if (settings.displayTooltip) {
this.createTooltip(marker, dsIndex, settings, markerArgs);
}
if (onClickListener) {
marker.on('click', onClickListener);
}
return marker;
}
removeMarker(marker) {
this.map.removeLayer(marker);
}
createTooltip(marker, dsIndex, settings, markerArgs) {
var popup = L.popup();
popup.setContent('');
marker.bindPopup(popup, {autoClose: settings.autocloseTooltip, closeOnClick: false});
this.tooltips.push( {
markerArgs: markerArgs,
popup: popup,
locationSettings: settings,
dsIndex: dsIndex
});
}
updatePolylineColor(polyline, settings, color) {
var style = {
color: color,
opacity: settings.strokeOpacity,
weight: settings.strokeWeight
};
polyline.setStyle(style);
}
createPolyline(locations, settings) {
var polyline = L.polyline(locations,
{
color: settings.color,
opacity: settings.strokeOpacity,
weight: settings.strokeWeight
}
).addTo(this.map);
return polyline;
}
removePolyline(polyline) {
this.map.removeLayer(polyline);
}
fitBounds(bounds) {
if (bounds.isValid()) {
if (this.dontFitMapBounds && this.defaultZoomLevel) {
this.map.setZoom(this.defaultZoomLevel, {animate: false});
this.map.panTo(bounds.getCenter(), {animate: false});
} else {
var tbMap = this;
this.map.once('zoomend', function() {
if (!tbMap.defaultZoomLevel && tbMap.map.getZoom() > tbMap.minZoomLevel) {
tbMap.map.setZoom(tbMap.minZoomLevel, {animate: false});
}
});
this.map.fitBounds(bounds, {padding: [50, 50], animate: false});
}
}
}
createLatLng(lat, lng) {
return L.latLng(lat, lng);
}
extendBoundsWithMarker(bounds, marker) {
bounds.extend(marker.getLatLng());
}
getMarkerPosition(marker) {
return marker.getLatLng();
}
setMarkerPosition(marker, latLng) {
marker.setLatLng(latLng);
}
getPolylineLatLngs(polyline) {
return polyline.getLatLngs();
}
setPolylineLatLngs(polyline, latLngs) {
polyline.setLatLngs(latLngs);
}
createBounds() {
return L.latLngBounds();
}
extendBounds(bounds, polyline) {
if (polyline && polyline.getLatLngs()) {
bounds.extend(polyline.getBounds());
}
}
invalidateSize() {
this.map.invalidateSize(true);
}
getTooltips() {
return this.tooltips;
}
constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, mapProvider) {
this.utils = utils;
this.defaultZoomLevel = defaultZoomLevel;
this.dontFitMapBounds = dontFitMapBounds;
this.minZoomLevel = minZoomLevel;
this.tooltips = [];
if (!mapProvider) {
mapProvider = "OpenStreetMap.Mapnik";
}
this.map = L.map($containerElement[0]).setView([0, 0], this.defaultZoomLevel || 8);
var tileLayer = L.tileLayer.provider(mapProvider);
tileLayer.addTo(this.map);
if (initCallback) {
setTimeout(initCallback, 0); //eslint-disable-line
}
}
inited() {
return angular.isDefined(this.map);
}
updateMarkerLabel(marker, settings) {
marker.unbindTooltip();
marker.bindTooltip('<div style="color: ' + settings.labelColor + ';"><b>' + settings.labelText + '</b></div>',
{className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset});
}
updateMarkerColor(marker, color) {
this.createDefaultMarkerIcon(marker, color, (iconInfo) => {
marker.setIcon(iconInfo.icon);
});
}
updateMarkerIcon(marker, settings) {
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
if (settings.showLabel) {
marker.unbindTooltip();
marker.tooltipOffset = [0, -iconInfo.size[1] + 10];
marker.bindTooltip('<div style="color: ' + settings.labelColor + ';"><b>' + settings.labelText + '</b></div>',
{className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset});
}
});
}
createMarkerIcon(marker, settings, onMarkerIconReady) {
var currentImage = settings.currentImage;
var opMap = this;
if (currentImage && currentImage.url) {
this.utils.loadImageAspect(currentImage.url).then(
(aspect) => {
if (aspect) {
var width;
var height;
if (aspect > 1) {
width = currentImage.size;
height = currentImage.size / aspect;
} else {
width = currentImage.size * aspect;
height = currentImage.size;
}
var icon = L.icon({
iconUrl: currentImage.url,
iconSize: [width, height],
iconAnchor: [width / 2, height],
popupAnchor: [0, -height]
});
var iconInfo = {
size: [width, height],
icon: icon
};
onMarkerIconReady(iconInfo);
} else {
opMap.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
);
} else {
this.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
createDefaultMarkerIcon(marker, color, onMarkerIconReady) {
var pinColor = color.substr(1);
var icon = L.icon({
iconUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_letter&chld=%E2%80%A2|' + pinColor,
iconSize: [21, 34],
iconAnchor: [10, 34],
popupAnchor: [0, -34],
shadowUrl: 'https://chart.apis.google.com/chart?chst=d_map_pin_shadow',
shadowSize: [40, 37],
shadowAnchor: [12, 35]
});
var iconInfo = {
size: [21, 34],
icon: icon
};
onMarkerIconReady(iconInfo);
}
createMarker(location, dsIndex, settings, onClickListener, markerArgs) {
var marker = L.marker(location, {});
var opMap = this;
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
if (settings.showLabel) {
marker.tooltipOffset = [0, -iconInfo.size[1] + 10];
marker.bindTooltip('<div style="color: ' + settings.labelColor + ';"><b>' + settings.labelText + '</b></div>',
{className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset});
}
marker.addTo(opMap.map);
});
if (settings.displayTooltip) {
this.createTooltip(marker, dsIndex, settings, markerArgs);
}
if (onClickListener) {
marker.on('click', onClickListener);
}
return marker;
}
removeMarker(marker) {
this.map.removeLayer(marker);
}
createTooltip(marker, dsIndex, settings, markerArgs) {
var popup = L.popup();
popup.setContent('');
marker.bindPopup(popup, {autoClose: settings.autocloseTooltip, closeOnClick: false});
this.tooltips.push({
markerArgs: markerArgs,
popup: popup,
locationSettings: settings,
dsIndex: dsIndex
});
}
updatePolylineColor(polyline, settings, color) {
var style = {
color: color,
opacity: settings.strokeOpacity,
weight: settings.strokeWeight
};
polyline.setStyle(style);
}
createPolyline(locations, settings) {
var polyline = L.polyline(locations,
{
color: settings.color,
opacity: settings.strokeOpacity,
weight: settings.strokeWeight
}
).addTo(this.map);
return polyline;
}
removePolyline(polyline) {
this.map.removeLayer(polyline);
}
createPolygon(latLangs, settings) {
let polygon = L.polygon(latLangs, {
fill: true,
fillColor: settings.polygonColor,
color: settings.polygonStrokeColor,
weight: settings.polygonStrokeWeight,
fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity
}).addTo(this.map);
return polygon;
}
removePolygon(polygon) {
this.map.removeLayer(polygon);
}
updatePolygonColor(polygon, settings, color) {
let style = {
fill: true,
fillColor: color,
color: color,
weight: settings.polygonStrokeWeight,
fillOpacity: settings.polygonOpacity,
opacity: settings.polygonStrokeOpacity
};
polygon.setStyle(style);
}
getPolygonLatLngs(polygon) {
return polygon.getLatLngs();
}
setPolygonLatLngs(polygon, latLngs) {
polygon.setLatLngs(latLngs);
}
fitBounds(bounds) {
if (bounds.isValid()) {
if (this.dontFitMapBounds && this.defaultZoomLevel) {
this.map.setZoom(this.defaultZoomLevel, {animate: false});
this.map.panTo(bounds.getCenter(), {animate: false});
} else {
var tbMap = this;
this.map.once('zoomend', function () {
if (!tbMap.defaultZoomLevel && tbMap.map.getZoom() > tbMap.minZoomLevel) {
tbMap.map.setZoom(tbMap.minZoomLevel, {animate: false});
}
});
this.map.fitBounds(bounds, {padding: [50, 50], animate: false});
}
}
}
createLatLng(lat, lng) {
return L.latLng(lat, lng);
}
extendBoundsWithMarker(bounds, marker) {
bounds.extend(marker.getLatLng());
}
getMarkerPosition(marker) {
return marker.getLatLng();
}
setMarkerPosition(marker, latLng) {
marker.setLatLng(latLng);
}
getPolylineLatLngs(polyline) {
return polyline.getLatLngs();
}
setPolylineLatLngs(polyline, latLngs) {
polyline.setLatLngs(latLngs);
}
createBounds() {
return L.latLngBounds();
}
extendBounds(bounds, polyline) {
if (polyline && polyline.getLatLngs()) {
bounds.extend(polyline.getBounds());
}
}
invalidateSize() {
this.map.invalidateSize(true);
}
getTooltips() {
return this.tooltips;
}
}

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

@ -14,377 +14,425 @@
* limitations under the License.
*/
var tmGlobals = {
loadingTmId: null,
tmApiKeys: {}
loadingTmId: null,
tmApiKeys: {}
}
export default class TbTencentMap {
constructor($containerElement,utils, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, tmApiKey, tmDefaultMapType) {
var tbMap = this;
this.utils = utils;
this.defaultZoomLevel = defaultZoomLevel;
this.dontFitMapBounds = dontFitMapBounds;
this.minZoomLevel = minZoomLevel;
this.tooltips = [];
this.defaultMapType = tmDefaultMapType;
function clearGlobalId() {
if (tmGlobals.loadingTmId && tmGlobals.loadingTmId === tbMap.mapId) {
tmGlobals.loadingTmId = null;
}
}
function displayError(message) {
$containerElement.html( // eslint-disable-line angular/angularelement
"<div class='error'>"+ message + "</div>"
);
}
function initTencentMap() {
tbMap.map = new qq.maps.Map($containerElement[0], { // eslint-disable-line no-undef
scrollwheel: true,
mapTypeId: getTencentMapTypeId(tbMap.defaultMapType),
zoom: tbMap.defaultZoomLevel || 8
});
if (initCallback) {
initCallback();
}
}
/* eslint-disable no-undef */
function getTencentMapTypeId(mapType) {
var mapTypeId =qq.maps.MapTypeId.ROADMAP;
if (mapType) {
if (mapType === 'hybrid') {
mapTypeId = qq.maps.MapTypeId.HYBRID;
} else if (mapType === 'satellite') {
mapTypeId = qq.maps.MapTypeId.SATELLITE;
} else if (mapType === 'terrain') {
mapTypeId = qq.maps.MapTypeId.ROADMAP;
}
}
return mapTypeId;
}
/* eslint-enable no-undef */
this.mapId = '' + Math.random().toString(36).substr(2, 9);
this.apiKey = tmApiKey || '84d6d83e0e51e481e50454ccbe8986b';
window.tm_authFailure = function() { // eslint-disable-line no-undef, angular/window-service
if (tmGlobals.loadingTmId && tmGlobals.loadingTmId === tbMap.mapId) {
tmGlobals.loadingTmId = null;
tmGlobals.tmApiKeys[tbMap.apiKey].error = 'Unable to authentificate for tencent Map API.</br>Please check your API key.';
displayError(tmGlobals.tmApiKeys[tbMap.apiKey].error);
}
};
this.initMapFunctionName = 'initTencentMap_' + this.mapId;
window[this.initMapFunctionName] = function() { // eslint-disable-line no-undef, angular/window-service
tmGlobals.tmApiKeys[tbMap.apiKey].loaded = true;
initTencentMap();
for (var p = 0; p < tmGlobals.tmApiKeys[tbMap.apiKey].pendingInits.length; p++) {
var pendingInit = tmGlobals.tmApiKeys[tbMap.apiKey].pendingInits[p];
pendingInit();
}
tmGlobals.tmApiKeys[tbMap.apiKey].pendingInits = [];
};
if (this.apiKey && this.apiKey.length > 0) {
if (tmGlobals.tmApiKeys[this.apiKey]) {
if (tmGlobals.tmApiKeys[this.apiKey].error) {
displayError(tmGlobals.tmApiKeys[this.apiKey].error);
} else if (tmGlobals.tmApiKeys[this.apiKey].loaded) {
initTencentMap();
} else {
tmGlobals.tmApiKeys[this.apiKey].pendingInits.push(initTencentMap);
}
} else {
tmGlobals.tmApiKeys[this.apiKey] = {
loaded: false,
pendingInits: []
};
var tencentMapScriptRes = 'https://map.qq.com/api/js?v=2.exp&key='+this.apiKey+'&callback='+this.initMapFunctionName;
tmGlobals.loadingTmId = this.mapId;
lazyLoad.load({ type: 'js', path: tencentMapScriptRes }).then( // eslint-disable-line no-undef
function success() {
setTimeout(clearGlobalId, 2000); // eslint-disable-line no-undef, angular/timeout-service
},
function fail(e) {
clearGlobalId();
tmGlobals.tmApiKeys[tbMap.apiKey].error = 'tencent map api load failed!</br>'+e;
displayError(tmGlobals.tmApiKeys[tbMap.apiKey].error);
}
);
}
} else {
displayError('No tencent Map Api Key provided!');
}
}
inited() {
return angular.isDefined(this.map);
}
createMarkerLabelStyle(settings) {
return {
width: "200px",
textAlign: "center",
color: settings.labelColor,
background: "none",
border: "none",
fontSize: "12px",
fontFamily: "\"Helvetica Neue\", Arial, Helvetica, sans-serif",
fontWeight: "bold"
};
}
/* eslint-disable no-undef,no-unused-vars*/
updateMarkerLabel(marker, settings) {
if (marker.label) {
marker.label.setContent(settings.labelText);
marker.label.setStyle(this.createMarkerLabelStyle(settings));
}
}
/* eslint-enable no-undef,no-unused-vars */
/* eslint-disable no-undef,no-unused-vars */
updateMarkerColor(marker, color) {
this.createDefaultMarkerIcon(marker, color, (iconInfo) => {
marker.setIcon(iconInfo.icon);
});
}
/* eslint-enable no-undef,,no-unused-vars */
/* eslint-disable no-undef */
updateMarkerIcon(marker, settings) {
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
if (marker.label) {
marker.label.setOffset(new qq.maps.Size(-100, -iconInfo.size[1]-20));
}
});
}
/* eslint-disable no-undef */
/* eslint-disable no-undef */
createMarkerIcon(marker, settings, onMarkerIconReady) {
var currentImage = settings.currentImage;
var tMap = this;
if (currentImage && currentImage.url) {
this.utils.loadImageAspect(currentImage.url).then(
(aspect) => {
if (aspect) {
var width;
var height;
if (aspect > 1) {
width = currentImage.size;
height = currentImage.size / aspect;
} else {
width = currentImage.size * aspect;
height = currentImage.size;
}
var icon = new qq.maps.MarkerImage(currentImage.url,
new qq.maps.Size(width, height),
new qq.maps.Point(0,0),
new qq.maps.Point(width/2, height),
new qq.maps.Size(width, height));
var iconInfo = {
size: [width, height],
icon: icon
};
onMarkerIconReady(iconInfo);
} else {
tMap.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
);
} else {
this.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createDefaultMarkerIcon(marker, color, onMarkerIconReady) {
var pinColor = color.substr(1);
var icon = new qq.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_letter_withshadow&chld=%E2%80%A2|" + pinColor,
new qq.maps.Size(40, 37),
new qq.maps.Point(0,0),
new qq.maps.Point(10, 37));
var iconInfo = {
size: [40, 37],
icon: icon
};
onMarkerIconReady(iconInfo);
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createMarker(location, dsIndex, settings, onClickListener, markerArgs) {
var marker = new qq.maps.Marker({
position: location
});
var tMap = this;
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
marker.setMap(tMap.map);
if (settings.showLabel) {
marker.label = new qq.maps.Label({
clickable: false,
content: settings.labelText,
offset: new qq.maps.Size(-100, -iconInfo.size[1]-20),
style: tMap.createMarkerLabelStyle(settings),
visible: true,
position: location,
map: tMap.map,
zIndex: 1000
});
}
});
if (settings.displayTooltip) {
this.createTooltip(marker, dsIndex, settings, markerArgs);
}
if (onClickListener) {
qq.maps.event.addListener(marker, 'click', onClickListener);
}
return marker;
}
/* eslint-disable no-undef */
removeMarker(marker) {
marker.setMap(null);
if (marker.label) {
marker.label.setMap(null);
}
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createTooltip(marker, dsIndex, settings, markerArgs) {
var popup = new qq.maps.InfoWindow({
map :this.map
});
var map = this;
qq.maps.event.addListener(marker, 'click', function() {
if (settings.autocloseTooltip) {
map.tooltips.forEach((tooltip) => {
tooltip.popup.close();
});
}
popup.open();
popup.setPosition(marker);
});
this.tooltips.push( {
markerArgs: markerArgs,
popup: popup,
locationSettings: settings,
dsIndex: dsIndex
});
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
updatePolylineColor(polyline, settings, color) {
var options = {
path: polyline.getPath(),
strokeColor: color,
strokeOpacity: settings.strokeOpacity,
strokeWeight: settings.strokeWeight,
map: this.map
};
polyline.setOptions(options);
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createPolyline(locations, settings) {
var polyline = new qq.maps.Polyline({
path: locations,
strokeColor: settings.color,
strokeOpacity: settings.strokeOpacity,
strokeWeight: settings.strokeWeight,
map: this.map
});
return polyline;
}
/* eslint-enable no-undef */
removePolyline(polyline) {
polyline.setMap(null);
}
/* eslint-disable no-undef ,no-unused-vars*/
fitBounds(bounds) {
if (this.dontFitMapBounds && this.defaultZoomLevel) {
this.map.setZoom(this.defaultZoomLevel);
this.map.setCenter(bounds.getCenter());
} else {
var tbMap = this;
qq.maps.event.addListenerOnce(this.map, 'bounds_changed', function() { // eslint-disable-line no-undef
if (!tbMap.defaultZoomLevel && tbMap.map.getZoom() > tbMap.minZoomLevel) {
tbMap.map.setZoom(tbMap.minZoomLevel);
}
});
this.map.fitBounds(bounds);
}
}
/* eslint-enable no-undef,no-unused-vars */
createLatLng(lat, lng) {
return new qq.maps.LatLng(lat, lng); // eslint-disable-line no-undef
}
extendBoundsWithMarker(bounds, marker) {
bounds.extend(marker.getPosition());
}
getMarkerPosition(marker) {
return marker.getPosition();
}
setMarkerPosition(marker, latLng) {
marker.setPosition(latLng);
if (marker.label) {
marker.label.setPosition(latLng);
}
}
getPolylineLatLngs(polyline) {
return polyline.getPath().getArray();
}
setPolylineLatLngs(polyline, latLngs) {
polyline.setPath(latLngs);
}
createBounds() {
return new qq.maps.LatLngBounds(); // eslint-disable-line no-undef
}
extendBounds(bounds, polyline) {
if (polyline && polyline.getPath()) {
var locations = polyline.getPath();
for (var i = 0; i < locations.getLength(); i++) {
bounds.extend(locations.getAt(i));
}
}
}
invalidateSize() {
qq.maps.event.trigger(this.map, "resize"); // eslint-disable-line no-undef
}
getTooltips() {
return this.tooltips;
}
constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, minZoomLevel, tmApiKey, tmDefaultMapType) {
var tbMap = this;
this.utils = utils;
this.defaultZoomLevel = defaultZoomLevel;
this.dontFitMapBounds = dontFitMapBounds;
this.minZoomLevel = minZoomLevel;
this.tooltips = [];
this.defaultMapType = tmDefaultMapType;
function clearGlobalId() {
if (tmGlobals.loadingTmId && tmGlobals.loadingTmId === tbMap.mapId) {
tmGlobals.loadingTmId = null;
}
}
function displayError(message) {
$containerElement.html( // eslint-disable-line angular/angularelement
"<div class='error'>" + message + "</div>"
);
}
function initTencentMap() {
tbMap.map = new qq.maps.Map($containerElement[0], { // eslint-disable-line no-undef
scrollwheel: true,
mapTypeId: getTencentMapTypeId(tbMap.defaultMapType),
zoom: tbMap.defaultZoomLevel || 8
});
if (initCallback) {
initCallback();
}
}
/* eslint-disable no-undef */
function getTencentMapTypeId(mapType) {
var mapTypeId = qq.maps.MapTypeId.ROADMAP;
if (mapType) {
if (mapType === 'hybrid') {
mapTypeId = qq.maps.MapTypeId.HYBRID;
} else if (mapType === 'satellite') {
mapTypeId = qq.maps.MapTypeId.SATELLITE;
} else if (mapType === 'terrain') {
mapTypeId = qq.maps.MapTypeId.ROADMAP;
}
}
return mapTypeId;
}
/* eslint-enable no-undef */
this.mapId = '' + Math.random().toString(36).substr(2, 9);
this.apiKey = tmApiKey || '84d6d83e0e51e481e50454ccbe8986b';
window.tm_authFailure = function () { // eslint-disable-line no-undef, angular/window-service
if (tmGlobals.loadingTmId && tmGlobals.loadingTmId === tbMap.mapId) {
tmGlobals.loadingTmId = null;
tmGlobals.tmApiKeys[tbMap.apiKey].error = 'Unable to authentificate for tencent Map API.</br>Please check your API key.';
displayError(tmGlobals.tmApiKeys[tbMap.apiKey].error);
}
};
this.initMapFunctionName = 'initTencentMap_' + this.mapId;
window[this.initMapFunctionName] = function () { // eslint-disable-line no-undef, angular/window-service
tmGlobals.tmApiKeys[tbMap.apiKey].loaded = true;
initTencentMap();
for (var p = 0; p < tmGlobals.tmApiKeys[tbMap.apiKey].pendingInits.length; p++) {
var pendingInit = tmGlobals.tmApiKeys[tbMap.apiKey].pendingInits[p];
pendingInit();
}
tmGlobals.tmApiKeys[tbMap.apiKey].pendingInits = [];
};
if (this.apiKey && this.apiKey.length > 0) {
if (tmGlobals.tmApiKeys[this.apiKey]) {
if (tmGlobals.tmApiKeys[this.apiKey].error) {
displayError(tmGlobals.tmApiKeys[this.apiKey].error);
} else if (tmGlobals.tmApiKeys[this.apiKey].loaded) {
initTencentMap();
} else {
tmGlobals.tmApiKeys[this.apiKey].pendingInits.push(initTencentMap);
}
} else {
tmGlobals.tmApiKeys[this.apiKey] = {
loaded: false,
pendingInits: []
};
var tencentMapScriptRes = 'https://map.qq.com/api/js?v=2.exp&key=' + this.apiKey + '&callback=' + this.initMapFunctionName;
tmGlobals.loadingTmId = this.mapId;
lazyLoad.load({type: 'js', path: tencentMapScriptRes}).then( // eslint-disable-line no-undef
function success() {
setTimeout(clearGlobalId, 2000); // eslint-disable-line no-undef, angular/timeout-service
},
function fail(e) {
clearGlobalId();
tmGlobals.tmApiKeys[tbMap.apiKey].error = 'tencent map api load failed!</br>' + e;
displayError(tmGlobals.tmApiKeys[tbMap.apiKey].error);
}
);
}
} else {
displayError('No tencent Map Api Key provided!');
}
}
inited() {
return angular.isDefined(this.map);
}
createMarkerLabelStyle(settings) {
return {
width: "200px",
textAlign: "center",
color: settings.labelColor,
background: "none",
border: "none",
fontSize: "12px",
fontFamily: "\"Helvetica Neue\", Arial, Helvetica, sans-serif",
fontWeight: "bold"
};
}
/* eslint-disable no-undef,no-unused-vars*/
updateMarkerLabel(marker, settings) {
if (marker.label) {
marker.label.setContent(settings.labelText);
marker.label.setStyle(this.createMarkerLabelStyle(settings));
}
}
/* eslint-enable no-undef,no-unused-vars */
/* eslint-disable no-undef,no-unused-vars */
updateMarkerColor(marker, color) {
this.createDefaultMarkerIcon(marker, color, (iconInfo) => {
marker.setIcon(iconInfo.icon);
});
}
/* eslint-enable no-undef,,no-unused-vars */
/* eslint-disable no-undef */
updateMarkerIcon(marker, settings) {
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
if (marker.label) {
marker.label.setOffset(new qq.maps.Size(-100, -iconInfo.size[1] - 20));
}
});
}
/* eslint-disable no-undef */
/* eslint-disable no-undef */
createMarkerIcon(marker, settings, onMarkerIconReady) {
var currentImage = settings.currentImage;
var tMap = this;
if (currentImage && currentImage.url) {
this.utils.loadImageAspect(currentImage.url).then(
(aspect) => {
if (aspect) {
var width;
var height;
if (aspect > 1) {
width = currentImage.size;
height = currentImage.size / aspect;
} else {
width = currentImage.size * aspect;
height = currentImage.size;
}
var icon = new qq.maps.MarkerImage(currentImage.url,
new qq.maps.Size(width, height),
new qq.maps.Point(0, 0),
new qq.maps.Point(width / 2, height),
new qq.maps.Size(width, height));
var iconInfo = {
size: [width, height],
icon: icon
};
onMarkerIconReady(iconInfo);
} else {
tMap.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
);
} else {
this.createDefaultMarkerIcon(marker, settings.color, onMarkerIconReady);
}
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createDefaultMarkerIcon(marker, color, onMarkerIconReady) {
var pinColor = color.substr(1);
var icon = new qq.maps.MarkerImage("https://chart.apis.google.com/chart?chst=d_map_pin_letter_withshadow&chld=%E2%80%A2|" + pinColor,
new qq.maps.Size(40, 37),
new qq.maps.Point(0, 0),
new qq.maps.Point(10, 37));
var iconInfo = {
size: [40, 37],
icon: icon
};
onMarkerIconReady(iconInfo);
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createMarker(location, dsIndex, settings, onClickListener, markerArgs) {
var marker = new qq.maps.Marker({
position: location
});
var tMap = this;
this.createMarkerIcon(marker, settings, (iconInfo) => {
marker.setIcon(iconInfo.icon);
marker.setMap(tMap.map);
if (settings.showLabel) {
marker.label = new qq.maps.Label({
clickable: false,
content: settings.labelText,
offset: new qq.maps.Size(-100, -iconInfo.size[1] - 20),
style: tMap.createMarkerLabelStyle(settings),
visible: true,
position: location,
map: tMap.map,
zIndex: 1000
});
}
});
if (settings.displayTooltip) {
this.createTooltip(marker, dsIndex, settings, markerArgs);
}
if (onClickListener) {
qq.maps.event.addListener(marker, 'click', onClickListener);
}
return marker;
}
/* eslint-disable no-undef */
removeMarker(marker) {
marker.setMap(null);
if (marker.label) {
marker.label.setMap(null);
}
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createTooltip(marker, dsIndex, settings, markerArgs) {
var popup = new qq.maps.InfoWindow({
map: this.map
});
var map = this;
qq.maps.event.addListener(marker, 'click', function () {
if (settings.autocloseTooltip) {
map.tooltips.forEach((tooltip) => {
tooltip.popup.close();
});
}
popup.open();
popup.setPosition(marker);
});
this.tooltips.push({
markerArgs: markerArgs,
popup: popup,
locationSettings: settings,
dsIndex: dsIndex
});
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
updatePolylineColor(polyline, settings, color) {
var options = {
path: polyline.getPath(),
strokeColor: color,
strokeOpacity: settings.strokeOpacity,
strokeWeight: settings.strokeWeight,
map: this.map
};
polyline.setOptions(options);
}
/* eslint-enable no-undef */
/* eslint-disable no-undef */
createPolyline(locations, settings) {
var polyline = new qq.maps.Polyline({
path: locations,
strokeColor: settings.color,
strokeOpacity: settings.strokeOpacity,
strokeWeight: settings.strokeWeight,
map: this.map
});
return polyline;
}
/* eslint-enable no-undef */
removePolyline(polyline) {
polyline.setMap(null);
}
/* eslint-disable no-undef */
createPolygon(latLangs, settings) {
let polygon = new qq.maps.Polygon({
map: this.map,
path: latLangs,
strokeColor: settings.polygonStrokeColor,
fillColor: settings.polygonColor,
strokeWeight: settings.polygonStrokeWeight
});
return polygon;
}
/* eslint-disable no-undef */
removePolygon (polygon) {
polygon.setMap(null);
}
/* eslint-disable no-undef,no-unused-vars */
updatePolygonColor (polygon, settings, color) {
let options = {
path: polygon.getPath(),
map: this.map,
strokeColor: color,
fillColor: color,
strokeWeight: settings.polygonStrokeWeight
}
}
/* eslint-disable no-undef ,no-unused-vars*/
getPolygonLatLngs(polygon) {
return polygon.getPath().getArray();
}
setPolygonLatLngs(polygon, latLngs) {
polygon.setPath(latLngs);
}
/* eslint-disable no-undef ,no-unused-vars*/
fitBounds(bounds) {
if (this.dontFitMapBounds && this.defaultZoomLevel) {
this.map.setZoom(this.defaultZoomLevel);
this.map.setCenter(bounds.getCenter());
} else {
var tbMap = this;
qq.maps.event.addListenerOnce(this.map, 'bounds_changed', function () { // eslint-disable-line no-undef
if (!tbMap.defaultZoomLevel && tbMap.map.getZoom() > tbMap.minZoomLevel) {
tbMap.map.setZoom(tbMap.minZoomLevel);
}
});
this.map.fitBounds(bounds);
}
}
/* eslint-enable no-undef,no-unused-vars */
createLatLng(lat, lng) {
return new qq.maps.LatLng(lat, lng); // eslint-disable-line no-undef
}
extendBoundsWithMarker(bounds, marker) {
bounds.extend(marker.getPosition());
}
getMarkerPosition(marker) {
return marker.getPosition();
}
setMarkerPosition(marker, latLng) {
marker.setPosition(latLng);
if (marker.label) {
marker.label.setPosition(latLng);
}
}
getPolylineLatLngs(polyline) {
return polyline.getPath().getArray();
}
setPolylineLatLngs(polyline, latLngs) {
polyline.setPath(latLngs);
}
createBounds() {
return new qq.maps.LatLngBounds(); // eslint-disable-line no-undef
}
extendBounds(bounds, polyline) {
if (polyline && polyline.getPath()) {
var locations = polyline.getPath();
for (var i = 0; i < locations.getLength(); i++) {
bounds.extend(locations.getAt(i));
}
}
}
invalidateSize() {
qq.maps.event.trigger(this.map, "resize"); // eslint-disable-line no-undef
}
getTooltips() {
return this.tooltips;
}
}

Loading…
Cancel
Save