diff --git a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java b/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java index 8e7a9ae7e1..6a78f78d1b 100644 --- a/application/src/main/java/org/thingsboard/server/actors/app/AppActor.java +++ b/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; } diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java index a6daeecf4a..24608cb532 100644 --- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java +++ b/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 { diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 10a16ec9d5..7062ca97f9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/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 queries = toKeysList(keys).stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, interval, limit, agg)) + List 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)); diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java index 7f274ec6d7..724f652609 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java +++ b/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 functionsMap = new ConcurrentHashMap<>(); - - private Map blackListedFunctions = new ConcurrentHashMap<>(); + private final Map functionsMap = new ConcurrentHashMap<>(); + private final Map blackListedFunctions = new ConcurrentHashMap<>(); + private final Map> scriptToId = new ConcurrentHashMap<>(); + private final Map scriptIdToCount = new ConcurrentHashMap<>(); @PostConstruct public void init() { @@ -78,19 +80,27 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic @Override public ListenableFuture 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 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 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 deduplicate(JsScriptType scriptType, String scriptBody) { + Pair precomputed = Pair.of(UUID.randomUUID(), new AtomicInteger()); + + Pair 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; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index 98b0e77a0a..2ba87ec8da 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/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); } } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index 9551f9fe9f..35455b9f2e 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/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 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 queries = new ArrayList<>(); + List 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), diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java index 3b2d69030e..2ff8e89070 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java +++ b/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 keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet())); - List queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))) + List queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))) .collect(Collectors.toList()); FutureCallback> callback = new FutureCallback>() { @@ -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 queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(), + List queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList()); final FutureCallback> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java b/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java index 49dd20959f..d7ab6e3d54 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EntityView.java +++ b/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"); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseDeleteTsKvQuery.java new file mode 100644 index 0000000000..11b95ca62b --- /dev/null +++ b/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); + } + + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java new file mode 100644 index 0000000000..3c48adfc18 --- /dev/null +++ b/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"); + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java index 0afe00b4c3..c4fa69c18c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java +++ b/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); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/DeleteTsKvQuery.java new file mode 100644 index 0000000000..7607a59e36 --- /dev/null +++ b/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(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/ReadTsKvQuery.java new file mode 100644 index 0000000000..7d54745d11 --- /dev/null +++ b/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(); + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java index ca9f90c4e8..9df514e41d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/TsKvQuery.java +++ b/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(); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 9b89fe29f3..55c5987c74 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/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 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 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 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 fromAndTypeGroup = new ArrayList<>(); fromAndTypeGroup.add(relation.getFrom()); fromAndTypeGroup.add(relation.getTypeGroup()); + fromAndTypeGroup.add(EntitySearchDirection.FROM.name()); cache.evict(fromAndTypeGroup); List toAndTypeGroup = new ArrayList<>(); toAndTypeGroup.add(relation.getTo()); toAndTypeGroup.add(relation.getTypeGroup()); + toAndTypeGroup.add(EntitySearchDirection.TO.name()); cache.evict(toAndTypeGroup); List 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 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 fromAndTypeGroup = new ArrayList<>(); + fromAndTypeGroup.add(from); + fromAndTypeGroup.add(typeGroup); + fromAndTypeGroup.add(EntitySearchDirection.FROM.name()); + + Cache cache = cacheManager.getCache(RELATIONS_CACHE); + List fromCache = cache.get(fromAndTypeGroup, List.class); + if (fromCache != null) { + return Futures.immediateFuture(fromCache); + } else { + ListenableFuture> relationsFuture = relationDao.findAllByFrom(from, typeGroup); + Futures.addCallback(relationsFuture, + new FutureCallback>() { + @Override + public void onSuccess(@Nullable List 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 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 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 toAndTypeGroup = new ArrayList<>(); + toAndTypeGroup.add(to); + toAndTypeGroup.add(typeGroup); + toAndTypeGroup.add(EntitySearchDirection.TO.name()); + + Cache cache = cacheManager.getCache(RELATIONS_CACHE); + List fromCache = cache.get(toAndTypeGroup, List.class); + if (fromCache != null) { + return Futures.immediateFuture(fromCache); + } else { + ListenableFuture> relationsFuture = relationDao.findAllByTo(to, typeGroup); + Futures.addCallback(relationsFuture, + new FutureCallback>() { + @Override + public void onSuccess(@Nullable List 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 findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) { try { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java index e5f145f0fd..1eb2f0026d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/JpaTimeseriesDao.java +++ b/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> findAllAsync(EntityId entityId, List queries) { + public ListenableFuture> findAllAsync(EntityId entityId, List queries) { List>> futures = queries .stream() .map(query -> findAllAsync(entityId, query)) @@ -121,7 +122,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp }, service); } - private ListenableFuture> findAllAsync(EntityId entityId, TsKvQuery query) { + private ListenableFuture> 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> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) { + private ListenableFuture> 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 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 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 removePartition(EntityId entityId, DeleteTsKvQuery query) { + return service.submit(() -> null); + } + @PreDestroy void onDestroy() { if (insertService != null) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java index a1d19208b5..2b39d2596e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/timeseries/TsKvRepository.java +++ b/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 :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 :startTs AND tskv.ts < :endTs") CompletableFuture 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 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 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 :startTs AND tskv.ts < :endTs") CompletableFuture 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); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index 9b837b24a5..605c9410f6 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/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> findAll(EntityId entityId, List queries) { + public ListenableFuture> findAll(EntityId entityId, List 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> 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 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 matchingKeys = chooseKeysForEntityView(entityView, keys); + List 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 updateQueriesForEntityView(EntityView entityView, List queries) { - List newQueries = new ArrayList<>(); + private List updateQueriesForEntityView(EntityView entityView, List queries) { + List 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 chooseKeysForEntityView(EntityView entityView, Collection keys) { Collection 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> remove(EntityId entityId, List deleteTsKvQueries) { + validate(entityId); + deleteTsKvQueries.forEach(BaseTimeseriesService::validate); + List> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY); + for (DeleteTsKvQuery tsKvQuery : deleteTsKvQueries) { + deleteAndRegisterFutures(futures, entityId, tsKvQuery); + } + return Futures.allAsList(futures); + } + + private void deleteAndRegisterFutures(List> 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 { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index c025e64a31..03d569d78e 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/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 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 partition = TsPartitionDate.parse(partitioning); if (partition.isPresent()) { tsFormat = partition.get(); @@ -125,7 +130,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem } @Override - public ListenableFuture> findAllAsync(EntityId entityId, List queries) { + public ListenableFuture> findAllAsync(EntityId entityId, List queries) { List>> futures = queries.stream().map(query -> findAllAsync(entityId, query)).collect(Collectors.toList()); return Futures.transform(Futures.allAsList(futures), new Function>, List>() { @Nullable @@ -142,7 +147,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem } - private ListenableFuture> findAllAsync(EntityId entityId, TsKvQuery query) { + private ListenableFuture> 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> getPartitionsFuture(TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) { + private ListenableFuture> 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> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) { - + private ListenableFuture> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) { long minPartition = toPartitionTs(query.getStartTs()); long maxPartition = toPartitionTs(query.getEndTs()); - final ListenableFuture> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition); final SimpleListenableFuture> 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> findAndAggregateAsync(EntityId entityId, TsKvQuery query, long minPartition, long maxPartition) { + private ListenableFuture> 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> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition); ListenableFuture> aggregationChunks = Futures.transformAsync(partitionsListFuture, getFetchChunksAsyncFunction(entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor); @@ -260,7 +261,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem private AsyncFunction, List> 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 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 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 resultFuture = new SimpleListenableFuture<>(); + final ListenableFuture> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor); + + Futures.addCallback(partitionsListFuture, new FutureCallback>() { + @Override + public void onSuccess(@Nullable List 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 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() { + @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 removeLatest(EntityId entityId, DeleteTsKvQuery query) { + ListenableFuture latestEntryFuture = findLatest(entityId, query.getKey()); + + ListenableFuture 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 removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> { + if (isRemove) { + return deleteLatest(entityId, query.getKey()); + } + return Futures.immediateFuture(null); + }, readResultsProcessingExecutor); + + if (query.getRewriteLatestIfDeleted()) { + ListenableFuture 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 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> 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 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 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 resultFuture = new SimpleListenableFuture<>(); + final ListenableFuture> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor); + + Futures.addCallback(partitionsListFuture, new FutureCallback>() { + @Override + public void onSuccess(@Nullable List partitions) { + int index = 0; + if (minPartition != query.getStartTs()) { + index = 1; + } + List 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 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() { + @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 convertResultToTsKvEntryList(List rows) { List 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() { diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/QueryCursor.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/QueryCursor.java new file mode 100644 index 0000000000..f2106086ae --- /dev/null +++ b/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 partitions; + private int partitionIndex; + + public QueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List 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; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java index 1e3f4cecb7..a8cb547c3b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java +++ b/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> findAllAsync(EntityId entityId, List queries); + ListenableFuture> findAllAsync(EntityId entityId, List queries); ListenableFuture findLatest(EntityId entityId, String key); @@ -38,4 +39,10 @@ public interface TimeseriesDao { ListenableFuture savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl); ListenableFuture saveLatest(EntityId entityId, TsKvEntry tsKvEntry); + + ListenableFuture remove(EntityId entityId, DeleteTsKvQuery query); + + ListenableFuture removeLatest(EntityId entityId, DeleteTsKvQuery query); + + ListenableFuture removePartition(EntityId entityId, DeleteTsKvQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java index 2cd2d8dab9..c5076d2774 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java +++ b/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> findAll(EntityId entityId, List queries); + ListenableFuture> findAll(EntityId entityId, List queries); ListenableFuture> findLatest(EntityId entityId, Collection keys); @@ -37,4 +38,6 @@ public interface TimeseriesService { ListenableFuture> save(EntityId entityId, TsKvEntry tsKvEntry); ListenableFuture> save(EntityId entityId, List tsKvEntry, long ttl); + + ListenableFuture> remove(EntityId entityId, List queries); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java index d6b6bbd5c0..b56be56637 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TsKvQueryCursor.java +++ b/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 partitions; +public class TsKvQueryCursor extends QueryCursor { + @Getter private final List data; + @Getter + private String orderBy; private int partitionIndex; private int currentLimit; - public TsKvQueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List 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 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); + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index 743aefcc40..d269126d90 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/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)); } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index f045bf112f..4528d9afb1 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/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 list = tsService.findAll(deviceId, Collections.singletonList( + new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get(); + Assert.assertEquals(1, list.size()); + + List 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 list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0, + List 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()); diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java index a59d83b14b..bcb8bd15ba 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java +++ b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientConfig.java @@ -40,6 +40,8 @@ public final class MqttClientConfig { private Class 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; + } } diff --git a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java b/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java index a5df846899..b9460b3555 100644 --- a/netty-mqtt/src/main/java/org/thingsboard/mqtt/MqttClientImpl.java +++ b/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())); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java index aa57a027b8..c70b9060ee 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java +++ b/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 callback); + void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set attributes); + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java new file mode 100644 index 0000000000..d26f178f34 --- /dev/null +++ b/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 FIRST/LAST/ALL to fetch telemetry of certain time range that are added into Message metadata without any prefix. " + + "If selected fetch mode ALL Telemetry will be added like array into Message Metadata where key is Timestamp and value is value of Telemetry. " + + "If selected fetch mode FIRST or LAST 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 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 queries = buildQueries(); + ListenableFuture> 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 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 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() { + + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java new file mode 100644 index 0000000000..37ce0af42b --- /dev/null +++ b/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 { + + 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 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; + } +} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index 4f82884f33..5cdb04ed5b 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/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 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 diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index e06d2536a7..2e0ab78e50 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1,4 +1,4 @@ -!function(e){function t(r){if(n[r])return n[r].exports;var a=n[r]={exports:{},id:r,loaded:!1};return e[r].call(a.exports,a,a.exports,t),a.loaded=!0,a.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),r=e[t[0]];return function(e,t,a){r.apply(this,[e,t,a].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(76)},function(e,t){},1,1,1,function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }}
tb.rulenode.headers-hint
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'; -},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports='
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},22,function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(5),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(6),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue},a.testDetailsBuildJs=function(e){var n=angular.copy(a.configuration.alarmDetailsBuildJs);r.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(7),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n,r){var a=function(a,i,l,s){var u=o.default;i.html(u),a.types=n,a.originator=null,a.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(a.configuration)}),s.$render=function(){a.configuration=s.$viewValue,a.configuration.originatorId&&a.configuration.originatorType?a.originator={id:a.configuration.originatorId,entityType:a.configuration.originatorType}:a.originator=null,a.$watch("originator",function(e,t){angular.equals(e,t)||(a.originator?(s.$viewValue.originatorId=a.originator.id,s.$viewValue.originatorType=a.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},a.testScript=function(e){var n=angular.copy(a.configuration.jsScript);r.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}a.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(1);var i=n(8),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(54),i=r(a),o=n(38),l=r(o),s=n(41),u=r(s),d=n(40),c=r(d),m=n(39),g=r(m),p=n(44),f=r(p),b=n(49),v=r(b),y=n(50),q=r(y),h=n(48),$=r(h),k=n(43),T=r(k),w=n(52),x=r(w),C=n(53),M=r(C),_=n(47),S=r(_),N=n(45),V=r(N),j=n(51),P=r(j),F=n(46),E=r(F);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",q.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",T.default).directive("tbActionNodeSnsConfig",x.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",S.default).directive("tbActionNodeMqttConfig",V.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",E.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(9),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript);n.testNodeScript(e,a,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(10),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$mdExpansionPanel=t,r.ruleNodeTypes=n,r.credentialsTypeChanged=function(){var e=r.configuration.credentials.type;r.configuration.credentials={},r.configuration.credentials.type=e,r.updateValidity()},r.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){r.$apply(function(){if(n.target.result){l.$setDirty();var a=n.target.result;a&&a.length>0&&("caCert"==t&&(r.configuration.credentials.caCertFileName=e.name,r.configuration.credentials.caCert=a),"privateKey"==t&&(r.configuration.credentials.privateKeyFileName=e.name,r.configuration.credentials.privateKey=a),"Cert"==t&&(r.configuration.credentials.certFileName=e.name,r.configuration.credentials.cert=a)),r.updateValidity()}})},n.readAsText(e.file)},r.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(r.configuration.credentials.caCertFileName=null,r.configuration.credentials.caCert=null),"privateKey"==e&&(r.configuration.credentials.privateKeyFileName=null,r.configuration.credentials.privateKey=null),"Cert"==e&&(r.configuration.credentials.certFileName=null,r.configuration.credentials.cert=null),r.updateValidity()},r.updateValidity=function(){var e=!0,t=r.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(2);var i=n(11),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(12),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(13),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(14),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(15),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(16),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(17),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(18),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(19),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(20),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(21),o=r(i)},function(e,t){"use strict";function n(e){var t=function(t,n,r,a){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(22),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(23),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(60),i=r(a),o=n(61),l=r(o),s=n(58),u=r(s),d=n(62),c=r(d),m=n(57),g=r(m),p=n(63),f=r(p);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(24),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(25),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(26),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{ -value:!0}),t.default=a;var i=n(27),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(28),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(68),i=r(a),o=n(66),l=r(o),s=n(69),u=r(s),d=n(64),c=r(d),m=n(67),g=r(m);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),a.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),a.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=a,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||a.$setViewValue(t.query)}),a.$render=function(){if(a.$viewValue){var e=a.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(33),o=r(i);n(4)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(34),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t){var n=function(n,r,a,i){var l=o.default;r.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(r.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}a.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(35),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(72),i=r(a),o=n(74),l=r(o),s=n(75),u=r(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e,t,n){var r=function(r,a,i,l){var s=o.default;a.html(s),r.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(r.configuration)}),l.$render=function(){r.configuration=l.$viewValue},r.testScript=function(e){var a=angular.copy(r.configuration.jsScript);n.testNodeScript(e,a,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,l.$setDirty()})},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}a.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(36),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){var t=function(t,n,r,a){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||a.$setViewValue(t.configuration)}),a.$render=function(){t.configuration=a.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}a.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(37),o=r(i)},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var a=n(79),i=r(a),o=n(65),l=r(o),s=n(59),u=r(s),d=n(73),c=r(d),m=n(42),g=r(m),p=n(56),f=r(p),b=n(71),v=r(b),y=n(55),q=r(y),h=n(70),$=r(h),k=n(78),T=r(k);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",$.default).config(T.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function r(e){return e&&e.__esModule?e:{default:e}}function a(e){(0,o.default)(e)}a.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a;var i=n(77),o=r(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); +!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(79)},function(e,t){},1,1,1,1,function(e,t){e.exports='
{{scope.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-details-function' | translate }}
tb.rulenode.alarm-type-required
{{ severity.name | translate}}
tb.rulenode.alarm-severity-required
{{ 'tb.rulenode.propagate' | translate }}
"},function(e,t){e.exports="
tb.rulenode.message-count-required
tb.rulenode.min-message-count-message
tb.rulenode.period-seconds-required
tb.rulenode.min-period-seconds-message
{{ 'tb.rulenode.test-generator-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.bootstrap-servers-required
tb.rulenode.min-retries-message
tb.rulenode.min-batch-size-bytes-message
tb.rulenode.min-linger-ms-message
tb.rulenode.min-buffer-memory-bytes-message
{{ ackValue }}
tb.rulenode.key-serializer-required
tb.rulenode.value-serializer-required
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-to-string-function' | translate }}
"},function(e,t){e.exports='
tb.rulenode.topic-pattern-required
tb.rulenode.mqtt-topic-pattern-hint
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
tb.rulenode.connect-timeout-required
tb.rulenode.connect-timeout-range
tb.rulenode.connect-timeout-range
{{ \'tb.rulenode.clean-session\' | translate }} {{ \'tb.rulenode.enable-ssl\' | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{ \'tb.rulenode.credentials\' | translate }}
{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}
{{credentialsValue.name | translate}}
tb.rulenode.credentials-type-required
tb.rulenode.username-required
tb.rulenode.password-required
'},function(e,t){e.exports="
tb.rulenode.period-seconds-required
tb.rulenode.min-period-0-seconds-message
tb.rulenode.max-pending-messages-required
tb.rulenode.max-pending-messages-range
tb.rulenode.max-pending-messages-range
"},function(e,t){e.exports='
{{ property }}
tb.rulenode.host-required
tb.rulenode.port-required
tb.rulenode.port-range
tb.rulenode.port-range
{{ \'tb.rulenode.automatic-recovery\' | translate }}
tb.rulenode.min-connection-timeout-ms-message
tb.rulenode.min-handshake-timeout-ms-message
'},function(e,t){e.exports='
tb.rulenode.endpoint-url-pattern-required
tb.rulenode.endpoint-url-pattern-hint
{{ type }}
tb.rulenode.headers-hint
'},function(e,t){e.exports="
"},function(e,t){e.exports="
tb.rulenode.timeout-required
tb.rulenode.min-timeout-message
"},function(e,t){e.exports='
{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}
{{smtpProtocol.toUpperCase()}}
tb.rulenode.smtp-host-required
tb.rulenode.smtp-port-required
tb.rulenode.smtp-port-range
tb.rulenode.smtp-port-range
tb.rulenode.timeout-required
tb.rulenode.min-timeout-msec-message
{{ \'tb.rulenode.enable-tls\' | translate }}
'},function(e,t){e.exports="
tb.rulenode.topic-arn-pattern-required
tb.rulenode.topic-arn-pattern-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
"},function(e,t){e.exports='
{{ type.name | translate }}
tb.rulenode.queue-url-pattern-required
tb.rulenode.queue-url-pattern-hint
tb.rulenode.min-delay-seconds-message
tb.rulenode.max-delay-seconds-message
tb.rulenode.message-attributes-hint
tb.rulenode.aws-access-key-id-required
tb.rulenode.aws-secret-access-key-required
tb.rulenode.aws-region-required
'},function(e,t){e.exports="
tb.rulenode.default-ttl-required
tb.rulenode.min-default-ttl-message
"},function(e,t){e.exports='
{{ (\'relation.search-direction.\' + direction) | translate}}
relation.relation-type
device.device-types
'; +},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},function(e,t){e.exports='
'},function(e,t){e.exports='
{{ type }}
tb.rulenode.start-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
tb.rulenode.end-interval-value-required
tb.rulenode.time-value-range
tb.rulenode.time-value-range
{{timeUnit.name | translate}}
'},function(e,t){e.exports='
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.latest-telemetry' | translate }}
"},23,function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
"},function(e,t){e.exports='
{{item}}
tb.rulenode.no-message-types-found
tb.rulenode.no-message-type-matching tb.rulenode.create-new-message-type
{{$chip.name}}
'},function(e,t){e.exports='
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-filter-function' | translate }}
"},function(e,t){e.exports="
{{ 'tb.rulenode.test-switch-function' | translate }}
"},function(e,t){e.exports='
{{ keyText }} {{ valText }}  
{{keyRequiredText}}
{{valRequiredText}}
{{ \'tb.key-val.remove-entry\' | translate }} close
{{ \'tb.key-val.add-entry\' | translate }} add {{ \'action.add\' | translate }}
'},function(e,t){e.exports="
{{ ('relation.search-direction.' + direction) | translate}}
relation.relation-filters
"},function(e,t){e.exports='
{{ source.name | translate}}
'},function(e,t){e.exports="
{{ 'tb.rulenode.test-transformer-function' | translate }}
"},function(e,t){e.exports="
tb.rulenode.from-template-required
tb.rulenode.from-template-hint
tb.rulenode.to-template-required
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.mail-address-list-template-hint
tb.rulenode.subject-template-required
tb.rulenode.subject-template-hint
tb.rulenode.body-template-required
tb.rulenode.body-template-hint
"},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(56),i=a(r),o=n(40),l=a(o),s=n(43),u=a(s),d=n(42),c=a(d),m=n(41),g=a(m),p=n(46),f=a(p),b=n(51),v=a(b),y=n(52),q=a(y),h=n(50),$=a(h),T=n(45),k=a(T),w=n(54),x=a(w),C=n(55),M=a(C),S=n(49),_=a(S),N=n(47),V=a(N),E=n(53),P=a(E),j=n(48),F=a(j);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",q.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",x.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",V.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",F.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("
"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration); +}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s);var u=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,u],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(63),i=a(r),o=n(64),l=a(o),s=n(60),u=a(s),d=n(65),c=a(d),m=n(59),g=a(m),p=n(66),f=a(p),b=n(61),v=a(b);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(26),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(71),i=a(r),o=n(69),l=a(o),s=n(72),u=a(s),d=n(67),c=a(d),m=n(70),g=a(m);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(35),o=a(i);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(36),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(37),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(75),i=a(r),o=n(77),l=a(o),s=n(78),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(38),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(39),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(82),i=a(r),o=n(68),l=a(o),s=n(62),u=a(s),d=n(76),c=a(d),m=n(44),g=a(m),p=n(58),f=a(p),b=n(74),v=a(b),y=n(57),q=a(y),h=n(73),$=a(h),T=n(81),k=a(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use ${metaKeyName} to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use ${metaKeyName} to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use ${metaKeyName} to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use ${metaKeyName} to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use ${metaKeyName} to substitute variables from metadata","request-method":"Request method",headers:"Headers","headers-hint":"Use ${metaKeyName} in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use ${metaKeyName} to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use ${metaKeyName} to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use ${metaKeyName} to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","message-attributes-hint":"Use ${metaKeyName} in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){(0,o.default)(e)}r.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(80),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},fetchModeType:["FIRST","LAST","ALL"],httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}])); //# sourceMappingURL=rulenode-core-config.js.map \ No newline at end of file diff --git a/ui/.stylelintrc b/ui/.stylelintrc new file mode 100644 index 0000000000..c145c8b0cb --- /dev/null +++ b/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 + } +} diff --git a/ui/package.json b/ui/package.json index 1b8ceda7f2..498de06d4f 100644 --- a/ui/package.json +++ b/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": { diff --git a/ui/src/app/alarm/alarm-details-dialog.scss b/ui/src/app/alarm/alarm-details-dialog.scss index b7d3cd0899..ddae1f1510 100644 --- a/ui/src/app/alarm/alarm-details-dialog.scss +++ b/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; } } diff --git a/ui/src/app/alarm/alarm.scss b/ui/src/app/alarm/alarm.scss index faf7feb92d..2f6f459b7f 100644 --- a/ui/src/app/alarm/alarm.scss +++ b/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; } diff --git a/ui/src/app/audit/audit-log-details-dialog.scss b/ui/src/app/audit/audit-log-details-dialog.scss index 1a1507c115..e15fafe01c 100644 --- a/ui/src/app/audit/audit-log-details-dialog.scss +++ b/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; -} \ No newline at end of file + min-height: 50px; + border: 1px solid #c0c0c0; +} diff --git a/ui/src/app/audit/audit-log.scss b/ui/src/app/audit/audit-log.scss index 7cae1076a2..671cdc2551 100644 --- a/ui/src/app/audit/audit-log.scss +++ b/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; } - } diff --git a/ui/src/app/components/dashboard-autocomplete.scss b/ui/src/app/components/dashboard-autocomplete.scss index 9541fe1060..8727fa6131 100644 --- a/ui/src/app/components/dashboard-autocomplete.scss +++ b/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; diff --git a/ui/src/app/components/dashboard-select.scss b/ui/src/app/components/dashboard-select.scss index 1dee53df35..9ca36b6a18 100644 --- a/ui/src/app/components/dashboard-select.scss +++ b/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; } -} \ No newline at end of file +} diff --git a/ui/src/app/components/dashboard.scss b/ui/src/app/components/dashboard.scss index ca99fcc08f..e0a9afd54e 100644 --- a/ui/src/app/components/dashboard.scss +++ b/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; } diff --git a/ui/src/app/components/datakey-config.scss b/ui/src/app/components/datakey-config.scss index 0de8b63b00..646a18ac5e 100644 --- a/ui/src/app/components/datakey-config.scss +++ b/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; } diff --git a/ui/src/app/components/datasource-entity.scss b/ui/src/app/components/datasource-entity.scss index f054cab73b..681fed1f54 100644 --- a/ui/src/app/components/datasource-entity.scss +++ b/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; } } -} \ No newline at end of file +} diff --git a/ui/src/app/components/datasource-func.scss b/ui/src/app/components/datasource-func.scss index 4fc4281418..ce4ca2b6bd 100644 --- a/ui/src/app/components/datasource-func.scss +++ b/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; diff --git a/ui/src/app/components/datasource.scss b/ui/src/app/components/datasource.scss index bb5f8276d3..d8096bde28 100644 --- a/ui/src/app/components/datasource.scss +++ b/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; } diff --git a/ui/src/app/components/datetime-period.scss b/ui/src/app/components/datetime-period.scss index 61009adda1..8a690e4f8e 100644 --- a/ui/src/app/components/datetime-period.scss +++ b/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; diff --git a/ui/src/app/components/details-sidenav.scss b/ui/src/app/components/details-sidenav.scss index c7e991912b..0873cfd4b9 100644 --- a/ui/src/app/components/details-sidenav.scss +++ b/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; diff --git a/ui/src/app/components/entity-alias-select.scss b/ui/src/app/components/entity-alias-select.scss index d143933b6f..d7f6278584 100644 --- a/ui/src/app/components/entity-alias-select.scss +++ b/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; diff --git a/ui/src/app/components/expand-fullscreen.scss b/ui/src/app/components/expand-fullscreen.scss index eef599c80f..ae7e8b825f 100644 --- a/ui/src/app/components/expand-fullscreen.scss +++ b/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; diff --git a/ui/src/app/components/grid.scss b/ui/src/app/components/grid.scss index 17054d5548..43138481a9 100644 --- a/ui/src/app/components/grid.scss +++ b/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; } diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss index ade2830714..affdc5bf57 100644 --- a/ui/src/app/components/js-func.scss +++ b/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; } diff --git a/ui/src/app/components/json-content.scss b/ui/src/app/components/json-content.scss index 287c7e3ecf..4f245afeaf 100644 --- a/ui/src/app/components/json-content.scss +++ b/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; } diff --git a/ui/src/app/components/json-form.scss b/ui/src/app/components/json-form.scss index 66a5c1ada2..f102d4cf84 100644 --- a/ui/src/app/components/json-form.scss +++ b/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; -} \ No newline at end of file + overflow: auto; +} diff --git a/ui/src/app/components/json-object-edit.scss b/ui/src/app/components/json-object-edit.scss index 232d69a2fb..a2bdb6a047 100644 --- a/ui/src/app/components/json-object-edit.scss +++ b/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; } diff --git a/ui/src/app/components/kv-map.scss b/ui/src/app/components/kv-map.scss index d51653c3b8..35267d42f5 100644 --- a/ui/src/app/components/kv-map.scss +++ b/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); } } -} \ No newline at end of file +} diff --git a/ui/src/app/components/legend-config.scss b/ui/src/app/components/legend-config.scss index fb1092895d..205d67d511 100644 --- a/ui/src/app/components/legend-config.scss +++ b/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; } diff --git a/ui/src/app/components/legend.scss b/ui/src/app/components/legend.scss index 257a77f509..e5e3f04947 100644 --- a/ui/src/app/components/legend.scss +++ b/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; } } } diff --git a/ui/src/app/components/material-icon-select.scss b/ui/src/app/components/material-icon-select.scss index e12e1d094d..2814bb3958 100644 --- a/ui/src/app/components/material-icon-select.scss +++ b/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; - } -} \ No newline at end of file + md-icon { + padding: 4px; + margin: 8px 4px 4px; + cursor: pointer; + border: solid 1px rgba(0, 0, 0, .27); + } + + md-input-container { + margin-bottom: 0; + } +} diff --git a/ui/src/app/components/material-icons-dialog.scss b/ui/src/app/components/material-icons-dialog.scss index 3048fd1a7b..e50caff242 100644 --- a/ui/src/app/components/material-icons-dialog.scss +++ b/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); } -} \ No newline at end of file +} diff --git a/ui/src/app/components/menu-link.scss b/ui/src/app/components/menu-link.scss index b21c0f489c..299797ce6b 100644 --- a/ui/src/app/components/menu-link.scss +++ b/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); } diff --git a/ui/src/app/components/react/json-form-ace-editor.scss b/ui/src/app/components/react/json-form-ace-editor.scss index 42ab8c5ed7..c08eba150e 100644 --- a/ui/src/app/components/react/json-form-ace-editor.scss +++ b/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; } -} \ No newline at end of file + + button.tidy-button { + background: rgba(220, 220, 220, .35) !important; + + span { + padding: 0 !important; + font-size: 12px !important; + } + } + } +} diff --git a/ui/src/app/components/react/json-form-color.scss b/ui/src/app/components/react/json-form-color.scss index ee55ec3a58..7bc0826fd5 100644 --- a/ui/src/app/components/react/json-form-color.scss +++ b/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 { diff --git a/ui/src/app/components/react/json-form-image.scss b/ui/src/app/components/react/json-form-image.scss index 8872b0917d..e602f5238f 100644 --- a/ui/src/app/components/react/json-form-image.scss +++ b/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; } diff --git a/ui/src/app/components/react/json-form.scss b/ui/src/app/components/react/json-form.scss index f84fb855a5..cca9d07a07 100644 --- a/ui/src/app/components/react/json-form.scss +++ b/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); } diff --git a/ui/src/app/components/related-entity-autocomplete.scss b/ui/src/app/components/related-entity-autocomplete.scss index 8d4683ac37..74f652a233 100644 --- a/ui/src/app/components/related-entity-autocomplete.scss +++ b/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; diff --git a/ui/src/app/components/side-menu.scss b/ui/src/app/components/side-menu.scss index 469c575474..46da8db448 100644 --- a/ui/src/app/components/side-menu.scss +++ b/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; } diff --git a/ui/src/app/components/timeinterval.scss b/ui/src/app/components/timeinterval.scss index 4bae01d687..c5c0683a89 100644 --- a/ui/src/app/components/timeinterval.scss +++ b/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; } diff --git a/ui/src/app/components/timewindow.scss b/ui/src/app/components/timewindow.scss index 01ad5f91f7..8d4ed89167 100644 --- a/ui/src/app/components/timewindow.scss +++ b/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; } } } diff --git a/ui/src/app/components/widget/action/manage-widget-actions.scss b/ui/src/app/components/widget/action/manage-widget-actions.scss index 91d587ba4f..69891cc0e3 100644 --- a/ui/src/app/components/widget/action/manage-widget-actions.scss +++ b/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; } } } diff --git a/ui/src/app/components/widget/widget-config.scss b/ui/src/app/components/widget/widget-config.scss index 7ff2051cdf..4ac85618ff 100644 --- a/ui/src/app/components/widget/widget-config.scss +++ b/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%; } -} \ No newline at end of file +} diff --git a/ui/src/app/components/widget/widget.scss b/ui/src/app/components/widget/widget.scss index 347417bb08..3d5a9b41fa 100644 --- a/ui/src/app/components/widget/widget.scss +++ b/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); } } diff --git a/ui/src/app/components/widgets-bundle-select.scss b/ui/src/app/components/widgets-bundle-select.scss index 41c4ee7a54..f5d1f2295d 100644 --- a/ui/src/app/components/widgets-bundle-select.scss +++ b/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; diff --git a/ui/src/app/dashboard/dashboard-card.scss b/ui/src/app/dashboard/dashboard-card.scss index 0dac2131f6..8e6626c728 100644 --- a/ui/src/app/dashboard/dashboard-card.scss +++ b/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; } diff --git a/ui/src/app/dashboard/dashboard-settings.scss b/ui/src/app/dashboard/dashboard-settings.scss index 37da008d56..e278240e68 100644 --- a/ui/src/app/dashboard/dashboard-settings.scss +++ b/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; } diff --git a/ui/src/app/dashboard/dashboard-toolbar.scss b/ui/src/app/dashboard/dashboard-toolbar.scss index 40c17c486d..80fd876ad3 100644 --- a/ui/src/app/dashboard/dashboard-toolbar.scss +++ b/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 { } } } -} \ No newline at end of file +} diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss index 224dcf7e4a..00a0653de7 100644 --- a/ui/src/app/dashboard/dashboard.scss +++ b/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; } } } diff --git a/ui/src/app/dashboard/states/default-state-controller.scss b/ui/src/app/dashboard/states/default-state-controller.scss index 5d6ae97639..0fafd3b83b 100644 --- a/ui/src/app/dashboard/states/default-state-controller.scss +++ b/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; -} \ No newline at end of file + margin: 0; +} diff --git a/ui/src/app/dashboard/states/entity-state-controller.scss b/ui/src/app/dashboard/states/entity-state-controller.scss index 4c2d0c3c54..5756182903 100644 --- a/ui/src/app/dashboard/states/entity-state-controller.scss +++ b/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; } -} \ No newline at end of file + } +} diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.scss b/ui/src/app/dashboard/states/manage-dashboard-states.scss index 2aa1ca23b0..bf139bee43 100644 --- a/ui/src/app/dashboard/states/manage-dashboard-states.scss +++ b/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; } } } } } -} \ No newline at end of file +} diff --git a/ui/src/app/entity/alias/aliases-entity-select.scss b/ui/src/app/entity/alias/aliases-entity-select.scss index 978bb3e1ab..1244437b1c 100644 --- a/ui/src/app/entity/alias/aliases-entity-select.scss +++ b/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; } } diff --git a/ui/src/app/entity/alias/entity-alias-dialog.scss b/ui/src/app/entity/alias/entity-alias-dialog.scss index bd4cd519f0..c72854fc91 100644 --- a/ui/src/app/entity/alias/entity-alias-dialog.scss +++ b/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; } } -} \ No newline at end of file +} diff --git a/ui/src/app/entity/alias/entity-aliases.scss b/ui/src/app/entity/alias/entity-aliases.scss index 4b1fbeb79c..0f66d89dee 100644 --- a/ui/src/app/entity/alias/entity-aliases.scss +++ b/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; } } } diff --git a/ui/src/app/entity/attribute/attribute-table.scss b/ui/src/app/entity/attribute/attribute-table.scss index 5d5c0bef86..ef42f4e757 100644 --- a/ui/src/app/entity/attribute/attribute-table.scss +++ b/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; } } -} \ No newline at end of file +} diff --git a/ui/src/app/entity/entity-autocomplete.scss b/ui/src/app/entity/entity-autocomplete.scss index dad3d97c8b..6617fcbf3e 100644 --- a/ui/src/app/entity/entity-autocomplete.scss +++ b/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; diff --git a/ui/src/app/entity/entity-filter-view.scss b/ui/src/app/entity/entity-filter-view.scss index 69922c8074..e85128a1f7 100644 --- a/ui/src/app/entity/entity-filter-view.scss +++ b/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); } -} \ No newline at end of file +} diff --git a/ui/src/app/entity/entity-filter.scss b/ui/src/app/entity/entity-filter.scss index bbaa108e09..950ed0ad80 100644 --- a/ui/src/app/entity/entity-filter.scss +++ b/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; } } - -} \ No newline at end of file +} diff --git a/ui/src/app/entity/entity-list.scss b/ui/src/app/entity/entity-list.scss index 6e1c8925d3..a3c1bc0fe2 100644 --- a/ui/src/app/entity/entity-list.scss +++ b/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; } } -}*/ \ No newline at end of file +} +*/ diff --git a/ui/src/app/entity/entity-select.scss b/ui/src/app/entity/entity-select.scss index ab7bd3d917..5d1b04377d 100644 --- a/ui/src/app/entity/entity-select.scss +++ b/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 { -} \ No newline at end of file +/* +.tb-entity-select { +} +*/ diff --git a/ui/src/app/entity/entity-subtype-autocomplete.scss b/ui/src/app/entity/entity-subtype-autocomplete.scss index c8e3edcfd5..6dc3ec65c3 100644 --- a/ui/src/app/entity/entity-subtype-autocomplete.scss +++ b/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; diff --git a/ui/src/app/entity/entity-subtype-list.scss b/ui/src/app/entity/entity-subtype-list.scss index f0249568fc..001ca3adc5 100644 --- a/ui/src/app/entity/entity-subtype-list.scss +++ b/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; } } -}*/ +} +*/ diff --git a/ui/src/app/entity/entity-subtype-select.scss b/ui/src/app/entity/entity-subtype-select.scss index 15b3f2ad40..9b085e22f3 100644 --- a/ui/src/app/entity/entity-subtype-select.scss +++ b/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; } diff --git a/ui/src/app/entity/entity-type-list.scss b/ui/src/app/entity/entity-type-list.scss index 21a88eee15..a270f38970 100644 --- a/ui/src/app/entity/entity-type-list.scss +++ b/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; } } -}*/ \ No newline at end of file +} +*/ diff --git a/ui/src/app/entity/entity-type-select.scss b/ui/src/app/entity/entity-type-select.scss index 3a60865d93..15f2d89e13 100644 --- a/ui/src/app/entity/entity-type-select.scss +++ b/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 { } +*/ diff --git a/ui/src/app/entity/relation/relation-dialog.scss b/ui/src/app/entity/relation/relation-dialog.scss index 1c0bf2e224..6dcfdfdba7 100644 --- a/ui/src/app/entity/relation/relation-dialog.scss +++ b/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; } } diff --git a/ui/src/app/entity/relation/relation-filters.scss b/ui/src/app/entity/relation/relation-filters.scss index 34254d0255..affea5d590 100644 --- a/ui/src/app/entity/relation/relation-filters.scss +++ b/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; } } -} \ No newline at end of file +} diff --git a/ui/src/app/entity/relation/relation-table.scss b/ui/src/app/entity/relation/relation-table.scss index 2d618fb344..873c9d70d2 100644 --- a/ui/src/app/entity/relation/relation-table.scss +++ b/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 { diff --git a/ui/src/app/entity/relation/relation-type-autocomplete.scss b/ui/src/app/entity/relation/relation-type-autocomplete.scss index 2293d79546..6a06aa31c6 100644 --- a/ui/src/app/entity/relation/relation-type-autocomplete.scss +++ b/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; diff --git a/ui/src/app/event/event.scss b/ui/src/app/event/event.scss index b0fc46fea5..5a13d1f235 100644 --- a/ui/src/app/event/event.scss +++ b/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; } diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss index c8946df1e4..b809622890 100644 --- a/ui/src/app/extension/extension-table.scss +++ b/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; -} \ No newline at end of file + color: #862222 !important; +} diff --git a/ui/src/app/extension/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss index c7fcbf48b1..2332cf53c8 100644 --- a/ui/src/app/extension/extensions-forms/extension-form.scss +++ b/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%; -} \ No newline at end of file + height: 200%; +} diff --git a/ui/src/app/help/help.scss b/ui/src/app/help/help.scss index b37dc31bbb..0dd2bc3909 100644 --- a/ui/src/app/help/help.scss +++ b/ui/src/app/help/help.scss @@ -15,8 +15,12 @@ */ @import "../../scss/constants"; -.md-button.tb-help-button-style, .tb-help-button-style { +/* +.md-button.tb-help-button-style, +.tb-help-button-style { } -.md-button.tb-help-button-pos, .tb-help-button-pos { +.md-button.tb-help-button-pos, +.tb-help-button-pos { } +*/ diff --git a/ui/src/app/home/home-links.scss b/ui/src/app/home/home-links.scss index 8476eb4917..677620d9d0 100644 --- a/ui/src/app/home/home-links.scss +++ b/ui/src/app/home/home-links.scss @@ -16,10 +16,11 @@ @import "../../scss/constants"; .tb-home-links { - .md-headline { - font-size: 20px; - @media (min-width: $layout-breakpoint-xmd) { - font-size: 24px; - } + .md-headline { + font-size: 20px; + + @media (min-width: $layout-breakpoint-xmd) { + font-size: 24px; } -} \ No newline at end of file + } +} diff --git a/ui/src/app/import-export/import-dialog.scss b/ui/src/app/import-export/import-dialog.scss index c1561f3c01..511a910cb1 100644 --- a/ui/src/app/import-export/import-dialog.scss +++ b/ui/src/app/import-export/import-dialog.scss @@ -13,29 +13,30 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -$previewSize: 100px; +$previewSize: 100px !default; .tb-file-select-container { position: relative; - height: $previewSize; width: 100%; + height: $previewSize; } .tb-file-preview { - max-width: $previewSize; - max-height: $previewSize; width: auto; + max-width: $previewSize; height: auto; + max-height: $previewSize; } .tb-file-clear-container { - width: 48px; - height: $previewSize; position: relative; float: right; + width: 48px; + height: $previewSize; } + .tb-file-clear-btn { position: absolute !important; top: 50%; - transform: translate(0%,-50%) !important; + transform: translate(0%, -50%) !important; } diff --git a/ui/src/app/jsonform/jsonform.scss b/ui/src/app/jsonform/jsonform.scss index 8205fa4733..6b1c25857d 100644 --- a/ui/src/app/jsonform/jsonform.scss +++ b/ui/src/app/jsonform/jsonform.scss @@ -13,5 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -.form { height: 400px; } -.schema { height: 800px; } \ No newline at end of file + +.form { height: 400px; } + +.schema { height: 800px; } diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss index b1bb554bb2..48c1553877 100644 --- a/ui/src/app/layout/home.scss +++ b/ui/src/app/layout/home.scss @@ -29,23 +29,31 @@ .tb-breadcrumb { font-size: 18px !important; font-weight: 400 !important; - h1, a, span { + + h1, + a, + span { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + a { border: none; - opacity: 0.75; - @include transition(opacity 0.35s); + opacity: .75; + + @include transition(opacity .35s); } - a:hover, a:focus { - opacity: 1; + + a:hover, + a:focus { text-decoration: none !important; border: none; + opacity: 1; } + .divider { - padding: 0px 30px; + padding: 0 30px; } } @@ -54,21 +62,22 @@ md-sidenav.tb-site-sidenav { } md-icon.tb-logo-title { - height: 36px; width: 200px; + height: 36px; } .tb-nav-header { - flex-shrink: 0; z-index: 2; + flex-shrink: 0; white-space: nowrap; } .tb-nav-header-toolbar { - border-bottom: 1px solid rgba(0, 0, 0, 0.12); - flex-shrink: 0; z-index: 2; + flex-shrink: 0; white-space: nowrap; + border-bottom: 1px solid rgba(0, 0, 0, .12); + .md-toolbar-tools { flex-basis: auto; } diff --git a/ui/src/app/layout/user-menu.scss b/ui/src/app/layout/user-menu.scss index 9512bca546..8b684ee5de 100644 --- a/ui/src/app/layout/user-menu.scss +++ b/ui/src/app/layout/user-menu.scss @@ -13,28 +13,32 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + div.tb-user-info { line-height: 1.5; + span { - text-transform: none; text-align: left; + text-transform: none; } + span.tb-user-display-name { - font-size: 0.800rem; + font-size: .8rem; font-weight: 300; - letter-spacing: 0.008em; + letter-spacing: .008em; } + span.tb-user-authority { - font-size: 0.800rem; + font-size: .8rem; font-weight: 300; - letter-spacing: 0.005em; - opacity: 0.8; + letter-spacing: .005em; + opacity: .8; } } md-icon.tb-mini-avatar { + width: 36px; + height: 36px; margin: auto 8px; font-size: 36px; - height: 36px; - width: 36px; } diff --git a/ui/src/app/login/login.scss b/ui/src/app/login/login.scss index 373a68f7df..2930ab9890 100644 --- a/ui/src/app/login/login.scss +++ b/ui/src/app/login/login.scss @@ -13,18 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../scss/constants'; +@import "../../scss/constants"; md-card.tb-login-card { width: 330px !important; + @media (min-width: $layout-breakpoint-sm) { width: 450px !important; } + md-card-title { img.tb-login-logo { height: 50px; } } + md-card-content { margin-top: -50px; } diff --git a/ui/src/app/rulechain/link.scss b/ui/src/app/rulechain/link.scss index 3e8661915b..b37eaf3d91 100644 --- a/ui/src/app/rulechain/link.scss +++ b/ui/src/app/rulechain/link.scss @@ -17,12 +17,14 @@ .tb-link-label-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; diff --git a/ui/src/app/rulechain/message-type-autocomplete.scss b/ui/src/app/rulechain/message-type-autocomplete.scss index 4cad9cb17b..fc702d4b89 100644 --- a/ui/src/app/rulechain/message-type-autocomplete.scss +++ b/ui/src/app/rulechain/message-type-autocomplete.scss @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + .tb-message-type-autocomplete { .tb-message-type-item { display: block; height: 48px; } + li { height: auto !important; white-space: normal !important; diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss index 5999b7edd7..8f773005fa 100644 --- a/ui/src/app/rulechain/rulechain.scss +++ b/ui/src/app/rulechain/rulechain.scss @@ -18,88 +18,106 @@ .tb-fullscreen-button-style { z-index: 1; } + section.tb-header-buttons.tb-library-open { - pointer-events: none; position: absolute; - left: 0px; - top: 0px; + top: 0; + left: 0; z-index: 1; + pointer-events: none; + .md-button.tb-btn-open-library { - left: 0px; - top: 0px; - line-height: 36px; + top: 0; + left: 0; width: 36px; height: 36px; margin: 4px 0 0 4px; - opacity: 0.5; + line-height: 36px; + opacity: .5; } } + .tb-rulechain-library { + z-index: 1; width: 250px; min-width: 250px; - z-index: 1; + md-toolbar { - min-height: 48px; height: 48px; - .md-toolbar-tools>.md-button:last-child { - margin-right: 0px; + min-height: 48px; + + .md-toolbar-tools > .md-button:last-child { + margin-right: 0; } + .md-toolbar-tools { - font-size: 14px; - padding: 0px 6px; height: 48px; + padding: 0 6px; + font-size: 14px; + .md-button.md-icon-button { - margin: 0px; + margin: 0; + &.tb-small { + width: 32px; height: 32px; min-height: 32px; - line-height: 20px; padding: 6px; - width: 32px; + line-height: 20px; + md-icon { - line-height: 20px; - font-size: 20px; - height: 20px; width: 20px; - min-height: 20px; min-width: 20px; + height: 20px; + min-height: 20px; + font-size: 20px; + line-height: 20px; } } } } } + .tb-rulechain-library-panel-group { - overflow-y: auto; overflow-x: hidden; + overflow-y: auto; + .tb-panel-title { + min-width: 150px; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; - min-width: 150px; } + .fc-canvas { background: #f9f9f9; } + md-icon.md-expansion-panel-icon { - margin-right: 0px; + margin-right: 0; } - md-expansion-panel-collapsed, .md-expansion-panel-header-container { + + md-expansion-panel-collapsed, + .md-expansion-panel-header-container { + position: static; background: #e6e6e6; border-color: #909090; - position: static; } + md-expansion-panel { &.md-open { margin-top: 0; margin-bottom: 0; } } + md-expansion-panel-content { - padding: 0px; + padding: 0; } } } + .tb-rulechain-graph { z-index: 0; overflow: auto; @@ -107,29 +125,35 @@ } #tb-rule-chain-context-menu { - padding-top: 0px; - border-radius: 8px; max-height: 404px; + padding-top: 0; + border-radius: 8px; + .tb-context-menu-header { - padding: 8px 5px 5px; - font-size: 14px; display: flex; flex-direction: row; height: 36px; min-height: 36px; + padding: 8px 5px 5px; + font-size: 14px; + &.tb-rulechain { background-color: #aac7e4; } + &.tb-link { background-color: #aac7e4; } + md-icon { - padding-left: 2px; padding-right: 10px; + padding-left: 2px; } + .tb-context-menu-title { font-weight: 500; } + .tb-context-menu-subtitle { font-size: 12px; } @@ -139,37 +163,45 @@ .fc-canvas { min-width: 100%; min-height: 100%; - outline: none; - -webkit-touch-callout: none; -webkit-user-select: none; -khtml-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; + outline: none; + -webkit-touch-callout: none; + svg { display: block; } } -.tb-rule-node, #tb-rule-chain-context-menu .tb-context-menu-header { +.tb-rule-node, +#tb-rule-chain-context-menu .tb-context-menu-header { &.tb-filter-type { background-color: #f1e861; } + &.tb-enrichment-type { background-color: #cdf14e; } + &.tb-transformation-type { background-color: #79cef1; } + &.tb-action-type { background-color: #f1928f; } + &.tb-external-type { background-color: #fbc766; } + &.tb-rule-chain-type { background-color: #d6c4f1; } + &.tb-unknown-type { background-color: #f16c29; } @@ -180,102 +212,112 @@ flex-direction: row; min-width: 150px; max-width: 150px; + height: 32px; min-height: 32px; max-height: 32px; - height: 32px; padding: 5px 10px; - border-radius: 5px; - background-color: #F15B26; - pointer-events: none; - color: #333; - border: solid 1px #777; font-size: 12px; line-height: 16px; + color: #333; + pointer-events: none; + background-color: #f15b26; + border: solid 1px #777; + border-radius: 5px; + &.tb-rule-node-highlighted:not(.tb-rule-node-invalid) { box-shadow: 0 0 10px 6px #51cbee; + .tb-node-title { + font-weight: 700; text-decoration: underline; - font-weight: bold; } } + &.tb-rule-node-invalid { box-shadow: 0 0 10px 6px #ff5c50; } + &.tb-input-type { - background-color: #a3eaa9; user-select: none; + background-color: #a3eaa9; } md-icon { - font-size: 20px; width: 20px; + min-width: 20px; height: 20px; min-height: 20px; - min-width: 20px; padding-right: 4px; + font-size: 20px; } - .tb-node-type { - } .tb-node-title { font-weight: 500; } - .tb-node-type, .tb-node-title { + + .tb-node-type, + .tb-node-title { overflow: hidden; - white-space: nowrap; text-overflow: ellipsis; + white-space: nowrap; } } .fc-node { z-index: 1; - outline: none; border-radius: 8px; + outline: none; + &.fc-dragging { z-index: 10; } + p { padding: 0 15px; text-align: center; } + .fc-node-overlay { position: absolute; - pointer-events: none; - left: 0; top: 0; right: 0; bottom: 0; + left: 0; + pointer-events: none; background-color: #000; - opacity: 0; border-radius: 5px; + opacity: 0; } + &.fc-hover { .fc-node-overlay { - opacity: 0.25; + opacity: .25; } } + &.fc-selected { .fc-node-overlay { - opacity: 0.25; + opacity: .25; } - } - &.fc-selected { + &:not(.fc-edit) { - border: solid 3px red; margin: -3px; + border: solid 3px #f00; } } } -.fc-leftConnectors, .fc-rightConnectors { +.fc-leftConnectors, +.fc-rightConnectors { position: absolute; top: 0; - height: 100%; + + z-index: 0; display: flex; flex-direction: column; + height: 100%; - z-index: 0; .fc-magnet { align-items: center; } @@ -292,18 +334,18 @@ .fc-magnet { display: flex; flex-grow: 1; - height: 60px; justify-content: center; + height: 60px; } .fc-connector { width: 14px; height: 14px; - border: 1px solid #333; margin: 10px; - border-radius: 5px; - background-color: #ccc; pointer-events: all; + background-color: #ccc; + border: 1px solid #333; + border-radius: 5px; } .fc-connector.fc-hover { @@ -312,45 +354,49 @@ .fc-arrow-marker { polygon { - stroke: gray; - fill: gray; + fill: #808080; + stroke: #808080; } } .fc-arrow-marker-selected { polygon { - stroke: red; - fill: red; + fill: #f00; + stroke: #f00; } } .fc-edge { outline: none; - stroke: gray; - stroke-width: 4; - fill: transparent; transition: stroke-width .2s; + fill: transparent; + stroke: #808080; + stroke-width: 4; + &.fc-selected { - stroke: red; - stroke-width: 4; fill: transparent; + stroke: #f00; + stroke-width: 4; } + &.fc-active { animation: dash 3s linear infinite; stroke-dasharray: 20; } + &.fc-hover { - stroke: gray; - stroke-width: 6; fill: transparent; + stroke: #808080; + stroke-width: 6; } + &.fc-dragging { pointer-events: none; } } .edge-endpoint { - fill: gray; + fill: #808080; } .fc-nodedelete { @@ -364,22 +410,23 @@ } .fc-edit { - .fc-nodedelete, .fc-nodeedit { - outline: none; - display: block; + .fc-nodedelete, + .fc-nodeedit { position: absolute; - border: solid 2px white; - border-radius: 50%; - font-weight: 600; - line-height: 20px; + display: block; + width: 22px; height: 20px; padding-top: 2px; - width: 22px; - background: #f83e05; + font-weight: 600; + line-height: 20px; color: #fff; text-align: center; vertical-align: bottom; cursor: pointer; + background: #f83e05; + border: solid 2px #fff; + border-radius: 50%; + outline: none; } .fc-nodeedit { @@ -391,7 +438,6 @@ top: -24px; right: -13px; } - } .fc-noselect { @@ -407,31 +453,37 @@ .fc-edge-label { position: absolute; transition: transform .2s; -// opacity: 0.8; + // opacity: 0.8; + &.ng-leave { transition: 0s none; } + &.fc-hover { transform: scale(1.25); } + &.fc-selected { .fc-edge-label-text { span { - border: solid red; - color: #fff; font-weight: 600; - background-color: red; + color: #fff; + background-color: #f00; + border: solid #f00; } } } + .fc-nodeedit { top: -30px; right: 14px; } + .fc-nodedelete { top: -30px; right: -13px; } + &:focus { outline: 0; } @@ -439,27 +491,28 @@ .fc-edge-label-text { position: absolute; - -webkit-transform: translate(-50%, -50%); - transform: translate(-50%, -50%); - white-space: nowrap; - text-align: center; font-size: 14px; font-weight: 600; + text-align: center; + white-space: nowrap; + -webkit-transform: translate(-50%, -50%); + transform: translate(-50%, -50%); + span { + padding: 3px 5px; + color: #003a79; cursor: default; + background-color: #fff; border: solid 2px #003a79; border-radius: 10px; - color: #003a79; - background-color: #fff; - padding: 3px 5px; } } .fc-select-rectangle { - border: 2px dashed #5262ff; position: absolute; - background: rgba(20,125,255,0.1); z-index: 2; + background: rgba(20, 125, 255, .1); + border: 2px dashed #5262ff; } @keyframes dash { @@ -468,13 +521,15 @@ } } -.tb-rule-node-tooltip, .tb-rule-node-help { +.tb-rule-node-tooltip, +.tb-rule-node-help { color: #333; } .tb-rule-node-tooltip { - font-size: 14px; max-width: 300px; + font-size: 14px; + &.tb-lib-tooltip { width: 300px; } @@ -489,28 +544,33 @@ color: #ea0d0d; } -.tb-rule-node-tooltip, .tb-rule-node-error-tooltip, .tb-rule-node-help { +.tb-rule-node-tooltip, +.tb-rule-node-error-tooltip, +.tb-rule-node-help { #tb-node-content { .tb-node-title { font-weight: 600; } + .tb-node-description { font-style: italic; color: #555; } + .tb-node-details { padding-top: 10px; padding-bottom: 10px; } + code { - padding: 0px 3px 2px 3px; + padding: 0 3px 2px 3px; margin: 1px; - color: #AD1625; + font-size: 12px; + color: #ad1625; white-space: nowrap; background-color: #f7f7f9; border: 1px solid #e1e1e8; border-radius: 2px; - font-size: 12px; } } -} \ No newline at end of file +} diff --git a/ui/src/app/rulechain/rulenode.scss b/ui/src/app/rulechain/rulenode.scss index 0466673337..0bdb3ab253 100644 --- a/ui/src/app/rulechain/rulenode.scss +++ b/ui/src/app/rulechain/rulenode.scss @@ -16,13 +16,13 @@ .tb-rulenode { tb-json-object-edit.tb-rule-node-configuration-json { - height: 300px; display: block; + height: 300px; } } .tb-rulenode-directive-error { - color: rgb(221,44,0); font-size: 13px; font-weight: 400; -} \ No newline at end of file + color: rgb(221, 44, 0); +} diff --git a/ui/src/app/rulechain/script/node-script-test.scss b/ui/src/app/rulechain/script/node-script-test.scss index a75ae59a79..1dd174dd87 100644 --- a/ui/src/app/rulechain/script/node-script-test.scss +++ b/ui/src/app/rulechain/script/node-script-test.scss @@ -13,24 +13,25 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -@import '../../../scss/constants'; +@import "../../../scss/constants"; + @import "~compass-sass-mixins/lib/compass"; md-dialog.tb-node-script-test-dialog { &.md-dialog-fullscreen { - min-height: 100%; + width: 100%; min-width: 100%; - max-height: 100%; max-width: 100%; - width: 100%; height: 100%; + min-height: 100%; + max-height: 100%; border-radius: 0; } .tb-split { @include box-sizing(border-box); - overflow-y: auto; overflow-x: hidden; + overflow-y: auto; } .ace_editor { @@ -38,13 +39,13 @@ md-dialog.tb-node-script-test-dialog { } .tb-content { - border: 1px solid #C0C0C0; padding-top: 5px; padding-left: 5px; + border: 1px solid #c0c0c0; } .gutter { - background-color: #eeeeee; + background-color: #eee; background-repeat: no-repeat; background-position: 50%; @@ -52,21 +53,23 @@ md-dialog.tb-node-script-test-dialog { .gutter.gutter-horizontal { cursor: col-resize; - background-image: url('../../../../node_modules/split.js/grips/vertical.png'); + background-image: url("../../../../node_modules/split.js/grips/vertical.png"); } .gutter.gutter-vertical { cursor: row-resize; - background-image: url('../../../../node_modules/split.js/grips/horizontal.png'); + background-image: url("../../../../node_modules/split.js/grips/horizontal.png"); } - .tb-split.tb-split-horizontal, .gutter.gutter-horizontal { - height: 100%; + .tb-split.tb-split-horizontal, + .gutter.gutter-horizontal { float: left; + height: 100%; } .tb-split.tb-split-vertical { display: flex; + .tb-split.tb-content { height: 100%; } @@ -74,42 +77,44 @@ md-dialog.tb-node-script-test-dialog { div.tb-editor-area-title-panel { position: absolute; - font-size: 0.800rem; - font-weight: 500; top: 13px; right: 40px; z-index: 5; + font-size: .8rem; + font-weight: 500; + &.tb-js-function { right: 80px; } + label { - color: #00acc1; - background: rgba(220, 220, 220, 0.35); - border-radius: 5px; padding: 4px; + color: #00acc1; text-transform: uppercase; + background: rgba(220, 220, 220, .35); + border-radius: 5px; } + .md-button { - color: #7B7B7B; min-width: 32px; min-height: 15px; - line-height: 15px; - font-size: 0.800rem; - margin: 0; padding: 4px; - background: rgba(220, 220, 220, 0.35); + margin: 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); } } .tb-resize-container { - overflow-y: auto; - height: 100%; - width: 100%; position: relative; + width: 100%; + height: 100%; + overflow-y: auto; .ace_editor { height: 100%; } } - } diff --git a/ui/src/app/services/toast.scss b/ui/src/app/services/toast.scss index 428e94e5a5..952a42d855 100644 --- a/ui/src/app/services/toast.scss +++ b/ui/src/app/services/toast.scss @@ -13,20 +13,21 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + md-toast.tb-info-toast .md-toast-content { - font-size: 18px; - padding: 18px; height: 100%; + padding: 18px; + font-size: 18px; } md-toast.tb-success-toast .md-toast-content { - font-size: 18px !important; - background-color: green; height: 100%; + font-size: 18px !important; + background-color: #008000; } md-toast.tb-error-toast .md-toast-content { - font-size: 18px !important; - background-color: maroon; height: 100%; + font-size: 18px !important; + background-color: #800000; } diff --git a/ui/src/app/user/user-fieldset.scss b/ui/src/app/user/user-fieldset.scss index cf21abcb34..93b8b510c4 100644 --- a/ui/src/app/user/user-fieldset.scss +++ b/ui/src/app/user/user-fieldset.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-default-dashboard { .tb-default-dashboard-label { padding-bottom: 8px; } + tb-dashboard-autocomplete { @media (min-width: $layout-breakpoint-sm) { padding-right: 12px; } + @media (max-width: $layout-breakpoint-sm) { padding-bottom: 12px; } } -} \ No newline at end of file +} diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss index 6a71ccd6e0..2922996700 100644 --- a/ui/src/app/widget/lib/alarms-table-widget.scss +++ b/ui/src/app/widget/lib/alarms-table-widget.scss @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + .tb-has-timewindow { .tb-alarms-table { md-toolbar { min-height: 60px; max-height: 60px; + &.md-table-toolbar { .md-toolbar-tools { max-height: 60px; @@ -28,10 +30,10 @@ } .tb-alarms-table { - md-toolbar { min-height: 39px; max-height: 39px; + &.md-table-toolbar { .md-toolbar-tools { max-height: 39px; @@ -40,14 +42,15 @@ } &.tb-data-table { - table.md-table, table.md-table.md-row-select { + table.md-table, + table.md-table.md-row-select { tbody { tr { td { &.tb-action-cell { + width: 36px; min-width: 36px; max-width: 36px; - width: 36px; } } } diff --git a/ui/src/app/widget/lib/entities-table-widget.scss b/ui/src/app/widget/lib/entities-table-widget.scss index 62a1f975cf..85648d6f20 100644 --- a/ui/src/app/widget/lib/entities-table-widget.scss +++ b/ui/src/app/widget/lib/entities-table-widget.scss @@ -13,11 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + .tb-has-timewindow { .tb-entities-table { md-toolbar { min-height: 60px; max-height: 60px; + &.md-table-toolbar { .md-toolbar-tools { max-height: 60px; @@ -28,10 +30,10 @@ } .tb-entities-table { - md-toolbar { min-height: 39px; max-height: 39px; + &.md-table-toolbar { .md-toolbar-tools { max-height: 39px; @@ -40,14 +42,15 @@ } &.tb-data-table { - table.md-table, table.md-table.md-row-select { + table.md-table, + table.md-table.md-row-select { tbody { tr { td { &.tb-action-cell { + width: 36px; min-width: 36px; max-width: 36px; - width: 36px; } } } diff --git a/ui/src/app/widget/lib/extensions-table-widget.scss b/ui/src/app/widget/lib/extensions-table-widget.scss index f5f228d558..42fcf6dc7c 100644 --- a/ui/src/app/widget/lib/extensions-table-widget.scss +++ b/ui/src/app/widget/lib/extensions-table-widget.scss @@ -13,16 +13,19 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + tb-extension-table { md-content { background-color: #fff; } } + md-tabs.hide-tabs-menu { md-tabs-wrapper { display: none; } + md-tabs-content-wrapper { top: 0 !important; } -} \ No newline at end of file +} diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss index c41db430fc..bf2d3e019f 100644 --- a/ui/src/app/widget/lib/rpc/knob.scss +++ b/ui/src/app/widget/lib/rpc/knob.scss @@ -13,141 +13,156 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -$knob-img: url('./svg/knob.svg'); +$knob-img: url("./svg/knob.svg") !default; -$bars-margin-pct: percentage(0.033); -$background-margin-pct: percentage(0.05); -$value-container-margin-pct: percentage(0.35); -$error-height: percentage(0.05); -$title-height: percentage(0.066); -$title-container-margin-pct: percentage(0.2); -$title-container-margin-bottom-pct: percentage(0.05); -$minmax-height: percentage(0.04); -$minmax-container-margin-pct: percentage(0.18); -$minmax-container-margin-bottom-pct: percentage(0.12); +$bars-margin-pct: percentage(.033) !default; +$background-margin-pct: percentage(.05) !default; +$value-container-margin-pct: percentage(.35) !default; +$error-height: percentage(.05) !default; +$title-height: percentage(.066) !default; +$title-container-margin-pct: percentage(.2) !default; +$title-container-margin-bottom-pct: percentage(.05) !default; +$minmax-height: percentage(.04) !default; +$minmax-container-margin-pct: percentage(.18) !default; +$minmax-container-margin-bottom-pct: percentage(.12) !default; -$background-color: #e6e7e8; +$background-color: #e6e7e8 !default; .tb-knob { - width:100%; - height:100%; + width: 100%; + height: 100%; background: $background-color; .knob { position: relative; + &[draggable] { -moz-user-select: none; -webkit-user-select: none; user-select: none; } + #canvasBar { - position:absolute; - top:0; - left:0; - bottom: 0; + position: absolute; + top: 0; right: 0; + bottom: 0; + left: 0; z-index: 2; } + .canvas-background { - position:absolute; + position: absolute; top: $background-margin-pct; - left: $background-margin-pct; - bottom: $background-margin-pct; right: $background-margin-pct; - border-radius: 50%; + bottom: $background-margin-pct; + left: $background-margin-pct; + z-index: 2; background: #3f4346; - z-index:2; + border-radius: 50%; } + .value-container { - position:absolute; + position: absolute; top: $value-container-margin-pct; - left: $value-container-margin-pct; - bottom: $value-container-margin-pct; right: $value-container-margin-pct; - z-index:4; + bottom: $value-container-margin-pct; + left: $value-container-margin-pct; + z-index: 4; + .knob-value { - color: #fff; font-weight: 500; + color: #fff; white-space: nowrap; } } + .error-container { - position:absolute; + position: absolute; top: 1%; - left: 0; right: 0; - z-index:4; + left: 0; + z-index: 4; height: $error-height; + .knob-error { color: #ff3315; white-space: nowrap; } } + .title-container { - position:absolute; - left: $title-container-margin-pct; - bottom: $title-container-margin-bottom-pct; + position: absolute; right: $title-container-margin-pct; - z-index:4; + bottom: $title-container-margin-bottom-pct; + left: $title-container-margin-pct; + z-index: 4; height: $title-height; + .knob-title { - color: #757575; font-weight: 500; + color: #757575; white-space: nowrap; } } + .minmax-container { - position:absolute; - left: $minmax-container-margin-pct; - bottom: $minmax-container-margin-bottom-pct; + position: absolute; right: $minmax-container-margin-pct; - z-index:4; + bottom: $minmax-container-margin-bottom-pct; + left: $minmax-container-margin-pct; + z-index: 4; height: $minmax-height; + .minmax-label { - color: #757575; font-weight: 500; + color: #757575; white-space: nowrap; } } + .top-pointer-container { - position:absolute; + position: absolute; top: $bars-margin-pct; - left: $bars-margin-pct; - bottom: $bars-margin-pct; right: $bars-margin-pct; - z-index:3; - cursor:pointer !important; + bottom: $bars-margin-pct; + left: $bars-margin-pct; + z-index: 3; + cursor: pointer !important; + .top-pointer { - content:''; - width:5%; - height:5%; - background-color:#b5b5b5; - position:absolute; - top:50%; - left:22%; - margin-top:-2.5%; + position: absolute; + top: 50%; + left: 22%; + width: 5%; + height: 5%; + margin-top: -2.5%; + cursor: pointer !important; + content: ""; + background-color: #b5b5b5; border-radius: 50%; - cursor:pointer !important; box-shadow: 1px 0 2px #040404; } } + .top{ - position:absolute; + position: absolute; top: $bars-margin-pct; - left: $bars-margin-pct; - bottom: $bars-margin-pct; right: $bars-margin-pct; - background:$knob-img no-repeat; + bottom: $bars-margin-pct; + left: $bars-margin-pct; + z-index: 2; + cursor: pointer !important; + background: $knob-img no-repeat; background-size: contain; - z-index:2; - cursor:pointer !important; } + #text-measure { position: absolute; - visibility: hidden; - height: auto; width: auto; + height: auto; white-space: nowrap; + visibility: hidden; } } } diff --git a/ui/src/app/widget/lib/rpc/led-indicator.scss b/ui/src/app/widget/lib/rpc/led-indicator.scss index 9b68c42253..b1da5c6a40 100644 --- a/ui/src/app/widget/lib/rpc/led-indicator.scss +++ b/ui/src/app/widget/lib/rpc/led-indicator.scss @@ -15,61 +15,65 @@ */ @import "~compass-sass-mixins/lib/compass"; -$error-height: 14px; +$error-height: 14px !default; -$background-color: #e6e7e8; +$background-color: #e6e7e8 !default; .tb-led-indicator { - width:100%; - height:100%; + width: 100%; + height: 100%; background: $background-color; .title-container { .led-title { - color: #757575; font-weight: 500; + color: #757575; white-space: nowrap; } } .error-container { - position:absolute; + position: absolute; top: 1%; - left: 0; right: 0; - z-index:4; + left: 0; + z-index: 4; height: $error-height; + .led-error { color: #ff3315; white-space: nowrap; } } + #text-measure { position: absolute; - visibility: hidden; - height: auto; width: auto; + height: auto; white-space: nowrap; + visibility: hidden; } #led-container { padding: 10px; + .led { - cursor: pointer; position: relative; + cursor: pointer; + background-image: -owg-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25)); + background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25)); + background-image: -moz-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25)); + background-image: -o-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25)); + background-image: radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, .25)); border-radius: 50%; - background-image: -owg-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25)); - background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25)); - background-image: -moz-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25)); - background-image: -o-radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25)); - background-image: radial-gradient(50% 50%, circle closest-corner, transparent, rgba(0, 0, 0, 0.25)); - transition: background-color 0.5s, box-shadow 0.5s; + transition: background-color .5s, box-shadow .5s; + &.disabled { - background-image: -owg-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1)); - background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1)); - background-image: -moz-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1)); - background-image: -o-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1)); - background-image: radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, 0.5), rgba(0, 0, 0, 0.1)); + background-image: -owg-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1)); + background-image: -webkit-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1)); + background-image: -moz-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1)); + background-image: -o-radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1)); + background-image: radial-gradient(50% 50%, circle closest-corner, rgba(255, 255, 255, .5), rgba(0, 0, 0, .1)); } } } diff --git a/ui/src/app/widget/lib/rpc/round-switch.scss b/ui/src/app/widget/lib/rpc/round-switch.scss index 9396abc414..2e37f632a8 100644 --- a/ui/src/app/widget/lib/rpc/round-switch.scss +++ b/ui/src/app/widget/lib/rpc/round-switch.scss @@ -15,179 +15,201 @@ */ @import "~compass-sass-mixins/lib/compass"; -$error-height: 14px; +$error-height: 14px !default; -$background-color: #e6e7e8; +$background-color: #e6e7e8 !default; .tb-round-switch { - width:100%; - height:100%; + width: 100%; + height: 100%; background: $background-color; .title-container { .switch-title { - color: #757575; font-weight: 500; + color: #757575; white-space: nowrap; } } .error-container { - position:absolute; + position: absolute; top: 1%; - left: 0; right: 0; - z-index:4; + left: 0; + z-index: 4; height: $error-height; + .switch-error { color: #ff3315; white-space: nowrap; } } + #text-measure { position: absolute; - visibility: hidden; - height: auto; width: auto; + height: auto; white-space: nowrap; + visibility: hidden; } #switch-container { padding: 10px; + .switch { - cursor: pointer; position: relative; - background:#ddd; + width: 260px; + min-width: 260px; + height: 260px; + min-height: 260px; + padding: 25px; + font-family: sans-serif; + font-size: 48px; + + color: #424242; + cursor: pointer; + -pie-background: -pie-linear-gradient(270deg, #bbb, #ddd); + background: #ddd; background: -owg-linear-gradient(270deg, #bbb, #ddd); background: -webkit-linear-gradient(270deg, #bbb, #ddd); background: -moz-linear-gradient(270deg, #bbb, #ddd); background: -o-linear-gradient(270deg, #bbb, #ddd); - -pie-background: -pie-linear-gradient(270deg, #bbb, #ddd); background: linear-gradient(180deg, #bbb, #ddd); - border-radius:130px; + border-radius: 130px; + @include box-sizing(border-box); - @include box-shadow( - 0px 0px 0px 8px rgba(0,0,0,.1) - ,0px 0px 3px 1px rgba(0,0,0,.1) - ,inset 0 8px 3px -8px rgba(255,255,255,.4)); - height: 260px; - min-height: 260px; - padding: 25px; - width: 260px; - min-width: 260px; - color: #424242; - font-family:sans-serif; - font-size:48px; + @include box-shadow( + 0 0 0 8px rgba(0,0,0,.1) + ,0 0 3px 1px rgba(0,0,0,.1) + ,inset 0 8px 3px -8px rgba(255,255,255,.4)); input { - display:none + display: none; } - .on,.off { - position:absolute; - text-align:center; + .on, + .off { + position: absolute; + width: 100%; + text-align: center; + @include text-shadow(1px 1px 4px #4a4a4a); - width:100%; } .on { - color:#444; - top:10px; - @include transition(all 0.1s); - font-family:sans-serif + top: 10px; + font-family: sans-serif; + color: #444; + + @include transition(all .1s); } .off { - bottom:5px; - @include transition(all 0.1s); - @include transform(scaleY(0.85)); + bottom: 5px; + + @include transition(all .1s); + + @include transform(scaleY(.85)); } .but { + position: relative; + display: block; + width: 200px; + height: 178px; + font-size: 48px; cursor: pointer; - background-color:#d8d8d8; + background-color: #d8d8d8; + border-bottom-width: 0; border-radius: 400px 400px 400px 400px / 400px 400px 300px 300px; - border-bottom-width:0px; + @include box-shadow(inset 8px 6px 5px -7px #a2a2a2, - inset -8px 6px 5px -7px #a2a2a2, - inset 0 -3px 2px -2px rgba(200, 200, 200, 0.5), - 0 3px 3px -2px #ffffff, - inset 0 -230px 60px -200px rgba(255, 255, 255, 0.2), - inset 0 220px 40px -200px rgba(0, 0, 0, 0.3)); - display:block; - font-size:48px; - height:178px; - position:relative; - @include transition(all 0.2s); - width:200px; + inset -8px 6px 5px -7px #a2a2a2, + inset 0 -3px 2px -2px rgba(200, 200, 200, .5), + 0 3px 3px -2px #fff, + inset 0 -230px 60px -200px rgba(255, 255, 255, .2), + inset 0 220px 40px -200px rgba(0, 0, 0, .3)); + + @include transition(all .2s); } .back { + width: 210px; + height: 210px; + padding: 4px 4px; cursor: pointer; background-color: #888787; - background-image: -owg-linear-gradient(0deg, transparent 30%, transparent 70%), -owg-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%); - background-image: -webkit-linear-gradient(0deg, transparent 30%, transparent 70%), -webkit-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%); - background-image: -moz-linear-gradient(0deg, transparent 30%, transparent 70%), -moz-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%); - background-image: -o-linear-gradient(0deg, transparent 30%, transparent 70%), -o-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%); - background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, 0.2) 50%, rgba(150, 150, 150, 0) 70%); - border-radius:105px; - @include box-shadow(30px 30px 30px -20px rgba(58, 58, 58, 0.3), - -30px 30px 30px -20px rgba(58, 58, 58, 0.3), - 0 30px 30px 0px rgba(16, 16, 16, 0.3), - inset 0 -1px 0 0 #484848); + background-image: -owg-linear-gradient(0deg, transparent 30%, transparent 70%), -owg-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%); + background-image: -webkit-linear-gradient(0deg, transparent 30%, transparent 70%), -webkit-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%); + background-image: -moz-linear-gradient(0deg, transparent 30%, transparent 70%), -moz-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%); + background-image: -o-linear-gradient(0deg, transparent 30%, transparent 70%), -o-linear-gradient(90deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%); + background-image: linear-gradient(-90deg, transparent 30%, transparent 70%), linear-gradient(0deg, rgba(150, 150, 150, 0) 30%, rgba(150, 150, 150, .2) 50%, rgba(150, 150, 150, 0) 70%); + border-radius: 105px; + + @include box-shadow(30px 30px 30px -20px rgba(58, 58, 58, .3), + -30px 30px 30px -20px rgba(58, 58, 58, .3), + 0 30px 30px 0 rgba(16, 16, 16, .3), + inset 0 -1px 0 0 #484848); + @include box-sizing(border-box); - height:210px; - padding:4px 4px; - @include transition(all 0.2s); - width:210px; + + @include transition(all .2s); } - input:checked + .back .on,input:checked + .back .off{ + input:checked + .back .on, + input:checked + .back .off{ @include text-shadow(1px 1px 4px #4a4a4a); } + input:checked + .back .on{ - color:#4c4c4c; - top:10px; - @include transform(scaleY(0.85)); + top: 10px; + color: #4c4c4c; + + @include transform(scaleY(.85)); } + input:checked + .back .off{ - color:#444; - bottom:5px; + bottom: 5px; + color: #444; + @include transform(scaleY(1)); } + input:checked + .back .but{ - background:#dcdcdc; - background-image: -owg-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent); - background-image: -webkit-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent); - background-image: -moz-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent); - background-image: -o-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent); - background-image: radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, 0.3), transparent); + margin-top: 20px; + background: #dcdcdc; + background-image: -owg-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent); + background-image: -webkit-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent); + background-image: -moz-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent); + background-image: -o-radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent); + background-image: radial-gradient(50% 15%, circle closest-corner, rgba(0, 0, 0, .3), transparent); border-radius: 400px 400px 400px 400px / 300px 300px 400px 400px; + @include box-shadow(inset 8px -4px 5px -7px #a9a9a9, - inset -8px -4px 5px -7px #808080, - 0 -3px 8px -4px rgba(50, 50, 50, 0.4), - inset 0 3px 4px -2px #9c9c9c, - inset 0 280px 40px -200px rgba(0, 0, 0, 0.2), - inset 0 -200px 40px -200px rgba(180, 180, 180, 0.2)); - margin-top:20px; + inset -8px -4px 5px -7px #808080, + 0 -3px 8px -4px rgba(50, 50, 50, .4), + inset 0 3px 4px -2px #9c9c9c, + inset 0 280px 40px -200px rgba(0, 0, 0, .2), + inset 0 -200px 40px -200px rgba(180, 180, 180, .2)); } - input:checked + .back{ - background-image: -owg-linear-gradient(90deg, #868686 30%, transparent 70%), -owg-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%); - background-image: -webkit-linear-gradient(90deg, #868686 30%, transparent 70%), -webkit-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%); - background-image: -moz-linear-gradient(90deg, #868686 30%, transparent 70%), -moz-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%); - background-image: -o-linear-gradient(90deg, #868686 30%, transparent 70%), -o-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%); - background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, 0.74) 50%, rgba(105, 105, 105, 0) 100%); - - @include box-shadow(30px 30px 30px -20px rgba(49, 49, 49, 0.1), - -30px 30px 30px -20px rgba(111, 111, 111, 0.1), - 0 30px 30px 0px rgba(0, 0, 0, 0.2), - inset 0 1px 2px 0 rgba(167, 167, 167, 0.6)); - padding:2px 4px; + input:checked + .back{ + padding: 2px 4px; + + background-image: -owg-linear-gradient(90deg, #868686 30%, transparent 70%), -owg-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%); + background-image: -webkit-linear-gradient(90deg, #868686 30%, transparent 70%), -webkit-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%); + background-image: -moz-linear-gradient(90deg, #868686 30%, transparent 70%), -moz-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%); + background-image: -o-linear-gradient(90deg, #868686 30%, transparent 70%), -o-linear-gradient(180deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%); + background-image: linear-gradient(0deg, #868686 30%, transparent 70%), linear-gradient(90deg, rgba(115, 115, 115, 0) 0%, rgba(255, 255, 255, .74) 50%, rgba(105, 105, 105, 0) 100%); + + @include box-shadow(30px 30px 30px -20px rgba(49, 49, 49, .1), + -30px 30px 30px -20px rgba(111, 111, 111, .1), + 0 30px 30px 0 rgba(0, 0, 0, .2), + inset 0 1px 2px 0 rgba(167, 167, 167, .6)); } - } } } diff --git a/ui/src/app/widget/lib/rpc/switch.scss b/ui/src/app/widget/lib/rpc/switch.scss index dc0b8eeb5e..53893a0066 100644 --- a/ui/src/app/widget/lib/rpc/switch.scss +++ b/ui/src/app/widget/lib/rpc/switch.scss @@ -13,27 +13,28 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -$thumb-img: url('./svg/thumb.svg'); -$thumb-checked-img: url('./svg/thumb-checked.svg'); -$thumb-bar-img: url('./svg/thumb-bar.svg'); -$thumb-bar-checked-img: url('./svg/thumb-bar-checked.svg'); +$thumb-img: url("./svg/thumb.svg") !default; +$thumb-checked-img: url("./svg/thumb-checked.svg") !default; +$thumb-bar-img: url("./svg/thumb-bar.svg") !default; +$thumb-bar-checked-img: url("./svg/thumb-bar-checked.svg") !default; -$background-color: #e6e7e8; +$background-color: #e6e7e8 !default; -$error-height: 14px; +$error-height: 14px !default; .tb-switch { - width:100%; - height:100%; + width: 100%; + height: 100%; background: $background-color; .error-container { - position:absolute; + position: absolute; top: 1%; - left: 0; right: 0; - z-index:4; + left: 0; + z-index: 4; height: $error-height; + .switch-error { color: #ff3315; white-space: nowrap; @@ -42,62 +43,71 @@ $error-height: 14px; .onoff-container { height: 100%; - color: #757575; font-weight: 500; + color: #757575; white-space: nowrap; + .off-label { color: #b7b5b5; } + .on-label { color: #ff7e57; text-shadow: #ff6e4a 1px 1px 10px, #ffd1c3 1px 1px 10px; } } + .title-container { .switch-title { - color: #757575; font-weight: 500; + color: #757575; white-space: nowrap; } } #switch-container { - padding-left: 10px; - padding-right: 10px; + padding-right: 10px; + padding-left: 10px; } + .switch { position: relative; + md-switch { - margin: 0; - position:absolute; + position: absolute; top: 0; - left: 0; - bottom: 0; right: 0; + bottom: 0; + left: 0; + margin: 0; + .md-container { margin: 0; } + .md-bar { + top: 0; left: 0; width: 100%; - top: 0; height: 100%; - border-radius: 0; - background:$thumb-bar-img no-repeat; + background: $thumb-bar-img no-repeat; background-size: contain; + border-radius: 0; } + .md-thumb-container { - left: 0.25%; - width: 50%; top: 5%; + left: .25%; + width: 50%; height: 90%; } + .md-thumb { top: 0; left: 0; - height: 100%; width: 100%; - background:$thumb-img no-repeat; + height: 100%; + background: $thumb-img no-repeat; background-size: contain; border-radius: 0; box-shadow: none; @@ -105,22 +115,23 @@ $error-height: 14px; &.md-checked { .md-bar { - background:$thumb-bar-checked-img no-repeat; + background: $thumb-bar-checked-img no-repeat; background-size: contain; } + .md-thumb { - background:$thumb-checked-img no-repeat; + background: $thumb-checked-img no-repeat; background-size: contain; } } - } } + #text-measure { position: absolute; - visibility: hidden; - height: auto; width: auto; + height: auto; white-space: nowrap; + visibility: hidden; } } diff --git a/ui/src/app/widget/lib/timeseries-table-widget.scss b/ui/src/app/widget/lib/timeseries-table-widget.scss index cd8f8a9dad..12fbf614f7 100644 --- a/ui/src/app/widget/lib/timeseries-table-widget.scss +++ b/ui/src/app/widget/lib/timeseries-table-widget.scss @@ -13,20 +13,22 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + tb-timeseries-table-widget { - table.md-table thead.md-head>tr.md-row { - height: 40px; - } + table.md-table thead.md-head > tr.md-row { + height: 40px; + } - table.md-table tbody.md-body>tr.md-row, table.md-table tfoot.md-foot>tr.md-row { - height: 38px; - } + table.md-table tbody.md-body > tr.md-row, + table.md-table tfoot.md-foot > tr.md-row { + height: 38px; + } - .md-table-pagination>* { - height: 46px; - } + .md-table-pagination > * { + height: 46px; + } - .tb-data-table md-toolbar { - z-index: 10; - } + .tb-data-table md-toolbar { + z-index: 10; + } } diff --git a/ui/src/app/widget/widget-editor.scss b/ui/src/app/widget/widget-editor.scss index 077f68c118..cd374e37aa 100644 --- a/ui/src/app/widget/widget-editor.scss +++ b/ui/src/app/widget/widget-editor.scss @@ -15,13 +15,13 @@ */ @import "~compass-sass-mixins/lib/compass"; -$edit-toolbar-height: 40px; +$edit-toolbar-height: 40px !default; .tb-editor { .tb-split { @include box-sizing(border-box); - overflow-y: auto; overflow-x: hidden; + overflow-y: auto; } .ace_editor { @@ -29,7 +29,7 @@ $edit-toolbar-height: 40px; } .tb-content { - border: 1px solid #C0C0C0; + border: 1px solid #c0c0c0; } .gutter { @@ -41,21 +41,23 @@ $edit-toolbar-height: 40px; .gutter.gutter-horizontal { cursor: col-resize; - background-image: url('../../../node_modules/split.js/grips/vertical.png'); + background-image: url("../../../node_modules/split.js/grips/vertical.png"); } .gutter.gutter-vertical { cursor: row-resize; - background-image: url('../../../node_modules/split.js/grips/horizontal.png'); + background-image: url("../../../node_modules/split.js/grips/horizontal.png"); } - .tb-split.tb-split-horizontal, .gutter.gutter-horizontal { - height: 100%; + .tb-split.tb-split-horizontal, + .gutter.gutter-horizontal { float: left; + height: 100%; } .tb-split.tb-split-vertical { display: flex; + .tb-split.tb-content { height: 100%; } @@ -64,7 +66,6 @@ $edit-toolbar-height: 40px; .tb-split-vertical { md-tabs { - md-tabs-content-wrapper { height: calc(100% - 49px); @@ -74,45 +75,44 @@ $edit-toolbar-height: 40px; & > div { height: 100%; } - } - } - } } div.tb-editor-area-title-panel { position: absolute; - font-size: 0.800rem; - font-weight: 500; top: 5px; right: 20px; z-index: 5; + font-size: .8rem; + font-weight: 500; + label { - color: #00acc1; - background: rgba(220, 220, 220, 0.35); - border-radius: 5px; padding: 4px; + color: #00acc1; text-transform: uppercase; + background: rgba(220, 220, 220, .35); + border-radius: 5px; } + .md-button { - color: #7B7B7B; min-width: 32px; min-height: 15px; - line-height: 15px; - font-size: 0.800rem; - margin: 0; padding: 4px; - background: rgba(220, 220, 220, 0.35); + margin: 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); } } .tb-resize-container { - overflow-y: auto; - height: 100%; - width: 100%; position: relative; + width: 100%; + height: 100%; + overflow-y: auto; .ace_editor { height: 100%; @@ -127,24 +127,28 @@ md-toolbar.tb-edit-toolbar { .md-toolbar-tools { min-height: $edit-toolbar-height !important; max-height: $edit-toolbar-height !important; + .md-button { min-width: 65px; min-height: 30px; - line-height: 30px; font-size: 12px; + line-height: 30px; + md-icon { font-size: 20px; } + span { padding-right: 6px; } } + md-input-container { input { - font-size: 1.200rem; - font-weight: 400; - letter-spacing: 0.005em; height: 28px; + font-size: 1.2rem; + font-weight: 400; + letter-spacing: .005em; } } } diff --git a/ui/src/scss/animations.scss b/ui/src/scss/animations.scss index 719b6f940d..4ecfc34743 100644 --- a/ui/src/scss/animations.scss +++ b/ui/src/scss/animations.scss @@ -18,6 +18,7 @@ @include keyframes(tbMoveFromTopFade) { from { opacity: 0; + @include transform(translate(0, -100%)); } } @@ -25,6 +26,7 @@ @include keyframes(tbMoveToTopFade) { to { opacity: 0; + @include transform(translate(0, -100%)); } } @@ -32,6 +34,7 @@ @include keyframes(tbMoveFromBottomFade) { from { opacity: 0; + @include transform(translate(0, 100%)); } } @@ -39,6 +42,7 @@ @include keyframes(tbMoveToBottomFade) { to { opacity: 0; + @include transform(translate(0, 150%)); } -} \ No newline at end of file +} diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss index 5b6eab434f..b462c66d97 100644 --- a/ui/src/scss/constants.scss +++ b/ui/src/scss/constants.scss @@ -17,18 +17,18 @@ // Colors -$gray: #eee; +$gray: #eee !default; -$primary-palette-color: 'indigo'; -$default: '500'; -$hue-1: '300'; -$hue-2: '800'; -$hue-3: 'a100'; +$primary-palette-color: "indigo" !default; +$default: "500" !default; +$hue-1: "300" !default; +$hue-2: "800" !default; +$hue-3: "a100" !default; -$primary-default: #305680; //material-color($primary-palette-color, $default); -$primary-hue-1: material-color($primary-palette-color, $hue-1); -$primary-hue-2: material-color($primary-palette-color, $hue-2); -$primary-hue-3: rgb(207, 216, 220); +$primary-default: #305680 !default; //material-color($primary-palette-color, $default); +$primary-hue-1: material-color($primary-palette-color, $hue-1) !default; +$primary-hue-2: material-color($primary-palette-color, $hue-2) !default; +$primary-hue-3: rgb(207, 216, 220) !default; // Layout diff --git a/ui/src/scss/fonts.scss b/ui/src/scss/fonts.scss index 95089eb3f1..6348d3531a 100644 --- a/ui/src/scss/fonts.scss +++ b/ui/src/scss/fonts.scss @@ -14,8 +14,8 @@ * limitations under the License. */ @font-face { - font-family: 'Segment7Standard'; - src: url('data:font/opentype;charset=utf-8;base64,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AAEAAAAIAAAAAAAEAAAAAAAAAAEAAAAAzD2izwAAAADPr89TAAAAAM+4fiECSQAAAkkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpwBCAC8ALwAvAFwBpwAvAC8ALwBcAC8AXAIBAC8ALwGWAC8ALwBCAC4ALgBYAC8ALwBcAacALwAvAC8ALwAvAC8ALwAvAC8ALwAvAC8ALwGWAC8ALwAvAC8ALwAvAC8AQgAvAC4ALwAvAC8ALwAvAC8ALwAvAEIALwBCAFwBpwAvAC8ALwAvAC8ALwAvAC8BlgAvAC8ALwAvAC8ALwAvAEIALwAuAC8ALwAvAC8ALwAvAC8AAAGWAAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8ALwAAAAAAAAAAAAAAAAAAAC8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA') format('opentype'); - font-weight: normal; + font-family: "Segment7Standard"; font-style: italic; -} \ No newline at end of file + font-weight: 400; + src: url("data:font/opentype;charset=utf-8;base64,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AAEAAAAIAAAAAAAEAAAAAAAAAAEAAAAAzD2izwAAAADPr89TAAAAAM+4fiECSQAAAkkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABpwBCAC8ALwAvAFwBpwAvAC8ALwBcAC8AXAIBAC8ALwGWAC8ALwBCAC4ALgBYAC8ALwBcAacALwAvAC8ALwAvAC8ALwAvAC8ALwAvAC8ALwGWAC8ALwAvAC8ALwAvAC8AQgAvAC4ALwAvAC8ALwAvAC8ALwAvAEIALwBCAFwBpwAvAC8ALwAvAC8ALwAvAC8BlgAvAC8ALwAvAC8ALwAvAEIALwAuAC8ALwAvAC8ALwAvAC8AAAGWAAAALwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAEIAAAAAAAAAAAAvAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC8ALwAAAAAAAAAAAAAAAAAAAC8AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA") format("opentype"); +} diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss index 2852a7bd60..27c2d3b245 100644 --- a/ui/src/scss/main.scss +++ b/ui/src/scss/main.scss @@ -14,77 +14,92 @@ * limitations under the License. */ @import "~compass-sass-mixins/lib/compass"; + @import "constants"; + @import "animations"; + @import "mixins"; + @import "fonts"; /*************** * TYPE DEFAULTS ***************/ -button, html, input, select, textarea { - font-family: Roboto, 'Helvetica Neue', sans-serif; +button, +html, +input, +select, +textarea { + font-family: Roboto, "Helvetica Neue", sans-serif; } .mdi-set { line-height: 1; - letter-spacing: normal; text-transform: none; - white-space: nowrap; + letter-spacing: normal; word-wrap: normal; + white-space: nowrap; direction: ltr; - -webkit-font-feature-settings: 'liga'; + -webkit-font-feature-settings: "liga"; } a { - color: #106CC8; - text-decoration: none; font-weight: 400; - border-bottom: 1px solid rgba(64, 84, 178, 0.25); - @include transition(border-bottom 0.35s); + color: #106cc8; + text-decoration: none; + border-bottom: 1px solid rgba(64, 84, 178, .25); + + @include transition(border-bottom .35s); } -a:hover, a:focus { - border-bottom: 1px solid #4054B2; +a:hover, +a:focus { + border-bottom: 1px solid #4054b2; } -h1, h2, h3, h4, h5, h6 { - margin-bottom: 1rem; +h1, +h2, +h3, +h4, +h5, +h6 { margin-top: 1rem; + margin-bottom: 1rem; } h1 { - font-size: 3.400rem; + font-size: 3.4rem; font-weight: 400; line-height: 4rem; } h2 { - font-size: 2.400rem; + font-size: 2.4rem; font-weight: 400; line-height: 3.2rem; } h3 { - font-size: 2.000rem; + font-size: 2rem; font-weight: 500; - letter-spacing: 0.005em; + letter-spacing: .005em; } h4 { - font-size: 1.600rem; + font-size: 1.6rem; font-weight: 400; - letter-spacing: 0.010em; line-height: 2.4rem; + letter-spacing: .01em; } p { + margin: .8em 0 1.6em; font-size: 1.6rem; font-weight: 400; - letter-spacing: 0.010em; line-height: 1.6em; - margin: 0.8em 0 1.6em; + letter-spacing: .01em; } strong { @@ -92,16 +107,16 @@ strong { } blockquote { - border-left: 3px solid rgba(0, 0, 0, 0.12); - font-style: italic; - margin-left: 0; padding-left: 16px; + margin-left: 0; + font-style: italic; + border-left: 3px solid rgba(0, 0, 0, .12); } fieldset { - border: none; padding: 0; margin: 0; + border: none; } /********************************* @@ -119,26 +134,28 @@ form { } md-bottom-sheet .md-subheader { - font-family: Roboto, 'Helvetica Neue', sans-serif; + font-family: Roboto, "Helvetica Neue", sans-serif; } .md-chips { - font-family: Roboto, 'Helvetica Neue', sans-serif; + font-family: Roboto, "Helvetica Neue", sans-serif; } -md-content.md-default-theme, md-content { +md-content.md-default-theme, +md-content { background-color: $gray; } md-card { background-color: #fff; + h2:first-of-type { margin-top: 0; } } .md-button:not([disabled]).md-icon-button:hover { - background-color: rgba(158, 158, 158, 0.2); + background-color: rgba(158, 158, 158, .2); } md-toolbar:not(.md-hue-1), @@ -147,11 +164,12 @@ md-toolbar:not(.md-hue-1), } md-toolbar md-input-container .md-errors-spacer { - min-height: 0px; + min-height: 0; } md-toolbar { - md-select.md-default-theme:not([disabled]):focus .md-select-value, md-select:not([disabled]):focus .md-select-value { + md-select.md-default-theme:not([disabled]):focus .md-select-value, + md-select:not([disabled]):focus .md-select-value { color: #fff; } } @@ -159,11 +177,10 @@ md-toolbar { md-menu-item { overflow: hidden; fill: #737373; -} -md-menu-item { .md-button { display: block; + .tb-alt-text { float: right; } @@ -190,13 +207,14 @@ md-sidenav { overflow-y: auto; } -.md-radio-interactive input, button { +.md-radio-interactive input, +button { pointer-events: all; } .md-color-picker-input-container { md-input-container { - margin-bottom: 0px; + margin-bottom: 0; } } @@ -204,32 +222,36 @@ md-sidenav { * THINGSBOARD SPECIFIC ***********************/ -$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; label { &.tb-title { - pointer-events: none; - color: #666; + padding-bottom: 15px; font-size: 13px; font-weight: 400; - padding-bottom: 15px; + color: #666; + pointer-events: none; + &.no-padding { - padding-bottom: 0px; + padding-bottom: 0; } - &.tb-required:after { - content: ' *'; + + &.tb-required::after { font-size: 13px; + color: rgba(0, 0, 0, .54); vertical-align: top; - color: rgba(0,0,0,0.54); + content: " *"; } + &.tb-error { - color: rgb(221,44,0); - &.tb-required:after { - color: rgb(221,44,0); + color: rgb(221, 44, 0); + + &.tb-required::after { + color: rgb(221, 44, 0); } } } @@ -246,96 +268,107 @@ label { } .tb-readonly-label { - color: rgba(0,0,0,0.54); + color: rgba(0, 0, 0, .54); } .tb-disabled-label { - color: rgba(0,0,0,0.44); + color: rgba(0, 0, 0, .44); } +/* stylelint-disable-next-line no-duplicate-selectors */ label { &.tb-small { - pointer-events: none; - color: rgba(0,0,0,0.54); font-size: 12px; + color: rgba(0, 0, 0, .54); + pointer-events: none; } } div { &.tb-small { - color: rgba(0,0,0,0.54); font-size: 14px; + color: rgba(0, 0, 0, .54); } } .tb-hint { + padding-bottom: 15px; font-size: 12px; line-height: 14px; - transition: all 0.3s cubic-bezier(0.55, 0, 0.55, 0.2); - color: grey; - padding-bottom: 15px; - &.ng-hide, &.ng-enter, &.ng-leave.ng-leave-active { + color: #808080; + transition: all .3s cubic-bezier(.55, 0, .55, .2); + + &.ng-hide, + &.ng-enter, + &.ng-leave.ng-leave-active { bottom: 26px; opacity: 0; } - &.ng-leave, &.ng-enter.ng-enter-active { + + &.ng-leave, + &.ng-enter.ng-enter-active { bottom: 7px; opacity: 1; } } .md-caption { - &.tb-required:after { - content: ' *'; + &.tb-required::after { font-size: 10px; + color: rgba(0, 0, 0, .54); vertical-align: top; - color: rgba(0,0,0,0.54); + content: " *"; } } pre.tb-highlight { - background-color: #f7f7f7; display: block; - margin: 20px 0; padding: 15px; + margin: 20px 0; overflow-x: auto; + background-color: #f7f7f7; + code { + box-sizing: border-box; + display: inline-block; padding: 0; - color: #303030; font-family: monospace; - display: inline-block; - box-sizing: border-box; - vertical-align: bottom; font-size: 16px; - font-weight: bold; + font-weight: 700; + color: #303030; + vertical-align: bottom; } } .tb-notice { - background-color: #f7f7f7; padding: 15px; - border: 1px solid #ccc; font-size: 16px; + background-color: #f7f7f7; + border: 1px solid #ccc; } .tb-data-table { md-toolbar { z-index: 0; } + md-toolbar.md-table-toolbar.md-default-theme:not(.md-menu-toolbar).md-default .md-button[disabled], md-toolbar.md-table-toolbar:not(.md-menu-toolbar).md-default .md-button[disabled] { - color: rgba(0,0,0,0.38); + color: rgba(0, 0, 0, .38); } + md-toolbar.md-default-theme:not(.md-menu-toolbar) .md-button[disabled] md-icon, md-toolbar:not(.md-menu-toolbar) .md-button[disabled] md-icon { - color: rgba(0,0,0,.28); + color: rgba(0, 0, 0, .28); } + span.no-data-found { position: relative; + display: flex; height: calc(100% - 57px); text-transform: uppercase; - display: flex; } + table.md-table { &.md-row-select td.md-cell, &.md-row-select th.md-column { @@ -366,49 +399,55 @@ pre.tb-highlight { td.md-cell, th.md-column { - &:last-child { padding: 0 12px 0 0; } - } } - table.md-table, table.md-table.md-row-select { + table.md-table, + table.md-table.md-row-select { tbody { &.md-body { tr { &.md-row:not([disabled]) { outline: none; + &:hover { - background-color: rgba(221, 221, 221, 0.3) !important; + background-color: rgba(221, 221, 221, .3) !important; } + &.md-selected { - background-color: rgba(221, 221, 221, 0.5) !important; + background-color: rgba(221, 221, 221, .5) !important; } - &.tb-current, &.tb-current:hover{ - background-color: rgba(221, 221, 221, 0.65) !important; + + &.tb-current, + &.tb-current:hover{ + background-color: rgba(221, 221, 221, .65) !important; } } } } + tr { td { &.tb-action-cell { + width: 72px; + min-width: 72px; + max-width: 72px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; - min-width: 72px; - max-width: 72px; - width: 72px; + .md-button { &.md-icon-button { - margin: 0; - padding: 6px; width: 36px; height: 36px; + padding: 6px; + margin: 0; } } + .tb-spacer { padding-left: 38px; } @@ -420,34 +459,40 @@ pre.tb-highlight { } .tb-severity { - font-weight: bold; + font-weight: 700; + &.tb-critical { - color: red !important; + color: #f00 !important; } + &.tb-major { - color: orange !important; + color: #ffa500 !important; } + &.tb-minor { color: #ffca3d !important; } + &.tb-warning { color: #abab00 !important; } + &.tb-indeterminate { - color: green !important; + color: #008000 !important; } } .tb-card-description { - color: rgba(0,0,0,0.54); font-size: 13px; + color: rgba(0, 0, 0, .54); + @include line-clamp(2, 1.1); } /*********************** * Flow ***********************/ -$previewSize: 100px; +$previewSize: 100px !default; .file-input { display: none; @@ -455,17 +500,19 @@ $previewSize: 100px; .tb-flow-drop { position: relative; - border: dashed 2px; height: $previewSize; overflow: hidden; + border: dashed 2px; + label { - width: 100%; - height: 100%; display: flex; flex-direction: column; justify-content: center; + width: 100%; + height: 100%; font-size: 16px; text-align: center; + @media (min-width: $layout-breakpoint-sm) { font-size: 24px; } @@ -474,8 +521,8 @@ $previewSize: 100px; .tb-container { position: relative; - margin-top: 32px; padding: 10px 0; + margin-top: 32px; } /*********************** @@ -483,12 +530,12 @@ $previewSize: 100px; ***********************/ .tb-prompt { - color: rgba(0,0,0,0.38); - text-transform: uppercase; display: flex; font-size: 18px; font-weight: 400; line-height: 18px; + color: rgba(0, 0, 0, .38); + text-transform: uppercase; } /*********************** @@ -500,35 +547,37 @@ $previewSize: 100px; } .tb-error-message { + padding: 10px 0 0 10px; + margin-top: -6px; + overflow: hidden; font-size: 12px; line-height: 14px; - overflow: hidden; - padding: 10px 0px 0px 10px; - color: rgb(221,44,0); - margin-top: -6px; + color: rgb(221, 44, 0); } .tb-error-message.ng-animate { - @include transition(all .3s cubic-bezier(.55,0,.55,.2)); + @include transition(all .3s cubic-bezier(.55, 0, .55, .2)); } -.tb-error-message.ng-enter-prepare, .tb-error-message.ng-enter { - opacity:0; +.tb-error-message.ng-enter-prepare, +.tb-error-message.ng-enter { margin-top: -24px; + opacity: 0; } .tb-error-message.ng-enter.ng-enter-active { - opacity:1; margin-top: -6px; + opacity: 1; } .tb-error-message.ng-leave { - opacity:1; margin-top: -6px; + opacity: 1; } + .tb-error-message.ng-leave.ng-leave-active { - opacity:0; margin-top: -24px; + opacity: 0; } /*********************** @@ -545,39 +594,41 @@ md-tabs.tb-headless { .md-button.tb-card-button { width: 100%; - height: 100%; max-width: 240px; + height: 100%; + span { + height: 18px; + min-height: 18px; + max-height: 18px; padding: 0 0 20px 0; + margin: auto; font-size: 18px; font-weight: 400; - white-space: normal; line-height: 18px; - max-height: 18px; - min-height: 18px; - height: 18px; - margin: auto; + white-space: normal; } } .md-button.tb-layout-button { width: 100%; - height: 100%; max-width: 240px; + height: 100%; + span { padding: 40px; font-size: 18px; font-weight: 400; - white-space: normal; line-height: 18px; + white-space: normal; } } .md-button.tb-add-new-widget { + padding-right: 12px; + font-size: 24px; border-style: dashed; border-width: 2px; - font-size: 24px; - padding-right: 12px; } /*********************** @@ -585,11 +636,12 @@ md-tabs.tb-headless { ***********************/ section.tb-header-buttons { - pointer-events: none; position: absolute; - right: 0px; top: 86px; + right: 0; z-index: 3; + pointer-events: none; + @media (min-width: $layout-breakpoint-sm) { top: 86px; } @@ -633,6 +685,7 @@ section.tb-footer-buttons { ._md-toast-open-bottom .tb-footer-buttons { @include transition(all .4s cubic-bezier(.25, .8, .25, 1)); + @include transform(translate3d(0, -42px, 0)); } @@ -641,27 +694,27 @@ section.tb-footer-buttons { ***********************/ .md-icon-button.tb-md-32 { - vertical-align: middle; width: 32px; - height: 32px; min-width: 32px; + height: 32px; min-height: 32px; - margin: 0px !important; - padding: 0px !important; + padding: 0 !important; + margin: 0 !important; + vertical-align: middle; } .material-icons.tb-md-20 { - font-size: 20px; width: 20px; - height: 20px; min-width: 20px; + height: 20px; min-height: 20px; + font-size: 20px; } .material-icons.tb-md-96 { - font-size: 96px; width: 96px; height: 96px; + font-size: 96px; } /*********************** @@ -671,17 +724,17 @@ section.tb-footer-buttons { .tb-absolute-fill { position: absolute; top: 0; - left: 0; right: 0; bottom: 0; + left: 0; } .tb-progress-cover { position: absolute; top: 0; - left: 0; right: 0; bottom: 0; + left: 0; z-index: 6; opacity: 1; } diff --git a/ui/src/scss/mixins.scss b/ui/src/scss/mixins.scss index cb661719f2..a7545386aa 100644 --- a/ui/src/scss/mixins.scss +++ b/ui/src/scss/mixins.scss @@ -17,43 +17,49 @@ @mixin input-placeholder { // replaces compass/css/user-interface/input-placeholder() + &::-webkit-input-placeholder { @content; } + &:-moz-placeholder { @content; opacity: 1; } + &::-moz-placeholder { @content; opacity: 1; } + &:-ms-input-placeholder { @content; } } @mixin line-clamp($numLines: 1, $lineHeight: 1.412) { - overflow: hidden; position: relative; + max-height: ($numLines * $lineHeight * 1em); + padding-right: 2em; + margin-right: -1em; + overflow: hidden; line-height: $lineHeight; text-align: justify; - margin-right: -1em; - padding-right: 2em; - max-height: ($numLines*$lineHeight)+em; - &:before { - content: '...'; + + &::before { position: absolute; right: 1em; bottom: 0; + content: "..."; } - &:after { - content: ''; + + &::after { position: absolute; right: 1em; width: 1em; height: 1em; - margin-top: 0.2em; - background: white; + margin-top: .2em; + content: ""; + background: #fff; } } diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js index 242ad9d7aa..cfff811b23 100644 --- a/ui/webpack.config.dev.js +++ b/ui/webpack.config.dev.js @@ -18,6 +18,8 @@ const HtmlWebpackPlugin = require('html-webpack-plugin'); const ExtractTextPlugin = require('extract-text-webpack-plugin'); const CopyWebpackPlugin = require('copy-webpack-plugin'); +const StyleLintPlugin = require('stylelint-webpack-plugin') + const webpack = require('webpack'); const path = require('path'); const dirTree = require('directory-tree'); @@ -76,6 +78,7 @@ module.exports = { title: 'ThingsBoard', inject: 'body', }), + new StyleLintPlugin(), new webpack.optimize.OccurrenceOrderPlugin(), new webpack.NoErrorsPlugin(), new ExtractTextPlugin('style.[contentHash].css', {