Browse Source

Conflicts resolved

pull/1070/head
viktorbasanets 8 years ago
parent
commit
b02503a455
  1. 1
      application/src/main/java/org/thingsboard/server/actors/app/AppActor.java
  2. 2
      application/src/main/java/org/thingsboard/server/controller/DeviceController.java
  3. 7
      application/src/main/java/org/thingsboard/server/controller/TelemetryController.java
  4. 59
      application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java
  5. 2
      application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java
  6. 26
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java
  7. 8
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java
  8. 2
      common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java
  9. 35
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java
  10. 45
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java
  11. 12
      common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java
  12. 22
      common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java
  13. 28
      common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQuery.java
  14. 6
      common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java
  15. 127
      dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java
  16. 39
      dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java
  17. 45
      dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java
  18. 66
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  19. 292
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java
  20. 60
      dao/src/main/java/org/thingsboard/server/dao/timeseries/QueryCursor.java
  21. 11
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java
  22. 7
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  23. 46
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java
  24. 13
      dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java
  25. 36
      dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java
  26. 36
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java
  27. 4
      netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java
  28. 5
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java
  29. 164
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
  30. 57
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java
  31. 8
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java
  32. 6
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  33. 292
      ui/.stylelintrc
  34. 12
      ui/package.json
  35. 10
      ui/src/app/alarm/alarm-details-dialog.scss
  36. 23
      ui/src/app/alarm/alarm.scss
  37. 12
      ui/src/app/audit/audit-log-details-dialog.scss
  38. 27
      ui/src/app/audit/audit-log.scss
  39. 5
      ui/src/app/components/dashboard-autocomplete.scss
  40. 30
      ui/src/app/components/dashboard-select.scss
  41. 36
      ui/src/app/components/dashboard.scss
  42. 2
      ui/src/app/components/datakey-config.scss
  43. 16
      ui/src/app/components/datasource-entity.scss
  44. 11
      ui/src/app/components/datasource-func.scss
  45. 41
      ui/src/app/components/datasource.scss
  46. 7
      ui/src/app/components/datetime-period.scss
  47. 34
      ui/src/app/components/details-sidenav.scss
  48. 5
      ui/src/app/components/entity-alias-select.scss
  49. 29
      ui/src/app/components/expand-fullscreen.scss
  50. 13
      ui/src/app/components/grid.scss
  51. 23
      ui/src/app/components/js-func.scss
  52. 20
      ui/src/app/components/json-content.scss
  53. 5
      ui/src/app/components/json-form.scss
  54. 10
      ui/src/app/components/json-object-edit.scss
  55. 8
      ui/src/app/components/kv-map.scss
  56. 22
      ui/src/app/components/legend-config.scss
  57. 26
      ui/src/app/components/legend.scss
  58. 22
      ui/src/app/components/material-icon-select.scss
  59. 14
      ui/src/app/components/material-icons-dialog.scss
  60. 6
      ui/src/app/components/menu-link.scss
  61. 55
      ui/src/app/components/react/json-form-ace-editor.scss
  62. 14
      ui/src/app/components/react/json-form-color.scss
  63. 91
      ui/src/app/components/react/json-form-image.scss
  64. 121
      ui/src/app/components/react/json-form.scss
  65. 5
      ui/src/app/components/related-entity-autocomplete.scss
  66. 39
      ui/src/app/components/side-menu.scss
  67. 11
      ui/src/app/components/timeinterval.scss
  68. 37
      ui/src/app/components/timewindow.scss
  69. 7
      ui/src/app/components/widget/action/manage-widget-actions.scss
  70. 4
      ui/src/app/components/widget/widget-config.scss
  71. 11
      ui/src/app/components/widget/widget.scss
  72. 28
      ui/src/app/components/widgets-bundle-select.scss
  73. 3
      ui/src/app/dashboard/dashboard-card.scss
  74. 26
      ui/src/app/dashboard/dashboard-settings.scss
  75. 81
      ui/src/app/dashboard/dashboard-toolbar.scss
  76. 79
      ui/src/app/dashboard/dashboard.scss
  77. 5
      ui/src/app/dashboard/states/default-state-controller.scss
  78. 53
      ui/src/app/dashboard/states/entity-state-controller.scss
  79. 9
      ui/src/app/dashboard/states/manage-dashboard-states.scss
  80. 29
      ui/src/app/entity/alias/aliases-entity-select.scss
  81. 5
      ui/src/app/entity/alias/entity-alias-dialog.scss
  82. 18
      ui/src/app/entity/alias/entity-aliases.scss
  83. 30
      ui/src/app/entity/attribute/attribute-table.scss
  84. 2
      ui/src/app/entity/entity-autocomplete.scss
  85. 11
      ui/src/app/entity/entity-filter-view.scss
  86. 8
      ui/src/app/entity/entity-filter.scss
  87. 7
      ui/src/app/entity/entity-list.scss
  88. 6
      ui/src/app/entity/entity-select.scss
  89. 2
      ui/src/app/entity/entity-subtype-autocomplete.scss
  90. 7
      ui/src/app/entity/entity-subtype-list.scss
  91. 1
      ui/src/app/entity/entity-subtype-select.scss
  92. 7
      ui/src/app/entity/entity-type-list.scss
  93. 3
      ui/src/app/entity/entity-type-select.scss
  94. 10
      ui/src/app/entity/relation/relation-dialog.scss
  95. 29
      ui/src/app/entity/relation/relation-filters.scss
  96. 4
      ui/src/app/entity/relation/relation-table.scss
  97. 2
      ui/src/app/entity/relation/relation-type-autocomplete.scss
  98. 75
      ui/src/app/event/event.scss
  99. 21
      ui/src/app/extension/extension-table.scss
  100. 32
      ui/src/app/extension/extensions-forms/extension-form.scss

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/controller/DeviceController.java

@ -76,7 +76,7 @@ public class DeviceController extends BaseController {
device.setTenantId(getCurrentUser().getTenantId());
if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) {
if (device.getId() == null || device.getId().isNullUid() ||
device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
throw new ThingsboardException("You don't have permission to perform this operation!",
ThingsboardErrorCode.PERMISSION_DENIED);
} else {

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

@ -49,15 +49,15 @@ import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKey;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
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.ReadTsKvQuery;
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.core.TelemetryUploadRequest;
import org.thingsboard.server.common.transport.adaptor.JsonConverter;
@ -81,7 +81,6 @@ import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.stream.Collectors;
@ -201,7 +200,7 @@ public class TelemetryController extends BaseController {
(result, entityId) -> {
// If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
List<TsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, interval, limit, agg))
List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
.collect(Collectors.toList());
Futures.addCallback(tsService.findAll(entityId, queries), getTsKvListCallback(result));

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);
}
}

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

@ -24,26 +24,33 @@ 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.EntityView;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
//<<<<<<< HEAD
import org.thingsboard.server.common.data.id.EntityViewId;
//=======
import org.thingsboard.server.common.data.id.TenantId;
//>>>>>>> d909192071880b7af2137333142bc62ece369ec1
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
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.ReadTsKvQuery;
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.entityview.EntityViewService;
@ -108,6 +115,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Lazy
private DeviceStateService stateService;
@Autowired
@Lazy
private ActorService actorService;
private ExecutorService tsCallBackExecutor;
private ExecutorService wsCallBackExecutor;
@ -212,6 +223,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;
@ -364,9 +382,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor);
} else if (subscription.getType() == TelemetryFeature.TIMESERIES) {
long curTs = System.currentTimeMillis();
List<TsKvQuery> queries = new ArrayList<>();
List<ReadTsKvQuery> queries = new ArrayList<>();
subscription.getKeyStates().entrySet().forEach(e -> {
queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
});
DonAsynchron.withCallback(tsService.findAll(entityId, queries),

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

@ -30,10 +30,10 @@ import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.dao.attributes.AttributesService;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.service.security.AccessValidator;
@ -251,7 +251,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
}
EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
.collect(Collectors.toList());
FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() {
@ -337,7 +337,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), entityId);
startTs = cmd.getStartTs();
long endTs = cmd.getStartTs() + cmd.getTimeWindow();
List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(),
List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, cmd.getInterval(),
getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys);

2
common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java

