Browse Source

merged with master

pull/964/head
Dima Landiak 8 years ago
parent
commit
753ff4e31a
  1. 1
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  2. 2
      application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java
  3. 2
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java
  4. 4
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java
  5. 19
      application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java
  6. 7
      application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
  7. 2
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  8. 57
      application/src/main/java/org/thingsboard/server/controller/UserController.java
  9. 59
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
  10. 2
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  11. 15
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  12. 21
      application/src/main/resources/thingsboard.yml
  13. 1
      common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java
  14. 2
      dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
  15. 29
      dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
  16. 127
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  17. 40
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  18. 7
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TsPartitionDate.java
  19. 13
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java
  20. 3
      dao/src/test/resources/application-test.properties
  21. 4
      docker/tb/Dockerfile
  22. 36
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java
  23. 4
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
  24. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
  25. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  26. 2
      ui/package.json
  27. 84
      ui/src/app/api/user.service.js
  28. 8
      ui/src/app/locale/locale.constant-en_US.json
  29. 6
      ui/src/app/locale/locale.constant-es_ES.json
  30. 1461
      ui/src/app/locale/locale.constant-fr_FR.json
  31. 176
      ui/src/app/locale/locale.constant-it_IT.json
  32. 1463
      ui/src/app/locale/locale.constant-ja_JA.json
  33. 6
      ui/src/app/locale/locale.constant-ko_KR.json
  34. 6
      ui/src/app/locale/locale.constant-ru_RU.json
  35. 4
      ui/src/app/locale/locale.constant-zh_CN.json
  36. 3
      ui/src/app/user/user-fieldset.tpl.html
  37. 16
      ui/src/app/user/user.controller.js
  38. 9
      ui/src/app/user/user.directive.js
  39. 1
      ui/src/app/user/users.tpl.html

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

@ -116,6 +116,7 @@ public class AppActor extends RuleChainManagerActor {
break;
case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
onToDeviceSessionMsg((BasicActorSystemToDeviceSessionActorMsg) msg);
break;
default:
return false;
}

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