@ -1,4 +1,4 @@
/**
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");

35
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.kv;
import lombok.Data;
@Data
public class BaseDeleteTsKvQuery extends BaseTsKvQuery implements DeleteTsKvQuery {
private final Boolean rewriteLatestIfDeleted;
public BaseDeleteTsKvQuery(String key, long startTs, long endTs, boolean rewriteLatestIfDeleted) {
super(key, startTs, endTs);
this.rewriteLatestIfDeleted = rewriteLatestIfDeleted;
}
public BaseDeleteTsKvQuery(String key, long startTs, long endTs) {
this(key, startTs, endTs, false);
}
}

45
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java

@ -0,0 +1,45 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.common.data.kv;
import lombok.Data;
@Data
public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery {
private final long interval;
private final int limit;
private final Aggregation aggregation;
private final String orderBy;
public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation) {
this(key, startTs, endTs, interval, limit, aggregation, "DESC");
}
public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation,
String orderBy) {
super(key, startTs, endTs);
this.interval = interval;
this.limit = limit;
this.aggregation = aggregation;
this.orderBy = orderBy;
}
public BaseReadTsKvQuery(String key, long startTs, long endTs) {
this(key, startTs, endTs, endTs - startTs, 1, Aggregation.AVG, "DESC");
}
}

12
common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java

@ -23,21 +23,11 @@ public class BaseTsKvQuery implements TsKvQuery {
private final String key;
private final long startTs;
private final long endTs;
private final long interval;
private final int limit;
private final Aggregation aggregation;
public BaseTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation) {
public BaseTsKvQuery(String key, long startTs, long endTs) {
this.key = key;
this.startTs = startTs;
this.endTs = endTs;
this.interval = interval;
this.limit = limit;
this.aggregation = aggregation;
}
public BaseTsKvQuery(String key, long startTs, long endTs) {
this(key, startTs, endTs, endTs-startTs, 1, Aggregation.AVG);
}
}

22
common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java

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

28
common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQuery.java

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

6
common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java

@ -23,10 +23,4 @@ public interface TsKvQuery {
long getEndTs();
long getInterval();
int getLimit();
Aggregation getAggregation();
}

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 {

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

@ -31,9 +31,10 @@ import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
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.DaoUtil;
import org.thingsboard.server.dao.model.sql.TsKvEntity;
import org.thingsboard.server.dao.model.sql.TsKvLatestCompositeKey;
@ -102,7 +103,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
}
@Override
public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries
.stream()
.map(query -> findAllAsync(entityId, query))
@ -121,7 +122,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
}, service);
}
private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(entityId, query);
} else {
@ -228,7 +229,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
});
}
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
return Futures.immediateFuture(
DaoUtil.convertDataList(
tsKvRepository.findAllWithLimit(
@ -306,6 +307,36 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
});
}
@Override
public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
return service.submit(() -> {
tsKvRepository.delete(
fromTimeUUID(entityId.getId()),
entityId.getEntityType(),
query.getKey(),
query.getStartTs(),
query.getEndTs());
return null;
});
}
@Override
public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
TsKvLatestEntity latestEntity = new TsKvLatestEntity();
latestEntity.setEntityType(entityId.getEntityType());
latestEntity.setEntityId(fromTimeUUID(entityId.getId()));
latestEntity.setKey(query.getKey());
return service.submit(() -> {
tsKvLatestRepository.delete(latestEntity);
return null;
});
}
@Override
public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
return service.submit(() -> null);
}
@PreDestroy
void onDestroy() {
if (insertService != null) {

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

@ -16,10 +16,12 @@
package org.thingsboard.server.dao.sql.timeseries;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.scheduling.annotation.Async;
import org.springframework.transaction.annotation.Transactional;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.dao.model.sql.TsKvCompositeKey;
import org.thingsboard.server.dao.model.sql.TsKvEntity;
@ -41,6 +43,17 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Param("endTs") long endTs,
Pageable pageable);
@Transactional
@Modifying
@Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
"AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
"AND tskv.ts > :startTs AND tskv.ts < :endTs")
void delete(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String key,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(MAX(tskv.strValue), MAX(tskv.longValue), MAX(tskv.doubleValue)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
@ -56,30 +69,30 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
"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,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(COUNT(tskv.booleanValue), COUNT(tskv.strValue), 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> findCount(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(AVG(tskv.longValue), AVG(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,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Async
@ -87,8 +100,8 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findSum(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
@Param("entityType") EntityType entityType,
@Param("entityKey") String entityKey,
@Param("startTs") long startTs,
@Param("endTs") long endTs);
}

66
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -25,9 +25,10 @@ import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.Validator;
@ -46,6 +47,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
public class BaseTimeseriesService implements TimeseriesService {
public static final int INSERTS_PER_ENTRY = 3;
public static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY;
@Autowired
private TimeseriesDao timeseriesDao;
@ -54,9 +56,9 @@ public class BaseTimeseriesService implements TimeseriesService {
private EntityViewService entityViewService;
@Override
public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries) {
public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) {
validate(entityId);
queries.forEach(query -> validate(query));
queries.forEach(BaseTimeseriesService::validate);
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries));
@ -69,13 +71,14 @@ public class BaseTimeseriesService implements TimeseriesService {
validate(entityId);
List<ListenableFuture<TsKvEntry>> futures = Lists.newArrayListWithExpectedSize(keys.size());
keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key));
if (false/*entityId.getEntityType().equals(EntityType.ENTITY_VIEW)*/) {
if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
Collection<String> newKeys = chooseKeysForEntityView(entityView, keys);
newKeys.forEach(newKey -> futures.add(timeseriesDao.findLatest(entityView.getEntityId(), newKey)));
} else {
keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
Collection<String> matchingKeys = chooseKeysForEntityView(entityView, keys);
List<ReadTsKvQuery> queries = new ArrayList<>();
matchingKeys.forEach(key -> queries.add(new BaseReadTsKvQuery(key, entityView.getStartTs(), entityView.getEndTs())));
return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries));
}
keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
return Futures.allAsList(futures);
}
@ -129,8 +132,8 @@ public class BaseTimeseriesService implements TimeseriesService {
futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl));
}
private List<TsKvQuery> updateQueriesForEntityView(EntityView entityView, List<TsKvQuery> queries) {
List<TsKvQuery> newQueries = new ArrayList<>();
private List<ReadTsKvQuery> updateQueriesForEntityView(EntityView entityView, List<ReadTsKvQuery> queries) {
List<ReadTsKvQuery> newQueries = new ArrayList<>();
entityView.getKeys().getTimeseries()
.forEach(viewKey -> queries
.forEach(query -> {
@ -148,7 +151,6 @@ public class BaseTimeseriesService implements TimeseriesService {
return newQueries;
}
@Deprecated /*Will be a modified*/
private Collection<String> chooseKeysForEntityView(EntityView entityView, Collection<String> keys) {
Collection<String> newKeys = new ArrayList<>();
entityView.getKeys().getTimeseries()
@ -156,27 +158,53 @@ public class BaseTimeseriesService implements TimeseriesService {
.forEach(key -> {
if (key.equals(viewKey)) {
newKeys.add(key);
}}));
}
}));
return newKeys;
}
@Override
public ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
validate(entityId);
deleteTsKvQueries.forEach(BaseTimeseriesService::validate);
List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY);
for (DeleteTsKvQuery tsKvQuery : deleteTsKvQueries) {
deleteAndRegisterFutures(futures, entityId, tsKvQuery);
}
return Futures.allAsList(futures);
}
private void deleteAndRegisterFutures(List<ListenableFuture<Void>> futures, EntityId entityId, DeleteTsKvQuery query) {
futures.add(timeseriesDao.remove(entityId, query));
futures.add(timeseriesDao.removeLatest(entityId, query));
futures.add(timeseriesDao.removePartition(entityId, query));
}
private static void validate(EntityId entityId) {
Validator.validateEntityId(entityId, "Incorrect entityId " + entityId);
}
private static void validate(TsKvQuery query) {
private static void validate(ReadTsKvQuery query) {
if (query == null) {
throw new IncorrectParameterException("TsKvQuery can't be null");
throw new IncorrectParameterException("ReadTsKvQuery can't be null");
} else if (isBlank(query.getKey())) {
throw new IncorrectParameterException("Incorrect TsKvQuery. Key can't be empty");
throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Key can't be empty");
} else if (query.getAggregation() == null) {
throw new IncorrectParameterException("Incorrect TsKvQuery. Aggregation can't be empty");
throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty");
}
}
private static void validate(DeleteTsKvQuery query) {
if (query == null) {
throw new IncorrectParameterException("DeleteTsKvQuery can't be null");
} else if (isBlank(query.getKey())) {
throw new IncorrectParameterException("Incorrect DeleteTsKvQuery. Key can't be empty");
}
}
private static TsKvQuery updateQuery(Long startTs, Long endTs, String viewKey, TsKvQuery query) {
private static ReadTsKvQuery updateQuery(Long startTs, Long endTs, String viewKey, ReadTsKvQuery query) {
return startTs <= query.getStartTs() && endTs >= query.getEndTs() ? query :
new BaseTsKvQuery(viewKey, startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
new BaseReadTsKvQuery(viewKey, startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
}
private static void checkForNonEntityView(EntityId entityId) throws Exception {

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

@ -20,6 +20,7 @@ import com.datastax.driver.core.PreparedStatement;
import com.datastax.driver.core.ResultSet;
import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.Row;
import com.datastax.driver.core.Statement;
import com.datastax.driver.core.querybuilder.QueryBuilder;
import com.datastax.driver.core.querybuilder.Select;
import com.google.common.base.Function;
@ -34,16 +35,17 @@ 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.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DataType;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
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.ReadTsKvQuery;
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;
@ -54,11 +56,11 @@ import javax.annotation.PreDestroy;
import java.time.Instant;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ArrayList;
import java.util.Optional;
import java.util.Collections;
import java.util.stream.Collectors;
import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
@ -76,8 +78,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
public static final String GENERATED_QUERY_FOR_ENTITY_TYPE_AND_ENTITY_ID = "Generated query [{}] for entityType {} and entityId {}";
public static final String SELECT_PREFIX = "SELECT ";
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
@ -96,9 +98,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
private PreparedStatement latestInsertStmt;
private PreparedStatement[] saveStmts;
private PreparedStatement[] saveTtlStmts;
private PreparedStatement[] fetchStmts;
private PreparedStatement[] fetchStmtsAsc;
private PreparedStatement[] fetchStmtsDesc;
private PreparedStatement findLatestStmt;
private PreparedStatement findAllLatestStmt;
private PreparedStatement deleteStmt;
private PreparedStatement deletePartitionStmt;
private boolean isInstall() {
return environment.acceptsProfiles("install");
@ -108,7 +113,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
public void init() {
super.startExecutor();
if (!isInstall()) {
getFetchStmt(Aggregation.NONE);
getFetchStmt(Aggregation.NONE, DESC_ORDER);
Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning);
if (partition.isPresent()) {
tsFormat = partition.get();
@ -125,7 +130,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
@Override
public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityId, query)).collect(Collectors.toList());
return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() {
@Nullable
@ -142,7 +147,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(entityId, query);
} else {
@ -152,7 +157,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
while (stepTs < query.getEndTs()) {
long startTs = stepTs;
long endTs = stepTs + step;
TsKvQuery subQuery = new BaseTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation());
ReadTsKvQuery subQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation(), query.getOrderBy());
futures.add(findAndAggregateAsync(entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs)));
stepTs = endTs;
}
@ -171,7 +176,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return tsFormat.getTruncateUnit().equals(TsPartitionDate.EPOCH_START);
}
private ListenableFuture<List<Long>> getPartitionsFuture(TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
private ListenableFuture<List<Long>> getPartitionsFuture(ReadTsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
if (isFixedPartitioning()) { //no need to fetch partitions from DB
return Futures.immediateFuture(FIXED_PARTITION);
}
@ -179,11 +184,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
}
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery 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<>();
@ -212,7 +215,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
if (cursor.isFull() || !cursor.hasNextPartition()) {
resultFuture.set(cursor.getData());
} else {
PreparedStatement proto = getFetchStmt(Aggregation.NONE);
PreparedStatement proto = getFetchStmt(Aggregation.NONE, cursor.getOrderBy());
BoundStatement stmt = proto.bind();
stmt.setString(0, cursor.getEntityType());
stmt.setUUID(1, cursor.getEntityId());
@ -237,14 +240,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
}
private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, TsKvQuery query, long minPartition, long maxPartition) {
private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, ReadTsKvQuery query, long minPartition, long maxPartition) {
final Aggregation aggregation = query.getAggregation();
final String key = query.getKey();
final long startTs = query.getStartTs();
final long endTs = query.getEndTs();
final long ts = startTs + (endTs - startTs) / 2;
ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
ListenableFuture<List<ResultSet>> aggregationChunks = Futures.transformAsync(partitionsListFuture,
getFetchChunksAsyncFunction(entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor);
@ -260,7 +261,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
private AsyncFunction<List<Long>, List<ResultSet>> getFetchChunksAsyncFunction(EntityId entityId, String key, Aggregation aggregation, long startTs, long endTs) {
return partitions -> {
try {
PreparedStatement proto = getFetchStmt(aggregation);
PreparedStatement proto = getFetchStmt(aggregation, DESC_ORDER);
List<ResultSetFuture> futures = new ArrayList<>(partitions.size());
for (Long partition : partitions) {
log.trace("Fetching data for partition [{}] for entityType {} and entityId {}", partition, entityId.getEntityType(), entityId.getId());
@ -363,6 +364,204 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return getFuture(executeAsyncWrite(stmt), rs -> null);
}
@Override
public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
long minPartition = toPartitionTs(query.getStartTs());
long maxPartition = toPartitionTs(query.getEndTs());
ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
@Override
public void onSuccess(@Nullable List<Long> partitions) {
QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitions);
deleteAsync(cursor, resultFuture);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
}
}, readResultsProcessingExecutor);
return resultFuture;
}
private void deleteAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
if (!cursor.hasNextPartition()) {
resultFuture.set(null);
} else {
PreparedStatement proto = getDeleteStmt();
BoundStatement stmt = proto.bind();
stmt.setString(0, cursor.getEntityType());
stmt.setUUID(1, cursor.getEntityId());
stmt.setString(2, cursor.getKey());
stmt.setLong(3, cursor.getNextPartition());
stmt.setLong(4, cursor.getStartTs());
stmt.setLong(5, cursor.getEndTs());
Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
@Override
public void onSuccess(@Nullable ResultSet result) {
deleteAsync(cursor, resultFuture);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
}
}, readResultsProcessingExecutor);
}
}
private PreparedStatement getDeleteStmt() {
if (deleteStmt == null) {
deleteStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_CF +
" WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " > ? "
+ "AND " + ModelConstants.TS_COLUMN + " <= ?");
}
return deleteStmt;
}
@Override
public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(entityId, query.getKey());
ListenableFuture<Boolean> booleanFuture = Futures.transformAsync(latestEntryFuture, latestEntry -> {
long ts = latestEntry.getTs();
if (ts >= query.getStartTs() && ts <= query.getEndTs()) {
return Futures.immediateFuture(true);
} else {
log.trace("Won't be deleted latest value for [{}], key - {}", entityId, query.getKey());
}
return Futures.immediateFuture(false);
}, readResultsProcessingExecutor);
ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
if (isRemove) {
return deleteLatest(entityId, query.getKey());
}
return Futures.immediateFuture(null);
}, readResultsProcessingExecutor);
if (query.getRewriteLatestIfDeleted()) {
ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
if (isRemove) {
return getNewLatestEntryFuture(entityId, query);
}
return Futures.immediateFuture(null);
}, readResultsProcessingExecutor);
return Futures.transformAsync(Futures.allAsList(Arrays.asList(savedLatestFuture, removedLatestFuture)),
list -> Futures.immediateFuture(null), readResultsProcessingExecutor);
}
return removedLatestFuture;
}
private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
long startTs = 0;
long endTs = query.getStartTs() - 1;
ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1,
Aggregation.NONE, DESC_ORDER);
ListenableFuture<List<TsKvEntry>> future = findAllAsync(entityId, findNewLatestQuery);
return Futures.transformAsync(future, entryList -> {
if (entryList.size() == 1) {
return saveLatest(entityId, entryList.get(0));
} else {
log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey());
}
return Futures.immediateFuture(null);
}, readResultsProcessingExecutor);
}
private ListenableFuture<Void> deleteLatest(EntityId entityId, String key) {
Statement delete = QueryBuilder.delete().all().from(ModelConstants.TS_KV_LATEST_CF)
.where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityId.getEntityType()))
.and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId.getId()))
.and(eq(ModelConstants.KEY_COLUMN, key));
log.debug("Remove request: {}", delete.toString());
return getFuture(executeAsyncWrite(delete), rs -> null);
}
@Override
public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
long minPartition = toPartitionTs(query.getStartTs());
long maxPartition = toPartitionTs(query.getEndTs());
if (minPartition == maxPartition) {
return Futures.immediateFuture(null);
} else {
ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
@Override
public void onSuccess(@Nullable List<Long> partitions) {
int index = 0;
if (minPartition != query.getStartTs()) {
index = 1;
}
List<Long> partitionsToDelete = new ArrayList<>();
for (int i = index; i < partitions.size() - 1; i++) {
partitionsToDelete.add(partitions.get(i));
}
QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitionsToDelete);
deletePartitionAsync(cursor, resultFuture);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
}
}, readResultsProcessingExecutor);
return resultFuture;
}
}
private void deletePartitionAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
if (!cursor.hasNextPartition()) {
resultFuture.set(null);
} else {
PreparedStatement proto = getDeletePartitionStmt();
BoundStatement stmt = proto.bind();
stmt.setString(0, cursor.getEntityType());
stmt.setUUID(1, cursor.getEntityId());
stmt.setLong(2, cursor.getNextPartition());
stmt.setString(3, cursor.getKey());
Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
@Override
public void onSuccess(@Nullable ResultSet result) {
deletePartitionAsync(cursor, resultFuture);
}
@Override
public void onFailure(Throwable t) {
log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
}
}, readResultsProcessingExecutor);
}
}
private PreparedStatement getDeletePartitionStmt() {
if (deletePartitionStmt == null) {
deletePartitionStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_PARTITIONS_CF +
" WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM);
}
return deletePartitionStmt;
}
private List<TsKvEntry> convertResultToTsKvEntryList(List<Row> rows) {
List<TsKvEntry> entries = new ArrayList<>(rows.size());
if (!rows.isEmpty()) {
@ -458,28 +657,43 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return saveTtlStmts[dataType.ordinal()];
}
private PreparedStatement getFetchStmt(Aggregation aggType) {
if (fetchStmts == null) {
fetchStmts = new PreparedStatement[Aggregation.values().length];
for (Aggregation type : Aggregation.values()) {
if (type == Aggregation.SUM && fetchStmts[Aggregation.AVG.ordinal()] != null) {
fetchStmts[type.ordinal()] = fetchStmts[Aggregation.AVG.ordinal()];
} else if (type == Aggregation.AVG && fetchStmts[Aggregation.SUM.ordinal()] != null) {
fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()];
} else {
fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX +
String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
+ " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " > ? "
+ "AND " + ModelConstants.TS_COLUMN + " <= ?"
+ (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " DESC LIMIT ?" : ""));
private PreparedStatement getFetchStmt(Aggregation aggType, String orderBy) {
switch (orderBy) {
case ASC_ORDER:
if (fetchStmtsAsc == null) {
fetchStmtsAsc = initFetchStmt(orderBy);
}
return fetchStmtsAsc[aggType.ordinal()];
case DESC_ORDER:
if (fetchStmtsDesc == null) {
fetchStmtsDesc = initFetchStmt(orderBy);
}
return fetchStmtsDesc[aggType.ordinal()];
default:
throw new RuntimeException("Not supported" + orderBy + "order!");
}
}
private PreparedStatement[] initFetchStmt(String orderBy) {
PreparedStatement[] fetchStmts = new PreparedStatement[Aggregation.values().length];
for (Aggregation type : Aggregation.values()) {
if (type == Aggregation.SUM && fetchStmts[Aggregation.AVG.ordinal()] != null) {
fetchStmts[type.ordinal()] = fetchStmts[Aggregation.AVG.ordinal()];
} else if (type == Aggregation.AVG && fetchStmts[Aggregation.SUM.ordinal()] != null) {
fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()];
} else {
fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX +
String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
+ " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ "AND " + ModelConstants.TS_COLUMN + " > ? "
+ "AND " + ModelConstants.TS_COLUMN + " <= ?"
+ (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : ""));
}
}
return fetchStmts[aggType.ordinal()];
return fetchStmts;
}
private PreparedStatement getLatestStmt() {

60
dao/src/main/java/org/thingsboard/server/dao/timeseries/QueryCursor.java

@ -0,0 +1,60 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.dao.timeseries;
import lombok.Getter;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import java.util.List;
import java.util.UUID;
public class QueryCursor {
@Getter
protected final String entityType;
@Getter
protected final UUID entityId;
@Getter
protected final String key;
@Getter
private final long startTs;
@Getter
private final long endTs;
final List<Long> partitions;
private int partitionIndex;
public QueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
this.entityType = entityType;
this.entityId = entityId;
this.key = baseQuery.getKey();
this.startTs = baseQuery.getStartTs();
this.endTs = baseQuery.getEndTs();
this.partitions = partitions;
this.partitionIndex = partitions.size() - 1;
}
public boolean hasNextPartition() {
return partitionIndex >= 0;
}
public long getNextPartition() {
long partition = partitions.get(partitionIndex);
partitionIndex--;
return partition;
}
}

11
dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java

@ -17,8 +17,9 @@ package org.thingsboard.server.dao.timeseries;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import java.util.List;
@ -27,7 +28,7 @@ import java.util.List;
*/
public interface TimeseriesDao {
ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries);
ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries);
ListenableFuture<TsKvEntry> findLatest(EntityId entityId, String key);
@ -38,4 +39,10 @@ public interface TimeseriesDao {
ListenableFuture<Void> savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl);
ListenableFuture<Void> saveLatest(EntityId entityId, TsKvEntry tsKvEntry);
ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query);
ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query);
ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query);
}

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

@ -17,8 +17,9 @@ package org.thingsboard.server.dao.timeseries;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import java.util.Collection;
import java.util.List;
@ -28,7 +29,7 @@ import java.util.List;
*/
public interface TimeseriesService {
ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries);
ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries);
ListenableFuture<List<TsKvEntry>> findLatest(EntityId entityId, Collection<String> keys);
@ -37,4 +38,6 @@ public interface TimeseriesService {
ListenableFuture<List<Void>> save(EntityId entityId, TsKvEntry tsKvEntry);
ListenableFuture<List<Void>> save(EntityId entityId, List<TsKvEntry> tsKvEntry, long ttl);
ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> queries);
}

46
dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java

@ -16,57 +16,53 @@
package org.thingsboard.server.dao.timeseries;
import lombok.Getter;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import static org.thingsboard.server.dao.timeseries.CassandraBaseTimeseriesDao.DESC_ORDER;
/**
* Created by ashvayka on 21.02.17.
*/
public class TsKvQueryCursor {
@Getter
private final String entityType;
@Getter
private final UUID entityId;
@Getter
private final String key;
@Getter
private final long startTs;
@Getter
private final long endTs;
private final List<Long> partitions;
public class TsKvQueryCursor extends QueryCursor {
@Getter
private final List<TsKvEntry> data;
@Getter
private String orderBy;
private int partitionIndex;
private int currentLimit;
public TsKvQueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
this.entityType = entityType;
this.entityId = entityId;
this.key = baseQuery.getKey();
this.startTs = baseQuery.getStartTs();
this.endTs = baseQuery.getEndTs();
this.partitions = partitions;
this.partitionIndex = partitions.size() - 1;
public TsKvQueryCursor(String entityType, UUID entityId, ReadTsKvQuery baseQuery, List<Long> partitions) {
super(entityType, entityId, baseQuery, partitions);
this.orderBy = baseQuery.getOrderBy();
this.partitionIndex = isDesc() ? partitions.size() - 1 : 0;
this.data = new ArrayList<>();
this.currentLimit = baseQuery.getLimit();
}
@Override
public boolean hasNextPartition() {
return partitionIndex >= 0;
return isDesc() ? partitionIndex >= 0 : partitionIndex <= partitions.size() - 1;
}
public boolean isFull() {
return currentLimit <= 0;
}
@Override
public long getNextPartition() {
long partition = partitions.get(partitionIndex);
partitionIndex--;
if (isDesc()) {
partitionIndex--;
} else {
partitionIndex++;
}
return partition;
}
@ -78,4 +74,8 @@ public class TsKvQueryCursor {
currentLimit -= newData.size();
data.addAll(newData);
}
private boolean isDesc() {
return orderBy.equals(DESC_ORDER);
}
}

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));
}

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

@ -21,7 +21,8 @@ import org.junit.Assert;
import org.junit.Test;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.BooleanDataEntry;
import org.thingsboard.server.common.data.kv.DoubleDataEntry;
@ -53,6 +54,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
private static final String BOOLEAN_KEY = "booleanKey";
private static final long TS = 42L;
private static final String DESC_ORDER = "DESC";
KvEntry stringKvEntry = new StringDataEntry(STRING_KEY, "value");
KvEntry longKvEntry = new LongDataEntry(LONG_KEY, Long.MAX_VALUE);
@ -100,6 +102,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
Assert.assertEquals(toTsEntry(TS, stringKvEntry), entries.get(0));
}
@Test
public void testDeleteDeviceTsData() throws Exception {
DeviceId deviceId = new DeviceId(UUIDs.timeBased());
saveEntries(deviceId, 10000);
saveEntries(deviceId, 20000);
saveEntries(deviceId, 30000);
saveEntries(deviceId, 40000);
tsService.remove(deviceId, Collections.singletonList(
new BaseDeleteTsKvQuery(STRING_KEY, 15000, 45000))).get();
List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(
new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get();
Assert.assertEquals(1, list.size());
List<TsKvEntry> latest = tsService.findLatest(deviceId, Collections.singletonList(STRING_KEY)).get();
Assert.assertEquals(null, latest.get(0).getValueAsString());
}
@Test
public void testFindDeviceTsData() throws Exception {
DeviceId deviceId = new DeviceId(UUIDs.timeBased());
@ -114,7 +136,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
entries.add(save(deviceId, 45000, 500));
entries.add(save(deviceId, 55000, 600));
List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.NONE))).get();
assertEquals(3, list.size());
assertEquals(55000, list.get(0).getTs());
@ -126,7 +148,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(35000, list.get(2).getTs());
assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue());
list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.AVG))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
@ -138,7 +160,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.SUM))).get();
assertEquals(3, list.size());
@ -151,7 +173,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue());
list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MIN))).get();
assertEquals(3, list.size());
@ -164,7 +186,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue());
list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MAX))).get();
assertEquals(3, list.size());
@ -177,7 +199,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue());
list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.COUNT))).get();
assertEquals(3, list.size());

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);
}

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

@ -0,0 +1,164 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.metadata;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
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.extern.slf4j.Slf4j;
import org.thingsboard.rule.engine.api.*;
import org.thingsboard.rule.engine.api.util.DonAsynchron;
import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.kv.TsKvQuery;
import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.msg.TbMsg;
import java.util.List;
import java.util.concurrent.ExecutionException;
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.FETCH_MODE_ALL;
import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE;
import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
/**
* Created by mshvayka on 04.09.18.
*/
@Slf4j
@RuleNode(type = ComponentType.ENRICHMENT,
name = "originator telemetry",
configClazz = TbGetTelemetryNodeConfiguration.class,
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. " +
"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")
public class TbGetTelemetryNode implements TbNode {
private TbGetTelemetryNodeConfiguration config;
private List<String> tsKeyNames;
private long startTsOffset;
private long endTsOffset;
private int limit;
private ObjectMapper mapper;
@Override
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;
mapper = new ObjectMapper();
mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
if (tsKeyNames.isEmpty()) {
ctx.tellFailure(msg, new IllegalStateException("Telemetry is not selected!"));
} else {
try {
List<TsKvQuery> queries = buildQueries();
ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(msg.getOriginator(), queries);
DonAsynchron.withCallback(list, data -> {
process(data, msg);
TbMsg newMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
ctx.tellNext(newMsg, SUCCESS);
}, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor());
} catch (Exception e) {
ctx.tellFailure(msg, e);
}
}
}
//TODO: handle direction;
private List<TsKvQuery> buildQueries() {
long ts = System.currentTimeMillis();
long startTs = ts - startTsOffset;
long endTs = ts - endTsOffset;
return tsKeyNames.stream()
.map(key -> new BaseTsKvQuery(key, startTs, endTs, 1, limit, NONE))
.collect(Collectors.toList());
}
private void process(List<TsKvEntry> entries, TbMsg msg) {
ObjectNode resultNode = mapper.createObjectNode();
if (limit == MAX_FETCH_SIZE) {
entries.forEach(entry -> processArray(resultNode, entry));
} else {
entries.forEach(entry -> processSingle(resultNode, entry));
}
for (String key : tsKeyNames) {
if(resultNode.has(key)){
msg.getMetaData().putValue(key, resultNode.get(key).toString());
}
}
}
private void processSingle(ObjectNode node, TsKvEntry entry) {
node.put(entry.getKey(), entry.getValueAsString());
}
private void processArray(ObjectNode node, TsKvEntry entry) {
if(node.has(entry.getKey())){
ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey());
ObjectNode obj = buildNode(entry);
arrayNode.add(obj);
}else {
ArrayNode arrayNode = mapper.createArrayNode();
ObjectNode obj = buildNode(entry);
arrayNode.add(obj);
node.set(entry.getKey(), arrayNode);
}
}
private ObjectNode buildNode(TsKvEntry entry) {
ObjectNode obj = mapper.createObjectNode()
.put("ts", entry.getTs());
switch (entry.getDataType()) {
case STRING:
obj.put("value", entry.getValueAsString());
break;
case LONG:
obj.put("value", entry.getLongValue().get());
break;
case BOOLEAN:
obj.put("value", entry.getBooleanValue().get());
break;
case DOUBLE:
obj.put("value", entry.getDoubleValue().get());
break;
}
return obj;
}
@Override
public void destroy() {
}
}

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