@ -114,7 +114,7 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
private String deviceType;
private TbMsgMetaData defaultMetaData;
public DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) {
DeviceActorMessageProcessor(ActorSystemContext systemContext, LoggingAdapter logger, TenantId tenantId, DeviceId deviceId) {
super(systemContext, logger);
this.tenantId = tenantId;
this.deviceId = deviceId;

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

@ -28,7 +28,7 @@ import java.io.Serializable;
* Created by ashvayka on 19.03.18.
*/
@Data
final class RemoteToRuleChainTellNextMsg extends RuleNodeToRuleChainTellNextMsg implements TenantAwareMsg, RuleChainAwareMsg, Serializable {
final class RemoteToRuleChainTellNextMsg extends RuleNodeToRuleChainTellNextMsg implements TenantAwareMsg, RuleChainAwareMsg {
private static final long serialVersionUID = 2459605482321657447L;
private final TenantId tenantId;

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

@ -21,14 +21,16 @@ import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
import java.io.Serializable;
import java.util.Set;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
class RuleNodeToRuleChainTellNextMsg implements TbActorMsg, Serializable {
private static final long serialVersionUID = 4577026446412871820L;
private final RuleNodeId originator;
private final Set<String> relationTypes;
private final TbMsg msg;

19
application/src/main/java/org/thingsboard/server/actors/session/SessionManagerActor.java

@ -15,13 +15,17 @@
*/
package org.thingsboard.server.actors.session;
import akka.actor.ActorInitializationException;
import akka.actor.ActorRef;
import akka.actor.InvalidActorNameException;
import akka.actor.LocalActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
import akka.actor.SupervisorStrategy;
import akka.actor.Terminated;
import akka.event.Logging;
import akka.event.LoggingAdapter;
import akka.japi.Function;
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.service.ContextAwareActor;
import org.thingsboard.server.actors.service.ContextBasedCreator;
@ -34,6 +38,7 @@ import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
import org.thingsboard.server.common.msg.core.ActorSystemToDeviceSessionActorMsg;
import org.thingsboard.server.common.msg.core.SessionCloseMsg;
import org.thingsboard.server.common.msg.session.SessionCtrlMsg;
import scala.concurrent.duration.Duration;
import java.util.HashMap;
import java.util.Map;
@ -46,11 +51,16 @@ public class SessionManagerActor extends ContextAwareActor {
private final Map<String, ActorRef> sessionActors;
public SessionManagerActor(ActorSystemContext systemContext) {
SessionManagerActor(ActorSystemContext systemContext) {
super(systemContext);
this.sessionActors = new HashMap<>(INITIAL_SESSION_MAP_SIZE);
}
@Override
public SupervisorStrategy supervisorStrategy() {
return strategy;
}
@Override
protected boolean process(TbActorMsg msg) {
//TODO Move everything here, to work with TbActorMsg
@ -160,4 +170,11 @@ public class SessionManagerActor extends ContextAwareActor {
}
}
private final SupervisorStrategy strategy = new OneForOneStrategy(3, Duration.create("1 minute"), new Function<Throwable, SupervisorStrategy.Directive>() {
@Override
public SupervisorStrategy.Directive apply(Throwable t) {
logger.error(t, "Unknown failure");
return SupervisorStrategy.stop();
}
});
}

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

@ -15,6 +15,7 @@
*/
package org.thingsboard.server.actors.tenant;
import akka.actor.ActorInitializationException;
import akka.actor.ActorRef;
import akka.actor.OneForOneStrategy;
import akka.actor.Props;
@ -170,7 +171,11 @@ public class TenantActor extends RuleChainManagerActor {
@Override
public SupervisorStrategy.Directive apply(Throwable t) {
logger.error(t, "Unknown failure");
return SupervisorStrategy.resume();
if(t instanceof ActorInitializationException){
return SupervisorStrategy.stop();
} else {
return SupervisorStrategy.resume();
}
}
});

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

@ -86,7 +86,7 @@ public class AlarmController extends BaseController {
savedAlarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedAlarm;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ASSET), alarm,
logEntityAction(emptyId(EntityType.ALARM), alarm,
null, alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}

57
application/src/main/java/org/thingsboard/server/controller/UserController.java

@ -15,7 +15,12 @@
*/
package org.thingsboard.server.controller;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -39,7 +44,11 @@ import org.thingsboard.server.common.data.page.TextPageData;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.common.data.security.Authority;
import org.thingsboard.server.common.data.security.UserCredentials;
import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
import org.thingsboard.server.service.security.model.SecurityUser;
import org.thingsboard.server.service.security.model.UserPrincipal;
import org.thingsboard.server.service.security.model.token.JwtToken;
import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
import javax.servlet.http.HttpServletRequest;
@ -50,9 +59,21 @@ public class UserController extends BaseController {
public static final String USER_ID = "userId";
public static final String YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION = "You don't have permission to perform this operation!";
public static final String ACTIVATE_URL_PATTERN = "%s/api/noauth/activate?activateToken=%s";
@Value("${security.user_token_access_enabled}")
@Getter
private boolean userTokenAccessEnabled;
@Autowired
private MailService mailService;
@Autowired
private JwtTokenFactory tokenFactory;
@Autowired
private RefreshTokenRepository refreshTokenRepository;
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user/{userId}", method = RequestMethod.GET)
@ResponseBody
@ -71,6 +92,42 @@ public class UserController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/user/tokenAccessEnabled", method = RequestMethod.GET)
@ResponseBody
public boolean isUserTokenAccessEnabled() {
return userTokenAccessEnabled;
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')")
@RequestMapping(value = "/user/{userId}/token", method = RequestMethod.GET)
@ResponseBody
public JsonNode getUserToken(@PathVariable(USER_ID) String strUserId) throws ThingsboardException {
checkParameter(USER_ID, strUserId);
try {
UserId userId = new UserId(toUUID(strUserId));
SecurityUser authUser = getCurrentUser();
User user = userService.findUserById(userId);
if (!userTokenAccessEnabled || (authUser.getAuthority() == Authority.SYS_ADMIN && user.getAuthority() != Authority.TENANT_ADMIN)
|| (authUser.getAuthority() == Authority.TENANT_ADMIN && !authUser.getTenantId().equals(user.getTenantId()))) {
throw new ThingsboardException(YOU_DON_T_HAVE_PERMISSION_TO_PERFORM_THIS_OPERATION,
ThingsboardErrorCode.PERMISSION_DENIED);
}
UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
UserCredentials credentials = userService.findUserCredentialsByUserId(userId);
SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser);
JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser);
ObjectMapper objectMapper = new ObjectMapper();
ObjectNode tokenObject = objectMapper.createObjectNode();
tokenObject.put("token", accessToken.getToken());
tokenObject.put("refreshToken", refreshToken.getToken());
return tokenObject;
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/user", method = RequestMethod.POST)
@ResponseBody

59
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java

@ -22,6 +22,7 @@ import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@ -42,9 +43,10 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
private ScriptEngine engine;
private ExecutorService monitorExecutorService;
private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
private Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
private final Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
private final Map<String, Pair<UUID, AtomicInteger>> scriptToId = new ConcurrentHashMap<>();
private final Map<UUID, AtomicInteger> scriptIdToCount = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
@ -78,19 +80,27 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
@Override
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
UUID scriptId = UUID.randomUUID();
String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
try {
if (useJsSandbox()) {
sandbox.eval(jsScript);
} else {
engine.eval(jsScript);
Pair<UUID, AtomicInteger> deduplicated = deduplicate(scriptType, scriptBody);
UUID scriptId = deduplicated.getLeft();
AtomicInteger duplicateCount = deduplicated.getRight();
if(duplicateCount.compareAndSet(0, 1)) {
String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
try {
if (useJsSandbox()) {
sandbox.eval(jsScript);
} else {
engine.eval(jsScript);
}
functionsMap.put(scriptId, functionName);
} catch (Exception e) {
duplicateCount.decrementAndGet();
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
return Futures.immediateFailedFuture(e);
}
functionsMap.put(scriptId, functionName);
} catch (Exception e) {
log.warn("Failed to compile JS script: {}", e.getMessage(), e);
return Futures.immediateFailedFuture(e);
} else {
duplicateCount.incrementAndGet();
}
return Futures.immediateFuture(scriptId);
}
@ -122,6 +132,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
@Override
public ListenableFuture<Void> release(UUID scriptId) {
AtomicInteger count = scriptIdToCount.get(scriptId);
if(count != null) {
if(count.decrementAndGet() > 0) {
return Futures.immediateFuture(null);
}
}
String functionName = functionsMap.get(scriptId);
if (functionName != null) {
try {
@ -156,4 +173,16 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
}
}
private Pair<UUID, AtomicInteger> deduplicate(JsScriptType scriptType, String scriptBody) {
Pair<UUID, AtomicInteger> precomputed = Pair.of(UUID.randomUUID(), new AtomicInteger());
Pair<UUID, AtomicInteger> pair = scriptToId.computeIfAbsent(deduplicateKey(scriptType, scriptBody), i -> precomputed);
AtomicInteger duplicateCount = scriptIdToCount.computeIfAbsent(pair.getLeft(), i -> pair.getRight());
return Pair.of(pair.getLeft(), duplicateCount);
}
private String deduplicateKey(JsScriptType scriptType, String scriptBody) {
return scriptType + "_" + scriptBody;
}
}

2
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java

@ -45,7 +45,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
try {
this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
} catch (Exception e) {
throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
throw new IllegalArgumentException("Can't compile script: " + e.getMessage(), e);
}
}

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

@ -24,12 +24,15 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.util.DonAsynchron;
import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
@ -42,6 +45,7 @@ import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
@ -101,6 +105,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Lazy
private DeviceStateService stateService;
@Autowired
@Lazy
private ActorService actorService;
private ExecutorService tsCallBackExecutor;
private ExecutorService wsCallBackExecutor;
@ -203,6 +211,13 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
, System.currentTimeMillis())), callback);
}
@Override
public void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> attributes) {
DeviceAttributesEventNotificationMsg notificationMsg = DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
deviceId, DataConstants.SHARED_SCOPE, new ArrayList<>(attributes));
actorService.onMsg(new SendToClusterMsg(deviceId, notificationMsg));
}
@Override
public void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data) {
ClusterAPIProtos.SubscriptionProto proto;

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

@ -66,12 +66,16 @@ plugins:
# Comma seperated package list used during classpath scanning for plugins
scan_packages: "${PLUGINS_SCAN_PACKAGES:org.thingsboard.server.extensions,org.thingsboard.rule.engine}"
# JWT Token parameters
security.jwt:
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins)
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour)
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
# Security parameters
security:
# JWT Token parameters
jwt:
tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:900}" # Number of seconds (15 mins)
refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:3600}" # Seconds (1 hour)
tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}"
tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}"
# Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator
user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}"
# Device communication protocol parameters
http:
@ -201,7 +205,7 @@ cassandra:
read_consistency_level: "${CASSANDRA_READ_CONSISTENCY_LEVEL:ONE}"
write_consistency_level: "${CASSANDRA_WRITE_CONSISTENCY_LEVEL:ONE}"
default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}"
# Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS
# Specify partitioning size for timestamp key-value storage. Example MINUTES, HOURS, DAYS, MONTHS,INDEFINITE
ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}"
ts_key_value_ttl: "${TS_KV_TTL:0}"
buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}"
@ -291,6 +295,9 @@ caffeine:
devices:
timeToLiveInMinutes: 1440
maxSize: 100000
assets:
timeToLiveInMinutes: 1440
maxSize: 100000
redis:
# standalone or cluster

1
common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java

@ -19,4 +19,5 @@ public class CacheConstants {
public static final String DEVICE_CREDENTIALS_CACHE = "deviceCredentials";
public static final String RELATIONS_CACHE = "relations";
public static final String DEVICE_CACHE = "devices";
public static final String ASSET_CACHE = "assets";
}

2
dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java

@ -34,7 +34,7 @@ public interface AssetService {
ListenableFuture<Asset> findAssetByIdAsync(AssetId assetId);
Optional<Asset> findAssetByTenantIdAndName(TenantId tenantId, String name);
Asset findAssetByTenantIdAndName(TenantId tenantId, String name);
Asset saveAsset(Asset asset);

29
dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java

@ -21,6 +21,10 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Customer;
@ -48,15 +52,12 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
import static org.thingsboard.server.common.data.CacheConstants.ASSET_CACHE;
import static org.thingsboard.server.dao.DaoUtil.toUUIDs;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
import static org.thingsboard.server.dao.service.Validator.validateId;
import static org.thingsboard.server.dao.service.Validator.validateIds;
import static org.thingsboard.server.dao.service.Validator.validatePageLink;
import static org.thingsboard.server.dao.service.Validator.validateString;
import static org.thingsboard.server.dao.service.Validator.*;
@Service
@Slf4j
@ -75,6 +76,9 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
@Autowired
private CustomerDao customerDao;
@Autowired
private CacheManager cacheManager;
@Override
public Asset findAssetById(AssetId assetId) {
log.trace("Executing findAssetById [{}]", assetId);
@ -89,13 +93,16 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
return assetDao.findByIdAsync(assetId.getId());
}
@Cacheable(cacheNames = ASSET_CACHE, key = "{#tenantId, #name}")
@Override
public Optional<Asset> findAssetByTenantIdAndName(TenantId tenantId, String name) {
public Asset findAssetByTenantIdAndName(TenantId tenantId, String name) {
log.trace("Executing findAssetByTenantIdAndName [{}][{}]", tenantId, name);
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
return assetDao.findAssetsByTenantIdAndName(tenantId.getId(), name);
return assetDao.findAssetsByTenantIdAndName(tenantId.getId(), name)
.orElse(null);
}
@CacheEvict(cacheNames = ASSET_CACHE, key = "{#asset.tenantId, #asset.name}")
@Override
public Asset saveAsset(Asset asset) {
log.trace("Executing saveAsset [{}]", asset);
@ -122,6 +129,14 @@ public class BaseAssetService extends AbstractEntityService implements AssetServ
log.trace("Executing deleteAsset [{}]", assetId);
validateId(assetId, INCORRECT_ASSET_ID + assetId);
deleteEntityRelations(assetId);
Cache cache = cacheManager.getCache(ASSET_CACHE);
Asset asset = assetDao.findById(assetId.getId());
List<Object> list = new ArrayList<>();
list.add(asset.getTenantId());
list.add(asset.getName());
cache.evict(list);
assetDao.removeById(assetId.getId());
}

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

@ -16,9 +16,7 @@
package org.thingsboard.server.dao.relation;
import com.google.common.base.Function;
import com.google.common.util.concurrent.AsyncFunction;
import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.Cache;
@ -41,7 +39,6 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@ -94,10 +91,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
})
@Override
public boolean saveRelation(EntityRelation relation) {
@ -108,10 +105,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
})
@Override
public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) {
@ -122,10 +119,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
})
@Override
public boolean deleteRelation(EntityRelation relation) {
@ -136,10 +133,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
})
@Override
public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) {
@ -150,10 +147,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
})
@Override
public boolean deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
@ -164,10 +161,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}"),
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
})
@Override
public ListenableFuture<Boolean> deleteRelationAsync(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
@ -250,30 +247,36 @@ public class BaseRelationService implements RelationService {
fromTypeAndTypeGroup.add(relation.getFrom());
fromTypeAndTypeGroup.add(relation.getType());
fromTypeAndTypeGroup.add(relation.getTypeGroup());
fromTypeAndTypeGroup.add(EntitySearchDirection.FROM.name());
cache.evict(fromTypeAndTypeGroup);
List<Object> fromAndTypeGroup = new ArrayList<>();
fromAndTypeGroup.add(relation.getFrom());
fromAndTypeGroup.add(relation.getTypeGroup());
fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
cache.evict(fromAndTypeGroup);
List<Object> toAndTypeGroup = new ArrayList<>();
toAndTypeGroup.add(relation.getTo());
toAndTypeGroup.add(relation.getTypeGroup());
toAndTypeGroup.add(EntitySearchDirection.TO.name());
cache.evict(toAndTypeGroup);
List<Object> toTypeAndTypeGroup = new ArrayList<>();
fromTypeAndTypeGroup.add(relation.getTo());
fromTypeAndTypeGroup.add(relation.getType());
fromTypeAndTypeGroup.add(relation.getTypeGroup());
toTypeAndTypeGroup.add(relation.getTo());
toTypeAndTypeGroup.add(relation.getType());
toTypeAndTypeGroup.add(relation.getTypeGroup());
toTypeAndTypeGroup.add(EntitySearchDirection.TO.name());
cache.evict(toTypeAndTypeGroup);
}
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}")
@Override
public List<EntityRelation> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
validate(from);
validateTypeGroup(typeGroup);
try {
return findByFromAsync(from, typeGroup).get();
return relationDao.findAllByFrom(from, typeGroup).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
@ -284,7 +287,29 @@ public class BaseRelationService implements RelationService {
log.trace("Executing findByFrom [{}][{}]", from, typeGroup);
validate(from);
validateTypeGroup(typeGroup);
return relationDao.findAllByFrom(from, typeGroup);
List<Object> fromAndTypeGroup = new ArrayList<>();
fromAndTypeGroup.add(from);
fromAndTypeGroup.add(typeGroup);
fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
Cache cache = cacheManager.getCache(RELATIONS_CACHE);
List<EntityRelation> fromCache = cache.get(fromAndTypeGroup, List.class);
if (fromCache != null) {
return Futures.immediateFuture(fromCache);
} else {
ListenableFuture<List<EntityRelation>> relationsFuture = relationDao.findAllByFrom(from, typeGroup);
Futures.addCallback(relationsFuture,
new FutureCallback<List<EntityRelation>>() {
@Override
public void onSuccess(@Nullable List<EntityRelation> result) {
cache.putIfAbsent(fromAndTypeGroup, result);
}
@Override
public void onFailure(Throwable t) {}
});
return relationsFuture;
}
}
@Override
@ -305,7 +330,7 @@ public class BaseRelationService implements RelationService {
});
}
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}")
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}")
@Override
public List<EntityRelation> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
try {
@ -324,11 +349,13 @@ public class BaseRelationService implements RelationService {
return relationDao.findAllByFromAndType(from, relationType, typeGroup);
}
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}")
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}")
@Override
public List<EntityRelation> findByTo(EntityId to, RelationTypeGroup typeGroup) {
validate(to);
validateTypeGroup(typeGroup);
try {
return findByToAsync(to, typeGroup).get();
return relationDao.findAllByTo(to, typeGroup).get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
@ -339,7 +366,29 @@ public class BaseRelationService implements RelationService {
log.trace("Executing findByTo [{}][{}]", to, typeGroup);
validate(to);
validateTypeGroup(typeGroup);
return relationDao.findAllByTo(to, typeGroup);
List<Object> toAndTypeGroup = new ArrayList<>();
toAndTypeGroup.add(to);
toAndTypeGroup.add(typeGroup);
toAndTypeGroup.add(EntitySearchDirection.TO.name());
Cache cache = cacheManager.getCache(RELATIONS_CACHE);
List<EntityRelation> fromCache = cache.get(toAndTypeGroup, List.class);
if (fromCache != null) {
return Futures.immediateFuture(fromCache);
} else {
ListenableFuture<List<EntityRelation>> relationsFuture = relationDao.findAllByTo(to, typeGroup);
Futures.addCallback(relationsFuture,
new FutureCallback<List<EntityRelation>>() {
@Override
public void onSuccess(@Nullable List<EntityRelation> result) {
cache.putIfAbsent(toAndTypeGroup, result);
}
@Override
public void onFailure(Throwable t) {}
});
return relationsFuture;
}
}
@Override
@ -371,7 +420,7 @@ public class BaseRelationService implements RelationService {
});
}
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
@Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
@Override
public List<EntityRelation> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
try {

40
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java

@ -29,17 +29,8 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.*;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
import org.thingsboard.server.common.data.kv.KvEntry;
import org.thingsboard.server.common.data.kv.LongDataEntry;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao;
import org.thingsboard.server.dao.util.NoSqlDao;
@ -70,6 +61,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
public static final String EQUALS_PARAM = " = ? ";
public static final String ASC_ORDER = "ASC";
public static final String DESC_ORDER = "DESC";
private static List<Long> FIXED_PARTITION = Arrays.asList(new Long[]{0L});
@Autowired
private Environment environment;
@ -161,14 +153,23 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
}
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
long minPartition = toPartitionTs(query.getStartTs());
long maxPartition = toPartitionTs(query.getEndTs());
public boolean isFixedPartitioning() {
return tsFormat.getTruncateUnit().equals(TsPartitionDate.EPOCH_START);
}
private ListenableFuture<List<Long>> getPartitionsFuture(TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
if (isFixedPartitioning()) { //no need to fetch partitions from DB
return Futures.immediateFuture(FIXED_PARTITION);
}
ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
return Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
}
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
long minPartition = toPartitionTs(query.getStartTs());
long maxPartition = toPartitionTs(query.getEndTs());
final ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
final SimpleListenableFuture<List<TsKvEntry>> resultFuture = new SimpleListenableFuture<>();
final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
@Override
@ -179,7 +180,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), toPartitionTs(query.getStartTs()), toPartitionTs(query.getEndTs()), t);
}
}, readResultsProcessingExecutor);
@ -226,11 +227,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
final long startTs = query.getStartTs();
final long endTs = query.getEndTs();
final long ts = startTs + (endTs - startTs) / 2;
ResultSetFuture partitionsFuture = fetchPartitions(entityId, key, minPartition, maxPartition);
ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
ListenableFuture<List<ResultSet>> aggregationChunks = Futures.transformAsync(partitionsListFuture,
getFetchChunksAsyncFunction(entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor);
@ -306,6 +303,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
@Override
public ListenableFuture<Void> savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl) {
if (isFixedPartitioning()) {
return Futures.immediateFuture(null);
}
ttl = computeTtl(ttl);
long partition = toPartitionTs(tsKvEntryTs);
log.debug("Saving partition {} for the entity [{}-{}] and key {}", partition, entityId.getEntityType(), entityId.getId(), key);

7
dao/src/main/java/org/thingsboard/server/dao/timeseries/TsPartitionDate.java

@ -16,22 +16,25 @@
package org.thingsboard.server.dao.timeseries;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.temporal.ChronoUnit;
import java.time.temporal.TemporalUnit;
import java.util.Optional;
public enum TsPartitionDate {
MINUTES("yyyy-MM-dd-HH-mm", ChronoUnit.MINUTES), HOURS("yyyy-MM-dd-HH", ChronoUnit.HOURS), DAYS("yyyy-MM-dd", ChronoUnit.DAYS), MONTHS("yyyy-MM", ChronoUnit.MONTHS), YEARS("yyyy", ChronoUnit.YEARS);
MINUTES("yyyy-MM-dd-HH-mm", ChronoUnit.MINUTES), HOURS("yyyy-MM-dd-HH", ChronoUnit.HOURS), DAYS("yyyy-MM-dd", ChronoUnit.DAYS), MONTHS("yyyy-MM", ChronoUnit.MONTHS), YEARS("yyyy", ChronoUnit.YEARS),INDEFINITE("",ChronoUnit.FOREVER);
private final String pattern;
private final transient TemporalUnit truncateUnit;
public final static LocalDateTime EPOCH_START = LocalDateTime.ofEpochSecond(0,0, ZoneOffset.UTC);
TsPartitionDate(String pattern, TemporalUnit truncateUnit) {
this.pattern = pattern;
this.truncateUnit = truncateUnit;
}
public String getPattern() {
return pattern;
}
@ -46,6 +49,8 @@ public enum TsPartitionDate {
return time.truncatedTo(ChronoUnit.DAYS).withDayOfMonth(1);
case YEARS:
return time.truncatedTo(ChronoUnit.DAYS).withDayOfYear(1);
case INDEFINITE:
return EPOCH_START;
default:
return time.truncatedTo(truncateUnit);
}

13
dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java

@ -227,6 +227,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
Assert.assertTrue(relations.contains(relationA));
Assert.assertTrue(relations.contains(relationB));
Assert.assertTrue(relations.contains(relationC));
//Test from cache
relations = relationService.findByQuery(query).get();
Assert.assertEquals(3, relations.size());
Assert.assertTrue(relations.contains(relationA));
Assert.assertTrue(relations.contains(relationB));
Assert.assertTrue(relations.contains(relationC));
}
@Test
@ -253,6 +260,12 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
Assert.assertEquals(2, relations.size());
Assert.assertTrue(relations.contains(relationAB));
Assert.assertTrue(relations.contains(relationBC));
//Test from cache
relations = relationService.findByQuery(query).get();
Assert.assertEquals(2, relations.size());
Assert.assertTrue(relations.contains(relationAB));
Assert.assertTrue(relations.contains(relationBC));
}

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