@ -0,0 +1,57 @@
/**
* Copyright © 2016-2018 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.metadata;
import lombok.Data;
import org.thingsboard.rule.engine.api.NodeConfiguration;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
/**
* Created by mshvayka on 04.09.18.
*/
@Data
public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetTelemetryNodeConfiguration> {
public static final String FETCH_MODE_FIRST = "FIRST";
public static final String FETCH_MODE_LAST = "LAST";
public static final String FETCH_MODE_ALL = "ALL";
public static final int MAX_FETCH_SIZE = 1000;
private int startInterval;
private int endInterval;
private String startIntervalTimeUnit;
private String endIntervalTimeUnit;
private String fetchMode; //FIRST, LAST, LATEST
private List<String> latestTsKeyNames;
@Override
public TbGetTelemetryNodeConfiguration defaultConfiguration() {
TbGetTelemetryNodeConfiguration configuration = new TbGetTelemetryNodeConfiguration();
configuration.setLatestTsKeyNames(Collections.emptyList());
configuration.setFetchMode("FIRST");
configuration.setStartIntervalTimeUnit(TimeUnit.MINUTES.name());
configuration.setStartInterval(2);
configuration.setEndIntervalTimeUnit(TimeUnit.MINUTES.name());
configuration.setEndInterval(1);
return configuration;
}
}

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

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

File diff suppressed because one or more lines are too long

292
ui/.stylelintrc

@ -0,0 +1,292 @@
{
"extends": ["stylelint-config-standard", "stylelint-config-recommended-scss"],
"plugins": [
"stylelint-order"
],
"rules": {
"at-rule-empty-line-before": ["always", {
"except": ["first-nested"],
"ignore": ["after-comment"]
}],
"at-rule-name-space-after": "always",
"at-rule-no-vendor-prefix": true,
"at-rule-semicolon-space-before": "never",
"block-closing-brace-empty-line-before": "never",
"block-closing-brace-newline-after": null,
"block-opening-brace-space-before": null,
"color-named": "never",
"declaration-block-semicolon-newline-after": "always-multi-line",
"declaration-block-semicolon-newline-before": "never-multi-line",
"declaration-block-semicolon-space-after": "always-single-line",
"declaration-empty-line-before": null,
"declaration-no-important": null,
"font-family-name-quotes": "always-where-recommended",
"font-weight-notation": [
"numeric", {
"ignore": ["relative"]
}],
"function-url-no-scheme-relative": true,
"function-url-quotes": "always",
"length-zero-no-unit": true,
"max-empty-lines": 2,
"max-line-length": null,
"media-feature-name-no-vendor-prefix": true,
"media-feature-parentheses-space-inside": "never",
"media-feature-range-operator-space-after": "always",
"media-feature-range-operator-space-before": "never",
"no-descending-specificity": null,
"no-duplicate-selectors": true,
"number-leading-zero": "never",
"media-feature-name-no-unknown": [true, {
"ignoreMediaFeatureNames": ["prefers-reduced-motion"]
}],
"order/properties-order": [
"position",
"top",
"right",
"bottom",
"left",
"z-index",
"box-sizing",
"display",
"flex",
"flex-align",
"flex-basis",
"flex-direction",
"flex-wrap",
"flex-flow",
"flex-shrink",
"flex-grow",
"flex-order",
"flex-pack",
"align-content",
"align-items",
"align-self",
"justify-content",
"order",
"float",
"width",
"min-width",
"max-width",
"height",
"min-height",
"max-height",
"padding",
"padding-top",
"padding-right",
"padding-bottom",
"padding-left",
"margin",
"margin-top",
"margin-right",
"margin-bottom",
"margin-left",
"overflow",
"overflow-x",
"overflow-y",
"-webkit-overflow-scrolling",
"-ms-overflow-x",
"-ms-overflow-y",
"-ms-overflow-style",
"columns",
"column-count",
"column-fill",
"column-gap",
"column-rule",
"column-rule-width",
"column-rule-style",
"column-rule-color",
"column-span",
"column-width",
"orphans",
"widows",
"clip",
"clear",
"font",
"font-family",
"font-size",
"font-style",
"font-weight",
"font-variant",
"font-size-adjust",
"font-stretch",
"font-effect",
"font-emphasize",
"font-emphasize-position",
"font-emphasize-style",
"font-smooth",
"src",
"hyphens",
"line-height",
"color",
"text-align",
"text-align-last",
"text-emphasis",
"text-emphasis-color",
"text-emphasis-style",
"text-emphasis-position",
"text-decoration",
"text-indent",
"text-justify",
"text-outline",
"-ms-text-overflow",
"text-overflow",
"text-overflow-ellipsis",
"text-overflow-mode",
"text-shadow",
"text-transform",
"text-wrap",
"-webkit-text-size-adjust",
"-ms-text-size-adjust",
"letter-spacing",
"-ms-word-break",
"word-break",
"word-spacing",
"-ms-word-wrap",
"word-wrap",
"overflow-wrap",
"tab-size",
"white-space",
"vertical-align",
"direction",
"unicode-bidi",
"list-style",
"list-style-position",
"list-style-type",
"list-style-image",
"pointer-events",
"-ms-touch-action",
"touch-action",
"cursor",
"visibility",
"zoom",
"table-layout",
"empty-cells",
"caption-side",
"border-spacing",
"border-collapse",
"content",
"quotes",
"counter-reset",
"counter-increment",
"resize",
"user-select",
"nav-index",
"nav-up",
"nav-right",
"nav-down",
"nav-left",
"background",
"background-color",
"background-image",
"filter",
"background-repeat",
"background-attachment",
"background-position",
"background-position-x",
"background-position-y",
"background-clip",
"background-origin",
"background-size",
"border",
"border-color",
"border-style",
"border-width",
"border-top",
"border-top-color",
"border-top-style",
"border-top-width",
"border-right",
"border-right-color",
"border-right-style",
"border-right-width",
"border-bottom",
"border-bottom-color",
"border-bottom-style",
"border-bottom-width",
"border-left",
"border-left-color",
"border-left-style",
"border-left-width",
"border-radius",
"border-top-left-radius",
"border-top-right-radius",
"border-bottom-right-radius",
"border-bottom-left-radius",
"border-image",
"border-image-source",
"border-image-slice",
"border-image-width",
"border-image-outset",
"border-image-repeat",
"outline",
"outline-width",
"outline-style",
"outline-color",
"outline-offset",
"box-shadow",
"opacity",
"-ms-interpolation-mode",
"page-break-after",
"page-break-before",
"page-break-inside",
"transition",
"transition-delay",
"transition-timing-function",
"transition-duration",
"transition-property",
"transform",
"transform-origin",
"perspective",
"appearance",
"animation",
"animation-name",
"animation-duration",
"animation-play-state",
"animation-timing-function",
"animation-delay",
"animation-iteration-count",
"animation-direction",
"animation-fill-mode",
"fill",
"stroke"
],
"property-no-vendor-prefix": null,
"rule-empty-line-before": ["always", {
"except": ["first-nested"],
"ignore": ["after-comment"]
}],
"scss/dollar-variable-default": [true, { "ignore": "local" }],
"selector-attribute-quotes": "always",
"selector-list-comma-newline-after": "always",
"selector-list-comma-newline-before": "never-multi-line",
"selector-list-comma-space-after": "always-single-line",
"selector-list-comma-space-before": "never-single-line",
"selector-max-attribute": 2,
"selector-max-class": 6,
"selector-max-combinators": 8,
"selector-max-compound-selectors": 9,
"selector-max-empty-lines": 1,
"selector-max-id": 1,
"selector-max-specificity": null,
"selector-max-type": 5,
"selector-max-universal": 1,
"selector-no-qualifying-type": null,
"selector-no-vendor-prefix": null,
"selector-type-no-unknown": [true, {
"ignoreTypes": [
"/^md-/",
"/^mdp-/",
"/^ng-/",
"/^tb-/",
"/^v-pane/"
]
}],
"string-quotes": "double",
"value-keyword-case": "lower",
"value-list-comma-newline-after": "always-multi-line",
"value-list-comma-newline-before": "never-multi-line",
"value-list-comma-space-after": "always-single-line",
"value-no-vendor-prefix": null
}
}

12
ui/package.json

@ -100,6 +100,7 @@
"copy-webpack-plugin": "^3.0.1",
"cross-env": "^3.2.4",
"css-loader": "^0.25.0",
"directory-tree": "^2.1.0",
"eslint": "^3.4.0",
"eslint-config-angular": "^0.5.0",
"eslint-loader": "^1.5.0",
@ -112,6 +113,7 @@
"html-minifier-loader": "^1.3.4",
"html-webpack-plugin": "^2.30.1",
"img-loader": "^1.3.1",
"jsonminify": "^0.4.1",
"less": "^2.7.1",
"less-loader": "^2.2.3",
"ng-annotate-loader": "^0.1.1",
@ -122,14 +124,18 @@
"react-hot-loader": "^3.0.0-beta.6",
"sass-loader": "^4.0.2",
"style-loader": "^0.13.1",
"stylelint": "^9.5.0",
"stylelint-config-recommended-scss": "^3.2.0",
"stylelint-config-standard": "^18.2.0",
"stylelint-order": "^1.0.0",
"stylelint-scss": "^3.3.0",
"stylelint-webpack-plugin": "^0.10.5",
"url-loader": "^0.5.7",
"webpack": "^1.13.2",
"webpack-dev-middleware": "^1.6.1",
"webpack-dev-server": "^1.15.1",
"webpack-hot-middleware": "^2.12.2",
"webpack-material-design-icons": "^0.1.0",
"directory-tree": "^2.1.0",
"jsonminify": "^0.4.1"
"webpack-material-design-icons": "^0.1.0"
},
"engine": "node >= 5.9.0",
"nyc": {

10
ui/src/app/alarm/alarm-details-dialog.scss

@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-alarm-details-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: 100%;
margin-left: 15px;
border: 1px solid #c0c0c0;
#tb-alarm-details {
min-width: 600px;
min-height: 200px;
width: 100%;
min-width: 600px;
height: 100%;
min-height: 200px;
}
}

23
ui/src/app/alarm/alarm.scss