@ -21,6 +21,9 @@ caffeine.specs.deviceCredentials.maxSize=100000
caffeine.specs.devices.timeToLiveInMinutes=1440
caffeine.specs.devices.maxSize=100000
caffeine.specs.assets.timeToLiveInMinutes=1440
caffeine.specs.assets.maxSize=100000
caching.specs.devices.timeToLiveInMinutes=1440
caching.specs.devices.maxSize=100000

4
docker/tb/Dockerfile

@ -20,5 +20,7 @@ ADD run-application.sh /run-application.sh
ADD thingsboard.deb /thingsboard.deb
RUN apt-get update \
&& apt-get install -y nmap \
&& apt-get install --no-install-recommends -y nmap \
&& apt-get clean \
&& rm -r /var/lib/apt/lists/* \
&& chmod +x /run-application.sh

36
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java

@ -40,6 +40,8 @@ public final class MqttClientConfig {
private Class<? extends Channel> channelClass = NioSocketChannel.class;
private boolean reconnect = true;
private long reconnectDelay = 1L;
private int maxBytesInMessage = 8092;
public MqttClientConfig() {
this(null);
@ -146,4 +148,38 @@ public final class MqttClientConfig {
public void setReconnect(boolean reconnect) {
this.reconnect = reconnect;
}
public long getReconnectDelay() {
return reconnectDelay;
}
/**
* Sets the reconnect delay in seconds. Defaults to 1 second.
* @param reconnectDelay
* @throws IllegalArgumentException if reconnectDelay is smaller than 1.
*/
public void setReconnectDelay(long reconnectDelay) {
if (reconnectDelay <= 0) {
throw new IllegalArgumentException("reconnectDelay must be > 0");
}
this.reconnectDelay = reconnectDelay;
}
public int getMaxBytesInMessage() {
return maxBytesInMessage;
}
/**
* Sets the maximum number of bytes in the message for the {@link io.netty.handler.codec.mqtt.MqttDecoder}.
* Default value is 8092 as specified by Netty. The absolute maximum size is 256MB as set by the MQTT spec.
*
* @param maxBytesInMessage
* @throws IllegalArgumentException if maxBytesInMessage is smaller than 1 or greater than 256_000_000.
*/
public void setMaxBytesInMessage(int maxBytesInMessage) {
if (maxBytesInMessage <= 0 || maxBytesInMessage > 256_000_000) {
throw new IllegalArgumentException("maxBytesInMessage must be > 0 or < 256_000_000");
}
this.maxBytesInMessage = maxBytesInMessage;
}
}