@ -13,26 +13,27 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-alarm-container {
overflow-x: auto;
}
md-list.tb-alarm-table {
padding: 0px;
min-width: 700px;
padding: 0;
md-list-item {
padding: 0px;
padding: 0;
}
.tb-row {
height: 48px;
padding: 0px;
padding: 0;
overflow: hidden;
}
.tb-row:hover {
background-color: #EEEEEE;
background-color: #eee;
}
.tb-header:hover {
@ -41,9 +42,9 @@ md-list.tb-alarm-table {
.tb-header {
.tb-cell {
color: rgba(0,0,0,.54);
font-size: 12px;
font-weight: 700;
color: rgba(0, 0, 0, .54);
white-space: nowrap;
background: none;
}
@ -52,11 +53,12 @@ md-list.tb-alarm-table {
.tb-cell {
padding: 0 24px;
margin: auto 0;
color: rgba(0,0,0,.87);
overflow: hidden;
font-size: 13px;
vertical-align: middle;
color: rgba(0, 0, 0, .87);
text-align: left;
overflow: hidden;
vertical-align: middle;
.md-button {
padding: 0;
margin: 0;
@ -66,12 +68,11 @@ md-list.tb-alarm-table {
.tb-cell.tb-number {
text-align: right;
}
}
#tb-alarm-content {
min-width: 400px;
min-height: 50px;
width: 100%;
min-width: 400px;
height: 100%;
min-height: 50px;
}

12
ui/src/app/audit/audit-log-details-dialog.scss

@ -13,10 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#tb-audit-log-action-data, #tb-audit-log-failure-details {
min-width: 400px;
min-height: 50px;
#tb-audit-log-action-data,
#tb-audit-log-failure-details {
width: 100%;
min-width: 400px;
height: 100%;
border: 1px solid #C0C0C0;
}
min-height: 50px;
border: 1px solid #c0c0c0;
}

27
ui/src/app/audit/audit-log.scss

@ -13,17 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-audit-logs {
background-color: #fff;
.tb-audit-log-margin-18px {
margin-bottom: 18px;
}
.tb-audit-log-toolbar {
font-size: 20px;
}
md-input-container.tb-audit-log-search-input {
.md-errors-spacer {
min-height: 0px;
min-height: 0;
}
}
}
@ -32,27 +36,26 @@
overflow-x: auto;
}
md-list.tb-audit-log-table {
padding: 0px;
min-width: 700px;
padding: 0;
&.tb-audit-log-table-full {
min-width: 900px;
}
md-list-item {
padding: 0px;
padding: 0;
}
.tb-row {
height: 48px;
padding: 0px;
padding: 0;
overflow: hidden;
}
.tb-row:hover {
background-color: #EEEEEE;
background-color: #eee;
}
.tb-header:hover {
@ -61,9 +64,9 @@ md-list.tb-audit-log-table {
.tb-header {
.tb-cell {
color: rgba(0,0,0,.54);
font-size: 12px;
font-weight: 700;
color: rgba(0, 0, 0, .54);
white-space: nowrap;
background: none;
}
@ -72,11 +75,12 @@ md-list.tb-audit-log-table {
.tb-cell {
padding: 0 24px;
margin: auto 0;
color: rgba(0,0,0,.87);
overflow: hidden;
font-size: 13px;
vertical-align: middle;
color: rgba(0, 0, 0, .87);
text-align: left;
overflow: hidden;
vertical-align: middle;
.md-button {
padding: 0;
margin: 0;
@ -86,5 +90,4 @@ md-list.tb-audit-log-table {
.tb-cell.tb-number {
text-align: right;
}
}

5
ui/src/app/components/dashboard-autocomplete.scss

@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-dashboard-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
line-height: 1.5;
}
.tb-dashboard-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;

30
ui/src/app/components/dashboard-select.scss

@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../scss/constants';
@import "../../scss/constants";
tb-dashboard-select {
min-width: 52px;
md-select {
pointer-events: all;
max-width: 300px;
pointer-events: all;
}
}
.tb-dashboard-select {
min-height: 32px;
span {
pointer-events: all;
cursor: pointer;
@ -38,23 +40,27 @@ tb-dashboard-select {
}
.tb-dashboard-select-panel {
min-width: 300px;
max-width: 320px;
max-height: 150px;
overflow-x: hidden;
overflow-y: auto;
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
@media (min-height: 350px) {
max-height: 250px;
}
max-width: 320px;
@media (min-width: $layout-breakpoint-xs) {
max-width: 100%;
}
min-width: 300px;
background: white;
border-radius: 4px;
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
overflow-x: hidden;
overflow-y: auto;
md-content {
background-color: #fff;
}
}
}

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

@ -21,6 +21,7 @@ div.tb-widget {
margin: 0;
overflow: hidden;
outline: none;
@include transition(all .2s ease-in-out);
.tb-widget-title {
@ -32,7 +33,7 @@ div.tb-widget {
tb-timewindow {
font-size: 14px;
opacity: 0.85;
opacity: .85;
}
}
@ -44,17 +45,19 @@ div.tb-widget {
margin: 0;
.md-button.md-icon-button {
margin: 0 !important;
padding: 0 !important;
line-height: 20px;
width: 32px;
height: 32px;
min-width: 32px;
height: 32px;
min-height: 32px;
md-icon, ng-md-icon {
padding: 0 !important;
margin: 0 !important;
line-height: 20px;
md-icon,
ng-md-icon {
width: 20px;
height: 20px;
min-width: 20px;
height: 20px;
min-height: 20px;
font-size: 20px;
}
@ -63,8 +66,8 @@ div.tb-widget {
.tb-widget-content {
tb-widget {
width: 100%;
position: relative;
width: 100%;
}
}
}
@ -75,40 +78,41 @@ div.tb-widget.tb-highlighted {
}
div.tb-widget.tb-not-highlighted {
opacity: 0.5;
opacity: .5;
}
tb-dashboard {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
left: 0;
}
md-content.tb-dashboard-content {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
outline: none;
left: 0;
background: none;
outline: none;
.gridster-item {
@include transition(none);
@include transition(none);
}
}
.tb-widget-error-container {
position: absolute;
background-color: #fff;
width: 100%;
height: 100%;
background-color: #fff;
}
.tb-widget-error-msg {
color: red;
padding: 5px;
font-size: 16px;
color: #f00;
word-wrap: break-word;
padding: 5px;
}

2
ui/src/app/components/datakey-config.scss

@ -13,9 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-datakey-config {
min-width: 500px !important;
min-height: 500px !important;
md-content {
background-color: #fff;
}

16
ui/src/app/components/datasource-entity.scss

@ -13,17 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../scss/constants';
@import "../../scss/constants";
.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
.tb-entity-alias-autocomplete,
.tb-timeseries-datakey-autocomplete,
.tb-attribute-datakey-autocomplete,
.tb-alarm-datakey-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
line-height: 1.5;
.tb-no-entries {
line-height: 48px;
}
}
li {
height: auto !important;
white-space: normal !important;
@ -32,13 +37,14 @@
tb-datasource-entity {
@media (min-width: $layout-breakpoint-sm) {
padding-left: 4px;
padding-right: 4px;
padding-left: 4px;
}
tb-entity-alias-select {
@media (min-width: $layout-breakpoint-sm) {
width: 200px;
max-width: 200px;
}
}
}
}

11
ui/src/app/components/datasource-func.scss

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../scss/constants';
@import "../../scss/constants";
.tb-datasource-func {
@media (min-width: $layout-breakpoint-sm) {
@ -22,19 +22,22 @@
md-input-container.tb-datasource-name {
.md-errors-spacer {
display: none;
display: none;
}
}
.tb-func-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
.tb-func-datakey-autocomplete,
.tb-alarm-datakey-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
line-height: 1.5;
.tb-no-entries {
line-height: 48px;
}
}
li {
height: auto !important;
white-space: normal !important;

41
ui/src/app/components/datasource.scss

@ -13,39 +13,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-datasource {
#entity-autocomplete {
height: 30px;
margin-top: 18px;
md-autocomplete-wrap {
height: 30px;
}
input, input:not(.md-input) {
height: 30px;
}
}
#datasourceType {
#entity-autocomplete {
height: 30px;
margin-top: 18px;
md-autocomplete-wrap {
height: 30px;
}
input,
input:not(.md-input) {
height: 30px;
}
}
}
@mixin tb-checkered-bg() {
background-color: #fff;
background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
background-size: 8px 8px;
background-image:
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
background-position: 0 0, 4px 4px;
background-size: 8px 8px;
}
.tb-color-preview {
content: '';
min-width: 24px;
position: relative;
width: 24px;
min-width: 24px;
height: 24px;
overflow: hidden;
content: "";
border: 2px solid #fff;
border-radius: 50%;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
position: relative;
overflow: hidden;
@include tb-checkered-bg();
.tb-color-result {
@ -60,6 +64,7 @@
text-overflow: ellipsis;
white-space: nowrap;
}
.tb-chip-separator {
white-space: pre;
}

7
ui/src/app/components/datetime-period.scss

@ -13,13 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
tb-datetime-period {
md-input-container {
margin-bottom: 0px;
margin-bottom: 0;
.md-errors-spacer {
min-height: 0px;
min-height: 0;
}
}
mdp-date-picker {
.md-input {
width: 150px !important;

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

@ -13,47 +13,51 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../scss/constants';
@import "../../scss/constants";
.tb-details-title {
font-size: 1.000rem;
@media (min-width: $layout-breakpoint-gt-sm) {
font-size: 1.600rem;
}
font-weight: 400;
text-transform: uppercase;
width: inherit;
margin: 20px 8px 0 0;
overflow: hidden;
font-size: 1rem;
font-weight: 400;
text-overflow: ellipsis;
text-transform: uppercase;
white-space: nowrap;
width: inherit;
@media (min-width: $layout-breakpoint-gt-sm) {
font-size: 1.6rem;
}
}
.tb-details-subtitle {
font-size: 1.000rem;
width: inherit;
margin: 10px 0;
opacity: 0.8;
overflow: hidden;
font-size: 1rem;
text-overflow: ellipsis;
white-space: nowrap;
width: inherit;
opacity: .8;
}
md-sidenav.tb-sidenav-details {
.md-toolbar-tools {
min-height: 100px;
max-height: 120px;
height: 100%;
height: 100%;
min-height: 100px;
max-height: 120px;
}
z-index: 59 !important;
width: 100% !important;
max-width: 100% !important;
z-index: 59 !important;
@media (min-width: $layout-breakpoint-gt-sm) {
width: 80% !important;
}
@media (min-width: $layout-breakpoint-gt-md) {
width: 65% !important;
}
tb-dashboard {
md-content {
background-color: $primary-hue-3;

5
ui/src/app/components/entity-alias-select.scss

@ -13,15 +13,18 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-alias-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
line-height: 1.5;
.tb-no-entries {
line-height: 48px;
}
}
li {
height: auto !important;
white-space: normal !important;

29
ui/src/app/components/expand-fullscreen.scss

@ -16,37 +16,38 @@
@import "../../scss/constants";
.tb-fullscreen {
section.header-buttons {
top: 25px;
}
}
.tb-fullscreen {
width: 100% !important;
height: 100% !important;
position: fixed !important;
top: 0;
left: 0;
width: 100% !important;
height: 100% !important;
section.header-buttons {
top: 25px;
}
}
.tb-fullscreen-parent {
background-color: $gray;
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: $gray;
}
.md-button.tb-fullscreen-button-style, .tb-fullscreen-button-style {
.md-button.tb-fullscreen-button-style,
.tb-fullscreen-button-style {
background: #ccc;
opacity: 0.85;
opacity: .85;
ng-md-icon {
color: #666;
}
}
.md-button.tb-fullscreen-button-pos, .tb-fullscreen-button-pos {
.md-button.tb-fullscreen-button-pos,
.tb-fullscreen-button-pos {
position: absolute;
top: 10px;
right: 10px;

13
ui/src/app/components/grid.scss

@ -21,15 +21,19 @@
.tb-card-item {
@include transition(all .2s ease-in-out);
md-card-content {
padding-top: 0px;
max-height: 53px;
padding-top: 0;
}
md-card-title {
width: 100%;
md-card-title-text {
max-height: 32px;
min-width: 50%;
max-height: 32px;
.md-headline {
overflow: hidden;
text-overflow: ellipsis;
@ -40,14 +44,15 @@
}
.tb-current-item {
opacity: 0.5;
opacity: .5;
@include transform(scale(1.05));
}
#tb-vertical-container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
left: 0;
}

23
ui/src/app/components/js-func.scss

@ -13,11 +13,14 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
tb-js-func {
position: relative;
.tb-disabled {
color: rgba(0,0,0,0.38);
color: rgba(0, 0, 0, .38);
}
.fill-height {
height: 100%;
}
@ -25,25 +28,27 @@ tb-js-func {
.tb-js-func-toolbar {
.md-button.tidy {
color: #7B7B7B;
min-width: 32px;
min-height: 15px;
line-height: 15px;
font-size: 0.800rem;
margin: 0 5px 0 0;
padding: 4px;
background: rgba(220, 220, 220, 0.35);
margin: 0 5px 0 0;
font-size: .8rem;
line-height: 15px;
color: #7b7b7b;
background: rgba(220, 220, 220, .35);
}
}
.tb-js-func-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: calc(100% - 80px);
margin-left: 15px;
border: 1px solid #c0c0c0;
#tb-javascript-input {
min-width: 200px;
width: 100%;
min-width: 200px;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}

20
ui/src/app/components/json-content.scss

@ -13,8 +13,10 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
tb-json-content {
position: relative;
.fill-height {
height: 100%;
}
@ -22,25 +24,27 @@ tb-json-content {
.tb-json-content-toolbar {
.md-button.tidy {
color: #7B7B7B;
min-width: 32px;
min-height: 15px;
line-height: 15px;
font-size: 0.800rem;
margin: 0 5px 0 0;
padding: 4px;
background: rgba(220, 220, 220, 0.35);
margin: 0 5px 0 0;
font-size: .8rem;
line-height: 15px;
color: #7b7b7b;
background: rgba(220, 220, 220, .35);
}
}
.tb-json-content-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: 100%;
margin-left: 15px;
border: 1px solid #c0c0c0;
#tb-json-input {
min-width: 200px;
width: 100%;
min-width: 200px;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}

5
ui/src/app/components/json-form.scss

@ -13,7 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
tb-json-form {
overflow: auto;
padding-bottom: 14px !important;
}
overflow: auto;
}

10
ui/src/app/components/json-object-edit.scss

@ -13,21 +13,25 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
tb-json-object-edit {
position: relative;
.fill-height {
height: 100%;
}
}
.tb-json-object-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: 100%;
margin-left: 15px;
border: 1px solid #c0c0c0;
#tb-json-input {
min-width: 200px;
width: 100%;
min-width: 200px;
height: 100%;
&:not(.fill-height) {
min-height: 200px;
}

8
ui/src/app/components/kv-map.scss

@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-kv-map {
span.no-data-found {
position: relative;
display: flex;
height: 40px;
text-transform: uppercase;
display: flex;
&.disabled {
color: rgba(0,0,0,0.38);
color: rgba(0, 0, 0, .38);
}
}
}
}

22
ui/src/app/components/legend-config.scss

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.md-panel {
&.tb-legend-config-panel {
position: absolute;
@ -20,21 +21,26 @@
}
.tb-legend-config-panel {
max-height: 220px;
min-width: 220px;
background: white;
border-radius: 4px;
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
max-height: 220px;
overflow: hidden;
form, fieldset {
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
form,
fieldset {
height: 100%;
}
md-content {
background-color: #fff;
overflow: hidden;
background-color: #fff;
}
.md-padding {
padding: 0 16px;
}

26
ui/src/app/components/legend.scss

@ -13,39 +13,49 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
table.tb-legend {
width: 100%;
font-size: 12px;
.tb-legend-header, .tb-legend-value {
text-align: right;
.tb-legend-header,
.tb-legend-value {
text-align: right;
}
.tb-legend-header {
th {
color: rgb(255,110,64);
white-space: nowrap;
padding: 0 10px 1px 0;
color: rgb(255, 110, 64);
white-space: nowrap;
}
}
.tb-legend-keys {
td.tb-legend-label, td.tb-legend-value {
white-space: nowrap;
td.tb-legend-label,
td.tb-legend-value {
padding: 2px 10px;
white-space: nowrap;
}
.tb-legend-line {
display: inline-block;
width: 15px;
height: 3px;
display: inline-block;
vertical-align: middle;
}
.tb-legend-label {
text-align: left;
outline: none;
&.tb-horizontal {
width: 95%;
}
&.tb-hidden-label {
text-decoration: line-through;
opacity: 0.6;
opacity: .6;
}
}
}

22
ui/src/app/components/material-icon-select.scss

@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-material-icon-select {
md-icon {
padding: 4px;
margin: 8px 4px 4px;
cursor: pointer;
border: solid 1px rgba(0,0,0,0.27);
}
md-input-container {
margin-bottom: 0px;
}
}
md-icon {
padding: 4px;
margin: 8px 4px 4px;
cursor: pointer;
border: solid 1px rgba(0, 0, 0, .27);
}
md-input-container {
margin-bottom: 0;
}
}

14
ui/src/app/components/material-icons-dialog.scss

@ -13,18 +13,20 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-material-icons-dialog {
button.md-icon-button.tb-select-icon-button {
border: solid 1px orange;
border-radius: 0%;
padding: 16px;
height: 56px;
width: 56px;
height: 56px;
padding: 16px;
margin: 10px;
border: solid 1px #ffa500;
border-radius: 0%;
}
.tb-icons-load {
top: 64px;
background: rgba(255,255,255,0.75);
z-index: 3;
background: rgba(255, 255, 255, .75);
}
}
}

6
ui/src/app/components/menu-link.scss

@ -24,9 +24,11 @@
}
.tb-menu-toggle-list {
overflow: hidden;
position: relative;
z-index: 1;
@include transition(0.75s cubic-bezier(0.35, 0, 0.25, 1));
overflow: hidden;
@include transition(.75s cubic-bezier(.35, 0, .25, 1));
@include transition-property(height);
}

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

@ -13,30 +13,35 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.json-form-ace-editor {
position: relative;
border: 1px solid #C0C0C0;
height: 100%;
.title-panel {
position: absolute;
font-size: 0.800rem;
font-weight: 500;
top: 10px;
right: 20px;
z-index: 5;
label {
color: #00acc1;
background: rgba(220, 220, 220, 0.35);
border-radius: 5px;
padding: 4px;
text-transform: uppercase;
}
button.tidy-button {
background: rgba(220, 220, 220, 0.35) !important;
span {
padding: 0px !important;
font-size: 12px !important;
}
}
position: relative;
height: 100%;
border: 1px solid #c0c0c0;
.title-panel {
position: absolute;
top: 10px;
right: 20px;
z-index: 5;
font-size: .8rem;
font-weight: 500;
label {
padding: 4px;
color: #00acc1;
text-transform: uppercase;
background: rgba(220, 220, 220, .35);
border-radius: 5px;
}
}
button.tidy-button {
background: rgba(220, 220, 220, .35) !important;
span {
padding: 0 !important;
font-size: 12px !important;
}
}
}
}

14
ui/src/app/components/react/json-form-color.scss

@ -15,21 +15,23 @@
*/
@mixin tb-checkered-bg() {
background-color: #fff;
background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
background-size: 8px 8px;
background-image:
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
background-position: 0 0, 4px 4px;
background-size: 8px 8px;
}
.tb-color-preview {
content: '';
position: relative;
width: 24px;
height: 24px;
overflow: hidden;
content: "";
border: 2px solid #fff;
border-radius: 50%;
box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
position: relative;
overflow: hidden;
@include tb-checkered-bg();
.tb-color-result {

91
ui/src/app/components/react/json-form-image.scss

@ -13,66 +13,69 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$previewSize: 100px;
$previewSize: 100px !default;
.tb-image-select-container {
position: relative;
height: $previewSize;
width: 100%;
height: $previewSize;
}
.tb-image-preview {
max-width: $previewSize;
max-height: $previewSize;
width: 100%;
height: 100%;
width: 100%;
max-width: $previewSize;
height: 100%;
max-height: $previewSize;
}
.tb-image-preview-container {
position: relative;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
border: solid 1px;
vertical-align: top;
float: left;
div {
width: 100%;
font-size: 18px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
position: relative;
float: left;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
vertical-align: top;
border: solid 1px;
div {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
font-size: 18px;
text-align: center;
transform: translate(-50%, -50%);
}
}
.tb-dropzone {
position: relative;
border: dashed 2px;
height: $previewSize;
vertical-align: top;
padding: 0 8px;
overflow: hidden;
div {
width: 100%;
font-size: 24px;
text-align: center;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
}
position: relative;
height: $previewSize;
padding: 0 8px;
overflow: hidden;
vertical-align: top;
border: dashed 2px;
div {
position: absolute;
top: 50%;
left: 50%;
width: 100%;
font-size: 24px;
text-align: center;
transform: translate(-50%, -50%);
}
}
.tb-image-clear-container {
width: 48px;
height: $previewSize;
position: relative;
float: right;
position: relative;
float: right;
width: 48px;
height: $previewSize;
}
.tb-image-clear-btn {
position: absolute !important;
top: 50%;
transform: translate(0%,-50%) !important;
position: absolute !important;
top: 50%;
transform: translate(0%, -50%) !important;
}

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

@ -15,87 +15,92 @@
*/
@import "~compass-sass-mixins/lib/compass";
$swift-ease-out-duration: 0.4s !default;
$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
$swift-ease-out-duration: .4s !default;
$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
$input-label-float-offset: 6px !default;
$input-label-float-scale: 0.75 !default;
$input-label-float-scale: .75 !default;
.json-form-error {
position: relative;
bottom: -5px;
font-size: 12px;
line-height: 12px;
color: rgb(244, 67, 54);
@include transition(all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms);
position: relative;
bottom: -5px;
font-size: 12px;
line-height: 12px;
color: rgb(244, 67, 54);
@include transition(all 450ms cubic-bezier(.23, 1, .32, 1) 0ms);
}
.tb-container {
position: relative;
margin-top: 32px;
padding: 10px 0;
position: relative;
padding: 10px 0;
margin-top: 32px;
}
.tb-field {
&.tb-required {
label:after {
content: ' *';
font-size: 13px;
vertical-align: top;
color: rgba(0,0,0,0.54);
}
&.tb-required {
label::after {
font-size: 13px;
color: rgba(0, 0, 0, .54);
vertical-align: top;
content: " *";
}
&.tb-focused:not(.tb-readonly) {
label:after {
color: rgb(221,44,0);
}
}
&.tb-focused:not(.tb-readonly) {
label::after {
color: rgb(221, 44, 0);
}
}
}
.tb-date-field {
&.tb-required {
div>div:first-child:after {
content: ' *';
font-size: 13px;
vertical-align: top;
color: rgba(0,0,0,0.54);
}
&.tb-required {
div > div:first-child::after {
font-size: 13px;
color: rgba(0, 0, 0, .54);
vertical-align: top;
content: " *";
}
&.tb-focused:not(.tb-readonly) {
div>div:first-child:after {
color: rgb(221,44,0);
}
}
&.tb-focused:not(.tb-readonly) {
div > div:first-child::after {
color: rgb(221, 44, 0);
}
}
}
label.tb-label {
color: rgba(0,0,0,0.54);
-webkit-font-smoothing: antialiased;
position: absolute;
bottom: 100%;
left: 0;
right: auto;
@include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
@include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
width $swift-ease-out-timing-function $swift-ease-out-duration);
transform-origin: left top;
&.tb-focused {
color: rgb(96,125,139);
}
position: absolute;
right: auto;
bottom: 100%;
left: 0;
color: rgba(0, 0, 0, .54);
transform-origin: left top;
-webkit-font-smoothing: antialiased;
&.tb-required:after {
content: ' *';
font-size: 13px;
vertical-align: top;
color: rgba(0,0,0,0.54);
}
@include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
&.tb-focused:not(.tb-readonly):after {
color: rgb(221,44,0);
}
@include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
width $swift-ease-out-timing-function $swift-ease-out-duration);
&.tb-focused {
color: rgb(96, 125, 139);
}
&.tb-required::after {
font-size: 13px;
color: rgba(0, 0, 0, .54);
vertical-align: top;
content: " *";
}
&.tb-focused:not(.tb-readonly)::after {
color: rgb(221, 44, 0);
}
}
.tb-head-label {
color: rgba(0,0,0,0.54);
color: rgba(0, 0, 0, .54);
}

5
ui/src/app/components/related-entity-autocomplete.scss

@ -13,16 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-related-entity-autocomplete {
.tb-not-found {
display: block;
line-height: 1.5;
height: 48px;
line-height: 1.5;
}
.tb-entity-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;

39
ui/src/app/components/side-menu.scss

@ -16,21 +16,22 @@
@import "~compass-sass-mixins/lib/compass";
.tb-side-menu .md-button.tb-active {
background-color: rgba(255, 255, 255, 0.15);
font-weight: 500;
background-color: rgba(255, 255, 255, .15);
}
.tb-side-menu, .tb-side-menu ul {
list-style: none;
.tb-side-menu,
.tb-side-menu ul {
padding: 0;
margin-top: 0;
list-style: none;
}
.tb-side-menu .tb-menu-toggle-list a.md-button {
padding: 0 16px 0 32px;
font-weight: 500;
text-transform: none;
text-rendering: optimizeLegibility;
font-weight: 500;
}
.tb-side-menu .tb-menu-toggle-list .md-button {
@ -43,36 +44,38 @@
}
.tb-side-menu > li {
border-bottom: 1px solid rgba(0, 0, 0, 0.12);
border-bottom: 1px solid rgba(0, 0, 0, .12);
}
.tb-side-menu .md-button-toggle .md-toggle-icon {
background-size: 100% auto;
display: inline-block;
margin: auto 0 auto auto;
width: 15px;
margin: auto 0 auto auto;
background-size: 100% auto;
@include transition(transform .3s, ease-in-out);
}
.tb-side-menu .md-button {
display: flex;
border-radius: 0;
color: inherit;
cursor: pointer;
line-height: 40px;
margin: 0;
width: 100%;
max-height: 40px;
padding: 0 16px;
margin: 0;
overflow: hidden;
padding: 0px 16px;
line-height: 40px;
color: inherit;
text-align: left;
text-decoration: none;
white-space: nowrap;
text-overflow: ellipsis;
width: 100%;
white-space: nowrap;
cursor: pointer;
border-radius: 0;
span {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
white-space: nowrap;
}
}
@ -83,10 +86,10 @@
.tb-side-menu ng-md-icon {
margin-right: 8px;
margin-left: 0px;
margin-left: 0;
}
.tb-side-menu md-icon {
margin-right: 8px;
margin-left: 0px;
margin-left: 0;
}

11
ui/src/app/components/timeinterval.scss

@ -13,25 +13,32 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
tb-timeinterval {
min-width: 355px;
md-input-container {
margin-bottom: 0px;
margin-bottom: 0;
.md-errors-spacer {
min-height: 0px;
min-height: 0;
}
}
mdp-date-picker {
.md-input {
width: 150px;
}
}
.md-input {
width: 70px !important;
}
.advanced-switch {
margin-top: 0;
}
.advanced-label {
margin: 5px 0;
}

37
ui/src/app/components/timewindow.scss

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.md-panel {
&.tb-timewindow-panel {
position: absolute;
@ -20,38 +21,48 @@
}
.tb-timewindow-panel {
max-height: 440px;
min-width: 417px;
background: white;
border-radius: 4px;
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
max-height: 440px;
overflow: hidden;
form, fieldset {
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
form,
fieldset {
height: 100%;
}
md-content {
background-color: #fff;
overflow: hidden;
background-color: #fff;
}
.md-padding {
padding: 0 16px;
}
.md-radio-interactive {
md-select, md-switch {
md-select,
md-switch {
pointer-events: all;
}
}
md-radio-button {
.md-label {
width: 100%;
}
tb-timeinterval {
width: 355px;
.advanced-switch {
min-height: 30px;
max-width: 44px;
min-height: 30px;
}
}
}
@ -59,15 +70,17 @@
tb-timewindow {
min-width: 52px;
section.tb-timewindow {
min-height: 32px;
padding: 0 6px;
span {
pointer-events: all;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: all;
cursor: pointer;
}
}
}

7
ui/src/app/components/widget/action/manage-widget-actions.scss

@ -13,18 +13,19 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-manage-widget-actions {
table.md-table {
tbody {
tr {
td {
&.tb-action-cell {
width: 100px;
min-width: 100px;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 100px;
max-width: 100px;
width: 100px;
}
}
}

4
ui/src/app/components/widget/widget-config.scss

@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-widget-config {
md-tab-content.md-active > div {
height: 100%;
}
.tb-advanced-widget-config {
height: 100%;
}
}
}

11
ui/src/app/components/widget/widget.scss

@ -13,18 +13,21 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-widget {
.tb-widget-error {
display: flex;
justify-content: center;
align-items: center;
background: rgba(255,255,255,0.5);
justify-content: center;
background: rgba(255, 255, 255, .5);
span {
color: red;
color: #f00;
}
}
.tb-widget-loading {
background: rgba(255,255,255,0.15);
z-index: 3;
background: rgba(255, 255, 255, .15);
}
}

28
ui/src/app/components/widgets-bundle-select.scss

@ -17,9 +17,10 @@
tb-widgets-bundle-select {
md-select {
margin: 0;
padding: 5px 20px;
margin: 0;
}
.tb-bundle-item {
height: 24px;
line-height: 24px;
@ -33,24 +34,29 @@ tb-widgets-bundle-select {
}
}
tb-widgets-bundle-select, .tb-widgets-bundle-select {
tb-widgets-bundle-select,
.tb-widgets-bundle-select {
.md-text {
display: block;
width: 100%;
}
.tb-bundle-item {
display: inline-block;
width: 100%;
span {
display: inline-block;
vertical-align: middle;
}
.tb-bundle-system {
font-size: 0.8rem;
opacity: 0.8;
float: right;
font-size: .8rem;
opacity: .8;
}
}
md-option {
height: auto !important;
white-space: normal !important;
@ -60,19 +66,23 @@ tb-widgets-bundle-select, .tb-widgets-bundle-select {
md-toolbar {
tb-widgets-bundle-select {
md-select {
background: rgba(255,255,255,0.2);
background: rgba(255, 255, 255, .2);
.md-select-value {
color: #fff;
font-size: 1.2rem;
span:first-child:after {
color: #fff;
color: #fff;
span:first-child::after {
color: #fff;
}
}
.md-select-value.md-select-placeholder {
color: #fff;
opacity: 0.8;
opacity: .8;
}
}
md-select.ng-invalid.ng-touched {
.md-select-value {
color: #fff !important;

3
ui/src/app/dashboard/dashboard-card.scss

@ -13,14 +13,15 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-dashboard-assigned-customers {
display: block;
display: -webkit-box;
height: 34px;
margin-bottom: 4px;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
margin-bottom: 4px;
}

26
ui/src/app/dashboard/dashboard-settings.scss

@ -13,50 +13,54 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
$previewSize: 100px;
$previewSize: 100px !default;
.tb-image-select-container {
position: relative;
height: $previewSize;
width: 100%;
height: $previewSize;
}
.tb-image-preview {
max-width: $previewSize;
max-height: $previewSize;
width: auto;
max-width: $previewSize;
height: auto;
max-height: $previewSize;
}
.tb-image-preview-container {
position: relative;
float: left;
width: $previewSize;
height: $previewSize;
margin-right: 12px;
border: solid 1px;
vertical-align: top;
float: left;
border: solid 1px;
div {
width: 100%;
font-size: 18px;
text-align: center;
}
div, .tb-image-preview {
div,
.tb-image-preview {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%,-50%);
transform: translate(-50%, -50%);
}
}
.tb-image-clear-container {
width: 48px;
height: $previewSize;
position: relative;
float: right;
width: 48px;
height: $previewSize;
}
.tb-image-clear-btn {
position: absolute !important;
top: 50%;
transform: translate(0%,-50%) !important;
transform: translate(0%, -50%) !important;
}

81
ui/src/app/dashboard/dashboard-toolbar.scss

@ -14,13 +14,14 @@
* limitations under the License.
*/
@import "~compass-sass-mixins/lib/compass";
@import '../../scss/constants';
$toolbar-height: 50px;
$fullscreen-toolbar-height: 64px;
$mobile-toolbar-height: 80px;
$half-mobile-toolbar-height: 40px;
$mobile-toolbar-height-total: 84px;
@import "../../scss/constants";
$toolbar-height: 50px !default;
$fullscreen-toolbar-height: 64px !default;
$mobile-toolbar-height: 80px !default;
$half-mobile-toolbar-height: 40px !default;
$mobile-toolbar-height-total: 84px !default;
tb-dashboard-toolbar {
md-fab-toolbar {
@ -29,126 +30,156 @@ tb-dashboard-toolbar {
.md-button {
&.md-fab {
opacity: 1;
@include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
.md-fab-toolbar-background {
background-color: $primary-default !important;
background-color: $primary-default !important;
}
}
}
}
}
md-fab-trigger {
.md-button {
&.md-fab {
line-height: 36px;
width: 36px;
height: 36px;
margin: 4px 0 0 4px;
opacity: 0.5;
line-height: 36px;
opacity: .5;
@include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
md-icon {
position: absolute;
top: 25%;
margin: 0;
line-height: 18px;
height: 18px;
width: 18px;
min-height: 18px;
min-width: 18px;
height: 18px;
min-height: 18px;
margin: 0;
line-height: 18px;
}
}
}
}
&.is-fullscreen {
&.md-is-open {
md-fab-trigger {
.md-button {
&.md-fab {
.md-fab-toolbar-background {
transition-delay: 0ms !important;
transition-duration: 0ms !important;
transition-delay: 0ms !important;
transition-duration: 0ms !important;
}
}
}
}
}
.md-fab-toolbar-wrapper {
height: $mobile-toolbar-height-total;
@media (min-width: $layout-breakpoint-sm) {
height: $fullscreen-toolbar-height;
}
md-toolbar {
min-height: $mobile-toolbar-height;
height: $mobile-toolbar-height;
min-height: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
min-height: $fullscreen-toolbar-height;
height: $fullscreen-toolbar-height;
min-height: $fullscreen-toolbar-height;
}
}
}
}
.md-fab-toolbar-wrapper {
height: $mobile-toolbar-height-total;
@media (min-width: $layout-breakpoint-sm) {
height: $toolbar-height;
}
md-toolbar {
min-height: $mobile-toolbar-height;
height: $mobile-toolbar-height;
min-height: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
min-height: $toolbar-height;
height: $toolbar-height;
min-height: $toolbar-height;
}
md-fab-actions {
margin-top: 0;
font-size: 16px;
margin-top: 0px;
@media (max-width: $layout-breakpoint-sm) {
height: $mobile-toolbar-height;
max-height: $mobile-toolbar-height;
}
.close-action {
margin-right: -18px;
}
.md-fab-action-item {
width: 100%;
height: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
height: 46px;
}
.tb-dashboard-action-panels {
flex-direction: column-reverse;
height: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
height: 46px;
}
flex-direction: column-reverse;
@media (min-width: $layout-breakpoint-sm) {
flex-direction: row-reverse;
}
.tb-dashboard-action-panel {
min-width: 0px;
flex-direction: row-reverse;
min-width: 0;
height: $half-mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
height: 46px;
}
flex-direction: row-reverse;
div {
height: $half-mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
height: 46px;
}
}
md-select {
pointer-events: all;
}
tb-states-component {
pointer-events: all;
}
button.md-icon-button {
min-width: 40px;
@media (max-width: $layout-breakpoint-sm) {
min-width: 28px;
margin: 0px;
padding: 2px;
margin: 0;
}
}
}
@ -158,4 +189,4 @@ tb-dashboard-toolbar {
}
}
}
}
}

79
ui/src/app/dashboard/dashboard.scss

@ -14,11 +14,12 @@
* limitations under the License.
*/
@import "~compass-sass-mixins/lib/compass";
@import '../../scss/constants';
$toolbar-height: 50px;
$fullscreen-toolbar-height: 64px;
$mobile-toolbar-height: 84px;
@import "../../scss/constants";
$toolbar-height: 50px !default;
$fullscreen-toolbar-height: 64px !default;
$mobile-toolbar-height: 84px !default;
section.tb-dashboard-title {
position: absolute;
@ -27,10 +28,10 @@ section.tb-dashboard-title {
}
input.tb-dashboard-title {
font-size: 2.000rem;
font-weight: 500;
letter-spacing: 0.005em;
height: 38px;
font-size: 2rem;
font-weight: 500;
letter-spacing: .005em;
}
div.tb-padded {
@ -50,9 +51,11 @@ tb-details-sidenav.tb-widget-details-sidenav {
@media (min-width: $layout-breakpoint-sm) {
width: 85% !important;
}
@media (min-width: $layout-breakpoint-md) {
width: 75% !important;
}
@media (min-width: $layout-breakpoint-lg) {
width: 60% !important;
}
@ -65,47 +68,55 @@ tb-details-sidenav.tb-widget-details-sidenav {
section.tb-dashboard-toolbar {
position: absolute;
top: 0px;
left: 0px;
top: 0;
left: 0;
z-index: 13;
pointer-events: none;
&.tb-dashboard-toolbar-opened {
right: 0px;
// @include transition(right .3s cubic-bezier(.55,0,.55,.2));
right: 0;
// @include transition(right .3s cubic-bezier(.55,0,.55,.2));
}
&.tb-dashboard-toolbar-closed {
right: 18px;
@include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
}
}
.tb-dashboard-container {
&.tb-dashboard-toolbar-opened {
&.is-fullscreen {
margin-top: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
margin-top: $fullscreen-toolbar-height;
}
}
&:not(.is-fullscreen) {
margin-top: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
margin-top: $toolbar-height;
}
@include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
}
}
&.tb-dashboard-toolbar-closed {
margin-top: 0px;
@include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
&.tb-dashboard-toolbar-opened {
&.is-fullscreen {
margin-top: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
margin-top: $fullscreen-toolbar-height;
}
}
&:not(.is-fullscreen) {
margin-top: $mobile-toolbar-height;
@media (min-width: $layout-breakpoint-sm) {
margin-top: $toolbar-height;
}
@include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
}
}
&.tb-dashboard-toolbar-closed {
margin-top: 0;
@include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
}
.tb-dashboard-layouts {
md-backdrop {
z-index: 1;
}
#tb-main-layout {
}
#tb-right-layout {
md-sidenav {
z-index: 1;
@ -124,13 +135,15 @@ section.tb-powered-by-footer {
bottom: 5px;
z-index: 3;
pointer-events: none;
span {
font-size: 12px;
a {
font-weight: bold;
font-weight: 700;
text-decoration: none;
border: none;
pointer-events: all;
border: none;
}
}
}

5
ui/src/app/dashboard/states/default-state-controller.scss

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
md-select.default-state-controller {
margin: 0px;
}
margin: 0;
}

53
ui/src/app/dashboard/states/entity-state-controller.scss

@ -13,34 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../scss/constants';
@import "../../../scss/constants";
tb-states-component {
min-width: 0px;
min-width: 0;
}
.entity-state-controller {
.state-divider {
font-size: 18px;
padding-left: 15px;
padding-right: 15px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: none;
}
.state-entry {
font-size: 18px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
outline: none;
}
md-select {
margin: 0px;
.md-text {
font-size: 18px;
font-weight: bold;
}
.state-divider {
padding-right: 15px;
padding-left: 15px;
overflow: hidden;
font-size: 18px;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: none;
}
.state-entry {
overflow: hidden;
font-size: 18px;
text-overflow: ellipsis;
white-space: nowrap;
outline: none;
}
md-select {
margin: 0;
.md-text {
font-size: 18px;
font-weight: 700;
}
}
}
}

9
ui/src/app/dashboard/states/manage-dashboard-states.scss

@ -13,21 +13,22 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.manage-dashboard-states {
table.md-table {
tbody {
tr {
td {
&.tb-action-cell {
width: 100px;
min-width: 100px;
max-width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
min-width: 100px;
max-width: 100px;
width: 100px;
}
}
}
}
}
}
}

29
ui/src/app/entity/alias/aliases-entity-select.scss

@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../scss/constants';
@import "../../../scss/constants";
tb-aliases-entity-select {
min-width: 52px;
@ -26,18 +26,21 @@ tb-aliases-entity-select {
}
.tb-aliases-entity-select-panel {
min-width: 300px;
max-height: 150px;
overflow-x: hidden;
overflow-y: auto;
background: #fff;
border-radius: 4px;
box-shadow:
0 7px 8px -4px rgba(0, 0, 0, .2),
0 13px 19px 2px rgba(0, 0, 0, .14),
0 5px 24px 4px rgba(0, 0, 0, .12);
@media (min-height: 350px) {
max-height: 250px;
}
min-width: 300px;
background: white;
border-radius: 4px;
box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
0 13px 19px 2px rgba(0, 0, 0, 0.14),
0 5px 24px 4px rgba(0, 0, 0, 0.12);
overflow-x: hidden;
overflow-y: auto;
md-content {
background-color: #fff;
}
@ -46,15 +49,17 @@ tb-aliases-entity-select {
section.tb-aliases-entity-select {
min-height: 32px;
padding: 0 6px;
@media (max-width: $layout-breakpoint-sm) {
padding: 0px;
padding: 0;
}
span {
max-width: 200px;
pointer-events: all;
cursor: pointer;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
pointer-events: all;
cursor: pointer;
}
}

5
ui/src/app/entity/alias/entity-alias-dialog.scss

@ -13,14 +13,17 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-alias-dialog {
.tb-resolve-multiple-switch {
padding-left: 10px;
.resolve-multiple-switch {
margin: 0;
}
.resolve-multiple-label {
margin: 5px 0;
}
}
}
}

18
ui/src/app/entity/alias/entity-aliases.scss

@ -13,35 +13,43 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-aliases-dialog {
.md-dialog-content {
padding-bottom: 0px;
padding-top: 0px;
padding-top: 0;
padding-bottom: 0;
}
.tb-aliases-header {
min-height: 40px;
padding: 0 34px 0 34px;
margin: 5px;
.tb-header-label {
font-size: 14px;
color: rgba(0, 0, 0, 0.570588);
color: rgba(0, 0, 0, .570588);
}
}
.tb-alias {
padding: 0 0 0 10px;
margin: 5px;
md-input-container {
margin: 0px;
margin: 0;
}
.tb-resolve-multiple-switch {
padding-left: 10px;
.resolve-multiple-switch {
margin: 0;
}
}
.md-button {
&.md-icon-button {
margin: 0px;
margin: 0;
}
}
}

30
ui/src/app/entity/attribute/attribute-table.scss

@ -13,46 +13,48 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../scss/constants';
@import "../../../scss/constants";
$md-light: rgba(255, 255, 255, 100%);
$md-edit-icon-fill: #757575;
$md-light: rgba(255, 255, 255, 100%) !default;
$md-edit-icon-fill: #757575 !default;
md-toolbar.md-table-toolbar.alternate {
.md-toolbar-tools {
md-icon {
color: $md-light;
}
md-icon {
color: $md-light;
}
}
}
.md-table {
&.tb-attribute-table {
table-layout: fixed;
td.md-cell {
&.tb-value-cell {
overflow: auto;
}
table-layout: fixed;
td.md-cell {
&.tb-value-cell {
overflow: auto;
}
}
}
.md-cell {
ng-md-icon {
fill: $md-edit-icon-fill;
float: right;
height: 16px;
fill: $md-edit-icon-fill;
}
}
}
.widgets-carousel {
position: relative;
margin: 0px;
height: calc(100% - 100px);
min-height: 150px !important;
margin: 0;
tb-dashboard {
#gridster-parent {
padding: 0 7px;
}
}
}
}

2
ui/src/app/entity/entity-autocomplete.scss

@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-autocomplete {
.tb-entity-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;

11
ui/src/app/entity/entity-filter-view.scss

@ -13,20 +13,23 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-filter-view {
.entity-filter-empty {
color: rgba(221, 44, 0, 0.87);
font-size: 14px;
line-height: 16px;
color: rgba(221, 44, 0, .87);
}
.entity-filter-type {
font-size: 14px;
line-height: 16px;
color: rgba(0, 0, 0, 0.570588);
color: rgba(0, 0, 0, .570588);
}
.entity-filter-value {
font-size: 14px;
line-height: 16px;
color: rgba(0, 0, 0, 0.570588);
color: rgba(0, 0, 0, .570588);
}
}
}

8
ui/src/app/entity/entity-filter.scss

@ -13,10 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-filter {
.tb-entity-filter {
#relationsQueryFilter {
padding-top: 20px;
tb-entity-select {
min-height: 92px;
}
@ -24,12 +25,13 @@
.tb-root-state-entity-switch {
padding-left: 10px;
.root-state-entity-switch {
margin: 0;
}
.root-state-entity-label {
margin: 5px 0;
}
}
}
}

7
ui/src/app/entity/entity-list.scss

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*.tb-entity-list {
/*
.tb-entity-list {
#entity_list_chips {
.md-chips {
padding-bottom: 1px;
@ -26,4 +28,5 @@
padding-left: 1px;
}
}
}*/
}
*/

6
ui/src/app/entity/entity-select.scss

@ -13,6 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-select {
}
/*
.tb-entity-select {
}
*/

2
ui/src/app/entity/entity-subtype-autocomplete.scss

@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-entity-subtype-autocomplete {
.tb-entity-subtype-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;

7
ui/src/app/entity/entity-subtype-list.scss

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*.tb-entity-subtype-list {
/*
.tb-entity-subtype-list {
#entity_subtype_list_chips {
.md-chips {
padding-bottom: 1px;
@ -26,4 +28,5 @@
padding-left: 1px;
}
}
}*/
}
*/

1
ui/src/app/entity/entity-subtype-select.scss

@ -13,6 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
md-select.tb-entity-subtype-select {
min-width: 200px;
}

7
ui/src/app/entity/entity-type-list.scss

@ -13,7 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*.tb-entity-type-list {
/*
.tb-entity-type-list {
#entity_type_list_chips {
.md-chips {
padding-bottom: 1px;
@ -26,4 +28,5 @@
padding-left: 1px;
}
}
}*/
}
*/

3
ui/src/app/entity/entity-type-select.scss

@ -13,5 +13,8 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/*
md-select.tb-entity-type-select {
}
*/

10
ui/src/app/entity/relation/relation-dialog.scss

@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-relation-additional-info-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: 100%;
margin-left: 15px;
border: 1px solid #c0c0c0;
#tb-relation-additional-info {
min-width: 200px;
min-height: 200px;
width: 100%;
min-width: 200px;
height: 100%;
min-height: 200px;
}
}

29
ui/src/app/entity/relation/relation-filters.scss

@ -13,32 +13,37 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-relation-filters {
.header {
padding-left: 5px;
padding-right: 5px;
padding-bottom: 5px;
padding-left: 5px;
.cell {
padding-left: 5px;
padding-right: 5px;
color: rgba(0,0,0,.54);
padding-left: 5px;
font-size: 12px;
font-weight: 700;
color: rgba(0, 0, 0, .54);
white-space: nowrap;
}
}
.body {
padding-left: 5px;
padding-right: 5px;
max-height: 300px;
overflow: auto;
padding-right: 5px;
padding-bottom: 20px;
padding-left: 5px;
overflow: auto;
.row {
padding-top: 5px;
}
.cell {
padding-left: 5px;
padding-right: 5px;
padding-left: 5px;
md-select {
margin: 0 0 24px;
@ -49,25 +54,27 @@
}
md-chips-wrap {
padding: 0px;
padding: 0;
margin: 0 0 24px;
md-autocomplete {
height: 30px;
md-autocomplete-wrap {
height: 30px;
}
}
}
.md-chips .md-chip-input-container input {
padding: 2px 2px 2px;
height: 26px;
padding: 2px 2px 2px;
line-height: 26px;
}
}
.md-button {
margin: 0;
}
}
}
}

4
ui/src/app/entity/relation/relation-table.scss

@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../../scss/constants';
@import "../../../scss/constants";
$md-light: rgba(255, 255, 255, 100%);
$md-light: rgba(255, 255, 255, 100%) !default;
.tb-relation-table {
md-toolbar.md-table-toolbar.alternate {

2
ui/src/app/entity/relation/relation-type-autocomplete.scss

@ -13,11 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.tb-relation-type-autocomplete {
.tb-relation-type-item {
display: block;
height: 48px;
}
li {
height: auto !important;
white-space: normal !important;

75
ui/src/app/event/event.scss

@ -13,24 +13,28 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
md-list.tb-event-table {
padding: 0px;
padding: 0;
md-list-item {
padding: 0px;
}
md-list-item {
padding: 0;
}
.tb-row {
height: 48px;
padding: 0px;
padding: 0;
overflow: hidden;
.tb-cell {
text-overflow: ellipsis;
&.tb-scroll {
white-space: nowrap;
overflow-y: hidden;
overflow-x: auto;
overflow-y: hidden;
white-space: nowrap;
}
&.tb-nowrap {
white-space: nowrap;
}
@ -38,7 +42,7 @@ md-list.tb-event-table {
}
.tb-row:hover {
background-color: #EEEEEE;
background-color: #eee;
}
.tb-header:hover {
@ -46,44 +50,45 @@ md-list.tb-event-table {
}
.tb-header {
.tb-cell {
color: rgba(0,0,0,.54);
font-size: 12px;
font-weight: 700;
background: none;
white-space: nowrap;
}
.tb-cell {
font-size: 12px;
font-weight: 700;
color: rgba(0, 0, 0, .54);
white-space: nowrap;
background: none;
}
}
.tb-cell {
&:first-child {
padding-left: 14px;
}
&:last-child {
padding-right: 14px;
}
padding: 0 6px;
margin: auto 0;
color: rgba(0,0,0,.87);
font-size: 13px;
vertical-align: middle;
text-align: left;
overflow: hidden;
.md-button {
padding: 0;
margin: 0;
}
&:first-child {
padding-left: 14px;
}
&:last-child {
padding-right: 14px;
}
padding: 0 6px;
margin: auto 0;
overflow: hidden;
font-size: 13px;
color: rgba(0, 0, 0, .87);
text-align: left;
vertical-align: middle;
.md-button {
padding: 0;
margin: 0;
}
}
.tb-cell.tb-number {
text-align: right;
}
}
#tb-event-content {
min-width: 400px;
min-height: 50px;
width: 100%;
min-width: 400px;
height: 100%;
min-height: 50px;
}

21
ui/src/app/extension/extension-table.scss

@ -13,23 +13,26 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
@import '../../scss/constants';
@import "../../scss/constants";
.extension-table {
md-input-container .md-errors-spacer {
min-height: 0;
}
/*&.tb-data-table table.md-table tbody tr td.tb-action-cell,
/*
&.tb-data-table table.md-table tbody tr td.tb-action-cell,
&.tb-data-table table.md-table.md-row-select tbody tr td.tb-action-cell {
width: 114px;
}*/
}
*/
.sync-widget {
max-height: 90px;
overflow: hidden;
}
.toolbar-widget {
min-height: 39px;
max-height: 39px;
@ -37,11 +40,13 @@
}
.extension__syncStatus--black {
color: #000000!important;
color: #000 !important;
}
.extension__syncStatus--green {
color: #228634!important;
color: #228634 !important;
}
.extension__syncStatus--red {
color: #862222!important;
}
color: #862222 !important;
}

32
ui/src/app/extension/extensions-forms/extension-form.scss

@ -13,28 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.extension-form {
li > .md-button {
color: rgba(0, 0, 0, 0.7);
margin: 0;
color: rgba(0, 0, 0, .7);
}
.vAccordion--default {
margin-top: 0;
padding-left: 3px;
margin-top: 0;
}
.tb-container {
width:100%;
width: 100%;
}
.dropdown-messages {
.tb-error-message {
padding: 5px 0 0 0;
}
}
.dropdown-section {
margin-bottom: 30px;
}
v-pane.inner-invalid > v-pane-header {
border-bottom: 2px solid rgb(221,44,0);
border-bottom: 2px solid rgb(221, 44, 0);
}
}
@ -45,17 +51,19 @@
}
.tb-extension-custom-transformer-panel {
margin-left: 15px;
border: 1px solid #C0C0C0;
height: 100%;
margin-left: 15px;
border: 1px solid #c0c0c0;
.tb-extension-custom-transformer {
min-width: 600px;
min-height: 200px;
width: 100%;
min-width: 600px;
height: 100%;
min-height: 200px;
}
.ace_text-input {
position:absolute!important
position: absolute !important;
}
}
@ -68,9 +76,9 @@
}
.tb-drop-file-input-hide {
height: 200%;
display: block;
position: absolute;
bottom: 0;
display: block;
width: 100%;
}
height: 200%;
}

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

Loading…
Cancel
Save