4
netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java

@ -155,7 +155,7 @@ final class MqttClientImpl implements MqttClient {
if (reconnect) {
this.reconnect = true;
}
eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), 1L, TimeUnit.SECONDS);
eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), clientConfig.getReconnectDelay(), TimeUnit.SECONDS);
}
}
@ -512,7 +512,7 @@ final class MqttClientImpl implements MqttClient {
ch.pipeline().addLast(sslContext.newHandler(ch.alloc(), host, port));
}
ch.pipeline().addLast("mqttDecoder", new MqttDecoder());
ch.pipeline().addLast("mqttDecoder", new MqttDecoder(clientConfig.getMaxBytesInMessage()));
ch.pipeline().addLast("mqttEncoder", MqttEncoder.INSTANCE);
ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds(), MqttClientImpl.this.clientConfig.getTimeoutSeconds(), 0));
ch.pipeline().addLast("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds()));

5
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java

@ -16,11 +16,14 @@
package org.thingsboard.rule.engine.api;
import com.google.common.util.concurrent.FutureCallback;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import java.util.List;
import java.util.Set;
/**
* Created by ashvayka on 02.04.18.
@ -41,4 +44,6 @@ public interface RuleEngineTelemetryService {
void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback);
void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> attributes);
}

8
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java

@ -17,12 +17,15 @@ package org.thingsboard.rule.engine.telemetry;
import com.google.gson.JsonParser;
import lombok.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.api.RuleNode;
import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.api.TbNode;
import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
@ -62,6 +65,9 @@ public class TbMsgAttributesNode implements TbNode {
String src = msg.getData();
Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes();
ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg));
if (msg.getOriginator().getEntityType() == EntityType.DEVICE && DataConstants.SHARED_SCOPE.equals(config.getScope())) {
ctx.getTelemetryService().onSharedAttributesUpdate(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId()), attributes);
}
}
@Override

2
ui/package.json

@ -1,7 +1,7 @@
{
"name": "thingsboard",
"private": true,
"version": "2.1.0",
"version": "2.1.1",
"description": "Thingsboard UI",
"licenses": [
{

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

@ -27,6 +27,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
currentUserDetails = null,
lastPublicDashboardId = null,
allowedDashboardIds = [],
userTokenAccessEnabled = false,
userLoaded = false;
var refreshTokenQueue = [];
@ -59,7 +60,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
forceDefaultPlace: forceDefaultPlace,
updateLastPublicDashboardId: updateLastPublicDashboardId,
logout: logout,
reloadUser: reloadUser
reloadUser: reloadUser,
isUserTokenAccessEnabled: isUserTokenAccessEnabled,
loginAsUser: loginAsUser
}
reloadUser();
@ -105,6 +108,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
currentUser = null;
currentUserDetails = null;
lastPublicDashboardId = null;
userTokenAccessEnabled = false;
allowedDashboardIds = [];
if (!jwtToken) {
clearTokenData();
@ -299,24 +303,36 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
} else if (currentUser) {
currentUser.authority = "ANONYMOUS";
}
var sysParamsPromise = loadSystemParams();
if (currentUser.isPublic) {
$rootScope.forceFullscreen = true;
fetchAllowedDashboardIds();
sysParamsPromise.then(
() => { fetchAllowedDashboardIds(); },
() => { deferred.reject(); }
);
} else if (currentUser.userId) {
getUser(currentUser.userId, true).then(
function success(user) {
currentUserDetails = user;
updateUserLang();
$rootScope.forceFullscreen = false;
if (userForceFullscreen()) {
$rootScope.forceFullscreen = true;
}
if ($rootScope.forceFullscreen && (currentUser.authority === 'TENANT_ADMIN' ||
currentUser.authority === 'CUSTOMER_USER')) {
fetchAllowedDashboardIds();
} else {
deferred.resolve();
}
sysParamsPromise.then(
() => {
currentUserDetails = user;
updateUserLang();
$rootScope.forceFullscreen = false;
if (userForceFullscreen()) {
$rootScope.forceFullscreen = true;
}
if ($rootScope.forceFullscreen && (currentUser.authority === 'TENANT_ADMIN' ||
currentUser.authority === 'CUSTOMER_USER')) {
fetchAllowedDashboardIds();
} else {
deferred.resolve();
}
},
() => {
deferred.reject();
logout();
}
);
},
function fail() {
deferred.reject();
@ -353,6 +369,30 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
return deferred.promise;
}
function loadIsUserTokenAccessEnabled() {
var deferred = $q.defer();
if (currentUser.authority === 'SYS_ADMIN' || currentUser.authority === 'TENANT_ADMIN') {
var url = '/api/user/tokenAccessEnabled';
$http.get(url).then(function success(response) {
userTokenAccessEnabled = response.data;
deferred.resolve(response.data);
}, function fail() {
userTokenAccessEnabled = false;
deferred.reject();
});
} else {
userTokenAccessEnabled = false;
deferred.resolve(false);
}
return deferred.promise;
}
function loadSystemParams() {
var promises = [];
promises.push(loadIsUserTokenAccessEnabled());
return $q.all(promises);
}
function notifyUserLoaded() {
if (!userLoaded) {
userLoaded = true;
@ -520,7 +560,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
}
);
}
$state.go(place, params);
$state.go(place, params, {reload: true});
} else {
$state.go('login', params);
}
@ -549,4 +589,18 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
}
}
function isUserTokenAccessEnabled() {
return userTokenAccessEnabled;
}
function loginAsUser(userId) {
var url = '/api/user/' + userId + '/token';
$http.get(url).then(function success(response) {
var token = response.data.token;
var refreshToken = response.data.refreshToken;
setUserFromJwtToken(token, refreshToken, true);
}, function fail() {
});
}
}

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

@ -1270,7 +1270,9 @@
"activation-link-text": "In order to activate user use the following <a href='{{activationLink}}' target='_blank'>activation link</a> :",
"copy-activation-link": "Copy activation link",
"activation-link-copied-message": "User activation link has been copied to clipboard",
"details": "Details"
"details": "Details",
"login-as-tenant-admin": "Login as Tenant Admin",
"login-as-customer-user": "Login as Customer User"
},
"value": {
"type": "Value type",
@ -1451,12 +1453,14 @@
"language": {
"language": "Language",
"locales": {
"fr_FR": "French",
"zh_CN": "Chinese",
"en_US": "English",
"it_IT": "Italian",
"ko_KR": "Korean",
"ru_RU": "Russian",
"es_ES": "Spanish"
"es_ES": "Spanish",
"ja_JA": "Japanese"
}
}
}

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

@ -1307,11 +1307,13 @@
"language": "Lenguaje",
"locales": {
"en_US": "Inglés",
"fr_FR": "Francés",
"ko_KR": "Coreano",
"zh_CN": "Chino",
"ru_RU": "Ruso",
"es_ES": "Español",
"it_IT": "Italiano"
"it_IT": "Italiano",
"ja_JA": "Japonés"
}
}
}
}

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

File diff suppressed because it is too large

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

@ -30,8 +30,8 @@
"apply": "Applica",
"apply-changes": "Applica modifiche",
"edit-mode": "Modalità modifica",
"enter-edit-mode": "Attiva la modalità di modifica",
"decline-changes": "Annulla le modifiche",
"enter-edit-mode": "Attiva modalità di modifica",
"decline-changes": "Annulla modifiche",
"close": "Chiudi",
"back": "Indietro",
"run": "Esegui",
@ -108,7 +108,7 @@
"no-alarms-prompt": "Nessun allarme trovato",
"created-time": "Orario di creazione",
"type": "Tipo",
"severity": "Gravità",
"severity": "Livello di gravità",
"originator": "Origine",
"originator-type": "Tipo origine",
"details": "Dettagli",
@ -125,7 +125,7 @@
"severity-indeterminate": "Indeterminato",
"acknowledge": "Conferma",
"clear": "Cancella",
"search": "Ricerca allarmi",
"search": "Cerca allarmi",
"selected-alarms": "{ count, plural, 1 {1 allarme selezionato} other {# allarmi selezionati} }",
"no-data": "Nessun dato da visualizzare",
"polling-interval": "Intervallo di polling (sec) Allarmi",
@ -267,7 +267,7 @@
},
"audit-log": {
"audit": "Audit",
"audit-logs": "Audit Logs",
"audit-logs": "Log Audit",
"timestamp": "Timestamp",
"entity-type": "Tipo Entità",
"entity-name": "Nome Entità",
@ -294,7 +294,7 @@
"no-audit-logs-prompt": "Log non trovati",
"action-data": "Action data",
"failure-details": "Failure details",
"search": "Ricerca log audit",
"search": "Cerca log audit",
"clear-search": "Cancella ricerca"
},
"confirm-on-exit": {
@ -319,7 +319,7 @@
"password": "Password",
"enter-username": "Inserisci nome utente",
"enter-password": "Inserisci password",
"enter-search": "Inserisci ricerca"
"enter-search": "Cerca ..."
},
"content-type": {
"json": "Json",
@ -391,7 +391,7 @@
"unassign-from-customer": "Unassign from customer",
"make-public": "Rendi pubblica la dashboard",
"make-private": "Rendi privata la dashboard",
"manage-assigned-customers": "Gestisci i clienti assegnati",
"manage-assigned-customers": "Gestisci clienti assegnati",
"assigned-customers": "Clienti assegnati",
"assign-to-customers": "Assegna Dashboard ai Clienti",
"assign-to-customers-text": "Seleziona i clienti da assegnare alla/alle dashboard",
@ -407,7 +407,7 @@
"title-required": "Titolo obbligatorio.",
"description": "Descrizione",
"details": "Dettagli",
"dashboard-details": "Dettagli Dashboard",
"dashboard-details": "Dettagli dashboard",
"add-dashboard-text": "Aggiungi nuova dashboard",
"assign-dashboards": "Assegna dashboard",
"assign-new-dashboard": "Assegna nuova dashboard",
@ -472,7 +472,7 @@
"title-color": "Colore titolo",
"display-dashboards-selection": "Mostra selezione dashboard",
"display-entities-selection": "Mostra selezione entità",
"display-dashboard-timewindow": "Mostra finestra temporale",
"display-dashboard-timewindow": "Mostra intervallo temporale",
"display-dashboard-export": "Mostra esportazione",
"import": "Importa dashboard",
"export": "Esporta dashboard",
@ -498,25 +498,25 @@
"public-link": "Link pubblico",
"copy-public-link": "Copia link pubblico",
"public-link-copied-message": "Link pubblico della dashboard copiato negli appunti",
"manage-states": "Manage dashboard states",
"states": "Dashboard states",
"search-states": "Search dashboard states",
"selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selected",
"edit-state": "Edit dashboard state",
"delete-state": "Delete dashboard state",
"add-state": "Add dashboard state",
"state": "Dashboard state",
"manage-states": "Gestisci stati dashboard",
"states": "Stati dashboard",
"search-states": "Ricerca stati dashboard",
"selected-states": "{ count, plural, 1 {1 stato dashboard selezionato} other {# stati dashboard selezionati} }",
"edit-state": "Modifica stato dashboard",
"delete-state": "Elimina stato dashboard",
"add-state": "Aggiungi stato dashboard",
"state": "Stato dashboard",
"state-name": "Nome",
"state-name-required": "Dashboard state name is required.",
"state-id": "State Id",
"state-id-required": "Dashboard state id is required.",
"state-id-exists": "Dashboard state with the same id is already exists.",
"is-root-state": "Root state",
"delete-state-title": "Delete dashboard state",
"delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?",
"state-name-required": "Nome stato dashboard obbligatorio.",
"state-id": "Id stato",
"state-id-required": "Id stato dashboard obbligatorio.",
"state-id-exists": "Uno stato della dashboard con lo stesso id è già presente.",
"is-root-state": "Stato radice",
"delete-state-title": "Elimina stato dashboard",
"delete-state-text": "Sei sicuro di voler eliminare lo stato della dashboard di nome '{{stateName}}'?",
"show-details": "Mostra dettagli",
"hide-details": "Nascondi dettagli",
"select-state": "Select target state",
"select-state": "Seleziona stato target",
"state-controller": "Stato controller"
},
"datakey": {
@ -570,14 +570,14 @@
"alias-required": "Alias dispositivo richesto.",
"remove-alias": "Rimuovi alias dispositivo",
"add-alias": "Aggiungi alias dispositivo",
"name-starts-with": "Dispositivo il cui nome comincia per",
"name-starts-with": "Dispositivo il cui nome inizia per",
"device-list": "Lista dispositivi",
"use-device-name-filter": "Usa filtro",
"device-list-empty": "Nessun dispositivo selezionato.",
"device-name-filter-required": "Filtro nome dispositivo obbligatorio.",
"device-name-filter-no-device-matched": "Nessun dispositivo il cui nome inizia per '{{device}}' è stato trovato.",
"add": "Aggiungi Dispositivo",
"assign-to-customer": "Assigna al cliente",
"assign-to-customer": "Assegna al cliente",
"assign-device-to-customer": "Assegna dispositivo/dispositivi al Cliente",
"assign-device-to-customer-text": "Seleziona i dispositivi da assegnare al cliente",
"make-public": "Rendi pubblico il dispositivo",
@ -605,7 +605,7 @@
"delete-device-text": "Attenzione, dopo la conferma il dispositivo e tutti i suoi dati non saranno più recuperabili.",
"delete-devices-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 dispositivo} other {# dispositivi} }?",
"delete-devices-action-title": "Elimina { count, plural, 1 {1 dispositivo} other {# dispositivi} }",
"delete-devices-text": "Attenzione, dopo la conferma tutti i dispositivi selezionati saranno elimininati e i relativi dati non saranno più recuperabili.",
"delete-devices-text": "Attenzione, dopo la conferma tutti i dispositivi selezionati saranno eliminati e i relativi dati non saranno più recuperabili.",
"unassign-device-title": "Sei sicuro di voler annullare l'assegnazione del dispositivo '{{deviceName}}'?",
"unassign-device-text": "Dopo la conferma sarà annullata l'assegnazione del dispositivo e questo non sarà più accessibile dal cliente.",
"unassign-device": "Annulla assegnazione dispositivo",
@ -680,7 +680,7 @@
"entity-list-empty": "Nessuna entità selezionata.",
"entity-type-list-empty": "Nessun tipo di entità selezionato.",
"entity-name-filter-required": "Filtro nome entità obbligatorio.",
"entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
"entity-name-filter-no-entity-matched": "Nessuna entità che inizia per '{{entity}}' è stata trovata.",
"all-subtypes": "Tutte",
"select-entities": "Seleziona entità",
"no-aliases-found": "Nessun alias trovato.",
@ -733,7 +733,7 @@
"type-rulechains": "Rule chains",
"list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }",
"rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'",
"type-current-customer": "Current Customer",
"type-current-customer": "Cliente attuale",
"search": "Ricerca entità",
"selected-entities": "{ count, plural, 1 {1 entità selezionata} other {# entità selezionate} }",
"entity-name": "Nome entità",
@ -788,13 +788,13 @@
"delete-extension-text": "Attenzione, dopo la conferma l'estensione e tutti i suoi data non saranno più recuperabili.",
"delete-extensions-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 estensione} other {# estensioni} }?",
"delete-extensions-text": "Attenzione, dopo la conferma tutte le estensioni selezionate saranno eliminate.",
"converters": "Converters",
"converter-id": "Converter id",
"converters": "Convertitori",
"converter-id": "Id convertitore",
"configuration": "Configurazione",
"converter-configurations": "Converter configurations",
"converter-configurations": "Configurazioni convertitore",
"token": "Token di sicurezza",
"add-converter": "Add converter",
"add-config": "Add converter configuration",
"add-converter": "Aggiungi convertitore",
"add-config": "Aggiungi configurazione convertitore",
"device-name-expression": "Device name expression",
"device-type-expression": "Device type expression",
"custom": "Custom",
@ -828,7 +828,7 @@
"drop-file": "Trascina un file o fai clic per selezionare un file da caricare.",
"mapping": "Mapping",
"topic-filter": "Filtro topic",
"converter-type": "Converter type",
"converter-type": "Tipo convertitore",
"converter-json": "Json",
"json-name-expression": "Device name json expression",
"topic-name-expression": "Device name topic expression",
@ -847,10 +847,10 @@
"converter-json-required": "Convertitore json obbligatorio.",
"converter-json-parse": "Unable to parse converter json.",
"filter-expression": "Filter expression",
"connect-requests": "Connect requests",
"add-connect-request": "Add connect request",
"disconnect-requests": "Disconnect requests",
"add-disconnect-request": "Add disconnect request",
"connect-requests": "Richieste di connessione",
"add-connect-request": "Aggiungi richiesta di connessione",
"disconnect-requests": "Richieste di disconnessione",
"add-disconnect-request": "Aggiungi richiesta di disconnessione",
"attribute-requests": "Attribute requests",
"add-attribute-request": "Add attribute request",
"attribute-updates": "Attribute updates",
@ -919,8 +919,8 @@
"not-available": "Non disponibile"
},
"export-extensions-configuration": "Export extensions configuration",
"import-extensions-configuration": "Import extensions configuration",
"export-extensions-configuration": "Esporta configurazione estensioni",
"import-extensions-configuration": "Importa configurazione estensioni",
"import-extensions": "Importa estensione",
"import-extension": "Importa estensione",
"export-extension": "Esporta estensione",
@ -928,7 +928,7 @@
"invalid-file-error": "File estensione non valido"
},
"fullscreen": {
"expand": "Expand to fullscreen",
"expand": "Espandi a tutto schermo",
"exit": "Esci da schermo intero",
"toggle": "Commuta modalità schermo intero",
"fullscreen": "Schermo intero"
@ -937,17 +937,17 @@
"function": "Funzione"
},
"grid": {
"delete-item-title": "Are you sure you want to delete this item?",
"delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.",
"delete-items-title": "Are you sure you want to delete { count, plural, 1 {1 item} other {# items} }?",
"delete-items-action-title": "Delete { count, plural, 1 {1 item} other {# items} }",
"delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.",
"add-item-text": "Add new item",
"no-items-text": "No items found",
"item-details": "Item details",
"delete-item": "Delete Item",
"delete-items": "Delete Items",
"scroll-to-top": "Scroll to top"
"delete-item-title": "Sei sicuro di voler eliminare questo elemento?",
"delete-item-text": "Attenzione, dopo la conferma questo elemento e tutti i suoi dati non saranno più recuperabili.",
"delete-items-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 elemento} other {# elementi} }?",
"delete-items-action-title": "Elimina { count, plural, 1 {1 elemento} other {# elementi} }",
"delete-items-text": "Attenzione, dopo la conferma tutti gli elementi selezionati saranno rimossi e i relativi dati non saranno più recuperabili.",
"add-item-text": "Aggiungi nuovo elemento",
"no-items-text": "Nessun elemento trovato",
"item-details": "Dettagli elemento",
"delete-item": "Elimina elemento",
"delete-items": "Elimina elementi",
"scroll-to-top": "Scorri verso l'alto"
},
"help": {
"goto-help-page": "Go to help page"
@ -985,7 +985,7 @@
"settings": "Impostazioni layout",
"color": "Colore",
"main": "Main",
"right": "Right",
"right": "Destra",
"select": "Select target layout"
},
"legend": {
@ -1030,7 +1030,7 @@
},
"relation": {
"relations": "Relations",
"direction": "Direction",
"direction": "Direzione",
"search-direction": {
"FROM": "Da",
"TO": "A"
@ -1072,39 +1072,39 @@
},
"rulechain": {
"rulechain": "Rule chain",
"rulechains": "Rule chains",
"rulechains": "Rule chain",
"root": "Root",
"delete": "Delete rule chain",
"delete": "Cancella rule chain",
"name": "Nome",
"name-required": "Nome obbligatorio.",
"description": "Descrizione",
"add": "Add Rule Chain",
"add": "Aggiungi Rule Chain",
"set-root": "Make rule chain root",
"set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
"set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
"delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
"delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
"delete-rulechains-title": "Are you sure you want to delete { count, plural, 1 {1 rule chain} other {# rule chains} }?",
"delete-rulechains-action-title": "Delete { count, plural, 1 {1 rule chain} other {# rule chains} }",
"delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
"add-rulechain-text": "Add new rule chain",
"no-rulechains-text": "No rule chains found",
"rulechain-details": "Rule chain details",
"delete-rulechain-title": "Sei sicuro di voler eliminare la rule chain '{{ruleChainName}}'?",
"delete-rulechain-text": "Attenzione, dopo la conferma la rule chain e tutti i dati relativi non saranno più recuperabili.",
"delete-rulechains-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 rule chain} other {# rule chain} }?",
"delete-rulechains-action-title": "Elimina { count, plural, 1 {1 rule chain} other {# rule chain} }",
"delete-rulechains-text": "Attenzione, dopo la conferma tutte le rule chain selezionate saranno rimosse e tutti i relativi dati non saranno più recuperabili.",
"add-rulechain-text": "Aggiungi nuova rule chain",
"no-rulechains-text": "Nessuna rule chain trovata",
"rulechain-details": "Dettagli rule chain",
"details": "Dettagli",
"events": "Eventi",
"system": "Sistema",
"import": "Import rule chain",
"export": "Export rule chain",
"export-failed-error": "Unable to export rule chain: {{error}}",
"create-new-rulechain": "Create new rule chain",
"rulechain-file": "Rule chain file",
"invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
"copyId": "Copy rule chain Id",
"idCopiedMessage": "Rule chain Id has been copied to clipboard",
"select-rulechain": "Select rule chain",
"no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
"rulechain-required": "Rule chain is required",
"management": "Rules management",
"import": "Importa rule chain",
"export": "Esporta rule chain",
"export-failed-error": "Impossibile esportare rule chain: {{error}}",
"create-new-rulechain": "Crea nuova rule chain",
"rulechain-file": "File rule chain",
"invalid-rulechain-file-error": "Impossibile importare rule chain: struttura dati rule chain non valida.",
"copyId": "Copia Id rule chain",
"idCopiedMessage": "Id rule chain copiato negli appunti",
"select-rulechain": "Seleziona rule chain",
"no-rulechains-matching": "Nessuna rule chain corrispondente a '{{entity}}' è stata trovata.",
"rulechain-required": "Rule chain obbligatoria",
"management": "Gestione regole",
"debug-mode": "Modalità debug"
},
"rulenode": {
@ -1209,10 +1209,10 @@
"history": "Cronologia",
"last-prefix": "ultimo",
"period": "from {{ startTime }} to {{ endTime }}",
"edit": "Edit timewindow",
"date-range": "Date range",
"edit": "Modifica intervallo temporale",
"date-range": "Intervallo date",
"last": "Ultimo",
"time-period": "Time period"
"time-period": "Intervallo temporale"
},
"user": {
"user": "Utente",
@ -1379,10 +1379,10 @@
"mobile-mode-settings": "Impostazioni modalità mobile",
"order": "Ordinamento",
"height": "Altezza",
"units": "Simbolo speciale da mostrare vicino al valore",
"units": "Simbolo speciale da mostrare accanto al valore",
"decimals": "Numero di cifre decimali",
"timewindow": "Timewindow",
"use-dashboard-timewindow": "Use dashboard timewindow",
"timewindow": "Intervallo temporale",
"use-dashboard-timewindow": "Usa intervallo temporale dashboard",
"display-legend": "Mostra legenda",
"datasources": "Sorgenti dei dati",
"maximum-datasources": "Massimo { count, plural, 1 {1 sorgente dati consentita.} other {# sorgenti dati consentite} }",
@ -1434,12 +1434,14 @@
"language": {
"language": "Lingua",
"locales": {
"fr_FR": "Francese",
"zh_CN": "Cinese",
"ko_KR": "Coreano",
"en_US": "Inglese",
"it_IT": "Italiano",
"ru_RU": "Russo",
"es_ES": "Spagnolo"
"es_ES": "Spagnolo",
"ja_JA": "Giapponese"
}
}
}

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

File diff suppressed because it is too large

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

@ -1329,11 +1329,13 @@
"language": "언어",
"locales": {
"en_US": "영어",
"fr_FR": "프랑스의",
"ko_KR": "한글",
"zh_CN": "중국어",
"ru_RU": "러시아어",
"es_ES": "스페인어",
"it_IT": "이탈리아 사람"
"it_IT": "이탈리아 사람",
"ja_JA": "일본어"
}
}
}
}

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

@ -1354,12 +1354,14 @@
"language": "Язык",
"locales": {
"en_US": "Английский",
"fr_FR": "Французский",
"zh_CN": "Китайский",
"ko_KR": "Корейский",
"es_ES": "Испанский",
"it_IT": "Итальянский",
"ru_RU": "Русский"
"ru_RU": "Русский",
"ja_JA": "Японский"
}
}
}
}

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

@ -1438,11 +1438,13 @@
"language": "语言",
"locales": {
"en_US": "英语",
"fr_FR": "法国",
"ko_KR": "韩语",
"zh_CN": "汉语",
"ru_RU": "俄语",
"es_ES": "西班牙语",
"it_IT": "意大利"
"it_IT": "意大利",
"ja_JA": "日本"
}
}
}

3
ui/src/app/user/user-fieldset.tpl.html

@ -21,6 +21,9 @@
<md-button ng-click="onResendActivation({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{
'user.resend-activation' | translate }}
</md-button>
<md-button ng-click="onLoginAsUser({event: $event})" ng-show="!isEdit && loginAsUserEnabled" class="md-raised md-primary">{{
(isTenantAdmin() ? 'user.login-as-tenant-admin' : 'user.login-as-customer-user') | translate }}
</md-button>
<md-button ng-click="onDeleteUser({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'user.delete' |
translate }}
</md-button>

16
ui/src/app/user/user.controller.js

@ -30,6 +30,17 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
var usersType = $state.$current.data.usersType;
var userActionsList = [
{
onAction: function ($event, item) {
loginAsUser(item);
},
name: function() { return $translate.instant('login.login') },
details: function() { return $translate.instant(usersType === 'tenant' ? 'user.login-as-tenant-admin' : 'user.login-as-customer-user') },
icon: "login",
isEnabled: function() {
return userService.isUserTokenAccessEnabled();
}
},
{
onAction: function ($event, item) {
vm.grid.deleteItem($event, item);
@ -78,6 +89,7 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
vm.displayActivationLink = displayActivationLink;
vm.resendActivation = resendActivation;
vm.loginAsUser = loginAsUser;
initController();
@ -184,4 +196,8 @@ export default function UserController(userService, toast, $scope, $mdDialog, $d
toast.showSuccess($translate.instant('user.activation-email-sent-message'));
});
}
function loginAsUser(user) {
userService.loginAsUser(user.id.id);
}
}

9
ui/src/app/user/user.directive.js

@ -22,18 +22,20 @@ import userFieldsetTemplate from './user-fieldset.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
export default function UserDirective($compile, $templateCache/*, dashboardService*/) {
export default function UserDirective($compile, $templateCache, userService) {
var linker = function (scope, element) {
var template = $templateCache.get(userFieldsetTemplate);
element.html(template);
scope.isTenantAdmin = function() {
return scope.user && scope.user.authority === 'TENANT_ADMIN';
}
};
scope.isCustomerUser = function() {
return scope.user && scope.user.authority === 'CUSTOMER_USER';
}
};
scope.loginAsUserEnabled = userService.isUserTokenAccessEnabled();
$compile(element.contents())(scope);
}
@ -46,6 +48,7 @@ export default function UserDirective($compile, $templateCache/*, dashboardServi
theForm: '=',
onDisplayActivationLink: '&',
onResendActivation: '&',
onLoginAsUser: '&',
onDeleteUser: '&'
}
};

1
ui/src/app/user/users.tpl.html

@ -27,6 +27,7 @@
the-form="vm.grid.detailsForm"
on-display-activation-link="vm.displayActivationLink(event, vm.grid.detailsConfig.currentItem)"
on-resend-activation="vm.resendActivation(vm.grid.detailsConfig.currentItem)"
on-login-as-user="vm.loginAsUser(vm.grid.detailsConfig.currentItem)"
on-delete-user="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-user>
</md-tab>
<md-tab ng-if="!vm.grid.detailsConfig.isDetailsEditMode && vm.grid.isTenantAdmin()" md-on-select="vm.grid.triggerResize()" label="{{ 'audit-log.audit-logs' | translate }}">

Loading…
Cancel
Save