thingsboard-aplcache
Changes
application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java 32(+30 -2)
application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsSandboxService.java 207(+154 -53)
application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java 15(+9 -6)
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java 23(+19 -4)
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetryWebSocketService.java 8(+4 -4)
application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java 95(+87 -8)
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java 292(+253 -39)
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java 36(+29 -7)
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java 5(+5 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java 169(+169 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeConfiguration.java 57(+57 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java 8(+7 -1)
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js 6(+3 -3)
transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java 6(+6 -0)
ui/.stylelintrc 292(+292 -0)
ui/package.json 12(+9 -3)
ui/src/app/alarm/alarm.scss 23(+12 -11)
ui/src/app/api/subscription.js 8(+8 -0)
ui/src/app/audit/audit-log.scss 27(+15 -12)
ui/src/app/components/dashboard.scss 36(+20 -16)
ui/src/app/components/dashboard-select.scss 30(+18 -12)
ui/src/app/components/datasource.scss 41(+23 -18)
ui/src/app/components/datasource-entity.scss 16(+11 -5)
ui/src/app/components/details-sidenav.scss 34(+19 -15)
ui/src/app/components/expand-fullscreen.scss 29(+15 -14)
ui/src/app/components/grid.scss 13(+9 -4)
ui/src/app/components/js-func.scss 23(+14 -9)
ui/src/app/components/json-content.scss 20(+12 -8)
ui/src/app/components/json-form.scss 5(+3 -2)
ui/src/app/components/kv-map.scss 8(+5 -3)
ui/src/app/components/legend.scss 26(+18 -8)
ui/src/app/components/legend-config.scss 22(+14 -8)
ui/src/app/components/material-icon-select.scss 22(+12 -10)
ui/src/app/components/menu-link.scss 6(+4 -2)
ui/src/app/components/react/json-form.scss 121(+63 -58)
ui/src/app/components/react/json-form-image.scss 91(+47 -44)
ui/src/app/components/side-menu.scss 39(+21 -18)
ui/src/app/components/timeinterval.scss 11(+9 -2)
ui/src/app/components/timewindow.scss 37(+25 -12)
ui/src/app/components/widget/widget.scss 11(+7 -4)
ui/src/app/dashboard/dashboard.scss 79(+46 -33)
ui/src/app/dashboard/dashboard-settings.scss 26(+15 -11)
ui/src/app/dashboard/dashboard-toolbar.scss 81(+56 -25)
ui/src/app/entity/alias/entity-aliases.scss 18(+13 -5)
ui/src/app/entity/attribute/attribute-table.scss 30(+16 -14)
ui/src/app/entity/entity-filter.scss 8(+5 -3)
ui/src/app/entity/entity-list.scss 7(+5 -2)
ui/src/app/entity/entity-select.scss 6(+4 -2)
ui/src/app/entity/relation/relation-filters.scss 29(+18 -11)
ui/src/app/event/event.scss 75(+40 -35)
ui/src/app/extension/extension-table.scss 21(+13 -8)
ui/src/app/help/help.scss 8(+6 -2)
ui/src/app/home/home-links.scss 13(+7 -6)
ui/src/app/jsonform/jsonform.scss 6(+4 -2)
ui/src/app/layout/home.scss 29(+19 -10)
ui/src/app/layout/user-menu.scss 20(+12 -8)
ui/src/app/login/login.scss 5(+4 -1)
ui/src/app/rulechain/link.scss 4(+3 -1)
ui/src/app/rulechain/rulechain.scss 272(+166 -106)
ui/src/app/rulechain/rulenode.scss 6(+3 -3)
ui/src/app/services/toast.scss 13(+7 -6)
ui/src/app/user/user-fieldset.scss 6(+4 -2)
ui/src/app/widget/lib/alarms-table-widget.js 190(+176 -14)
ui/src/app/widget/lib/rpc/knob.scss 141(+78 -63)
ui/src/app/widget/lib/rpc/led-indicator.scss 48(+26 -22)
ui/src/app/widget/lib/rpc/round-switch.scss 214(+118 -96)
ui/src/app/widget/lib/rpc/switch.scss 73(+42 -31)
ui/src/app/widget/widget-editor.scss 60(+32 -28)
ui/src/scss/animations.scss 6(+5 -1)
ui/src/scss/constants.scss 20(+10 -10)
ui/src/scss/fonts.scss 8(+4 -4)
ui/src/scss/main.scss 309(+181 -128)
ui/src/scss/mixins.scss 26(+16 -10)
ui/webpack.config.dev.js 3(+3 -0)
Details
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 8e7a9ae..6a78f78 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/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
index 7279347..d5260da 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
@@ -154,7 +154,7 @@ class DefaultTbContext implements TbContext {
@Override
public ScriptEngine createJsScriptEngine(String script, String... argNames) {
- return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), script, argNames);
+ return new RuleNodeJsScriptEngine(mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames);
}
@Override
diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
index 586e8c3..00885e3 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -83,7 +83,7 @@ public class AlarmController extends BaseController {
Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
logEntityAction(savedAlarm.getId(), savedAlarm,
getCurrentUser().getCustomerId(),
- savedAlarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
+ alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
return savedAlarm;
} catch (Exception e) {
logEntityAction(emptyId(EntityType.ALARM), alarm,
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 a6daeec..24608cb 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/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
index 86b8fda..82ab170 100644
--- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java
@@ -276,7 +276,7 @@ public class RuleChainController extends BaseController {
String errorText = "";
ScriptEngine engine = null;
try {
- engine = new RuleNodeJsScriptEngine(jsSandboxService, script, argNames);
+ engine = new RuleNodeJsScriptEngine(jsSandboxService, getCurrentUser().getId(), script, argNames);
TbMsg inMsg = new TbMsg(UUIDs.timeBased(), msgType, null, new TbMsgMetaData(metadata), data, null, null, 0L);
switch (scriptType) {
case "update":
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 10a16ec..7062ca9 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<TsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, interval, limit, agg))
+ List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
.collect(Collectors.toList());
Futures.addCallback(tsService.findAll(entityId, queries), getTsKvListCallback(result));
diff --git a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
index c3ffbab..6f51cee 100644
--- a/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
+++ b/application/src/main/java/org/thingsboard/server/service/cluster/discovery/ZkDiscoveryService.java
@@ -24,6 +24,8 @@ import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
+import org.apache.curator.framework.state.ConnectionState;
+import org.apache.curator.framework.state.ConnectionStateListener;
import org.apache.curator.retry.RetryForever;
import org.apache.curator.utils.CloseableUtils;
import org.apache.zookeeper.CreateMode;
@@ -127,12 +129,38 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
.creatingParentsIfNeeded()
.withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(zkNodesDir + "/", SerializationUtils.serialize(self.getServerAddress()));
log.info("[{}:{}] Created ZK node for current instance: {}", self.getHost(), self.getPort(), nodePath);
+ client.getConnectionStateListenable().addListener(checkReconnect(self));
} catch (Exception e) {
log.error("Failed to create ZK node", e);
throw new RuntimeException(e);
}
}
+ private ConnectionStateListener checkReconnect(ServerInstance self) {
+ return (client, newState) -> {
+ log.info("[{}:{}] ZK state changed: {}", self.getHost(), self.getPort(), newState);
+ if (newState == ConnectionState.LOST) {
+ reconnect();
+ }
+ };
+ }
+
+ private boolean reconnectInProgress = false;
+
+ private synchronized void reconnect() {
+ if (!reconnectInProgress) {
+ reconnectInProgress = true;
+ try {
+ client.blockUntilConnected();
+ publishCurrentServer();
+ } catch (InterruptedException e) {
+ log.error("Failed to reconnect to ZK: {}", e.getMessage(), e);
+ } finally {
+ reconnectInProgress = false;
+ }
+ }
+ }
+
@Override
public void unpublishCurrentServer() {
try {
@@ -156,7 +184,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
.filter(cd -> !cd.getPath().equals(nodePath))
.map(cd -> {
try {
- return new ServerInstance( (ServerAddress) SerializationUtils.deserialize(cd.getData()));
+ return new ServerInstance((ServerAddress) SerializationUtils.deserialize(cd.getData()));
} catch (NoSuchElementException e) {
log.error("Failed to decode ZK node", e);
throw new RuntimeException(e);
@@ -198,7 +226,7 @@ public class ZkDiscoveryService implements DiscoveryService, PathChildrenCacheLi
}
ServerInstance instance;
try {
- ServerAddress serverAddress = SerializationUtils.deserialize(data.getData());
+ ServerAddress serverAddress = SerializationUtils.deserialize(data.getData());
instance = new ServerInstance(serverAddress);
} catch (SerializationException e) {
log.error("Failed to decode server instance for node {}", data.getPath(), e);
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 7f274ec..1d89c9d 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
@@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-
package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.Futures;
@@ -21,7 +20,12 @@ import com.google.common.util.concurrent.ListenableFuture;
import delight.nashornsandbox.NashornSandbox;
import delight.nashornsandbox.NashornSandboxes;
import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.server.common.data.id.EntityId;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
@@ -42,9 +46,11 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
private ScriptEngine engine;
private ExecutorService monitorExecutorService;
- private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
+ private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
+ private final Map<BlackListKey, BlackListInfo> blackListedFunctions = new ConcurrentHashMap<>();
- private Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
+ private final Map<String, ScriptInfo> scriptKeyToInfo = new ConcurrentHashMap<>();
+ private final Map<UUID, ScriptInfo> scriptIdToInfo = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
@@ -63,7 +69,7 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
@PreDestroy
public void stop() {
- if (monitorExecutorService != null) {
+ if (monitorExecutorService != null) {
monitorExecutorService.shutdownNow();
}
}
@@ -78,75 +84,107 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
@Override
public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
- UUID scriptId = UUID.randomUUID();
- String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
- String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
- try {
- if (useJsSandbox()) {
- sandbox.eval(jsScript);
+ ScriptInfo scriptInfo = deduplicate(scriptType, scriptBody);
+ UUID scriptId = scriptInfo.getId();
+ AtomicInteger duplicateCount = scriptInfo.getCount();
+
+ synchronized (scriptInfo.getLock()) {
+ if (duplicateCount.compareAndSet(0, 1)) {
+ try {
+ evaluate(scriptId, scriptType, scriptBody, argNames);
+ } catch (Exception e) {
+ duplicateCount.decrementAndGet();
+ log.warn("Failed to compile JS script: {}", e.getMessage(), e);
+ return Futures.immediateFailedFuture(e);
+ }
} else {
- engine.eval(jsScript);
+ duplicateCount.incrementAndGet();
}
- functionsMap.put(scriptId, functionName);
- } catch (Exception e) {
- log.warn("Failed to compile JS script: {}", e.getMessage(), e);
- return Futures.immediateFailedFuture(e);
}
return Futures.immediateFuture(scriptId);
}
+ private void evaluate(UUID scriptId, JsScriptType scriptType, String scriptBody, String... argNames) throws ScriptException {
+ String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
+ String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
+ if (useJsSandbox()) {
+ sandbox.eval(jsScript);
+ } else {
+ engine.eval(jsScript);
+ }
+ functionsMap.put(scriptId, functionName);
+ }
+
@Override
- public ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args) {
+ public ListenableFuture<Object> invokeFunction(UUID scriptId, EntityId entityId, Object... args) {
String functionName = functionsMap.get(scriptId);
if (functionName == null) {
- return Futures.immediateFailedFuture(new RuntimeException("No compiled script found for scriptId: [" + scriptId + "]!"));
- }
- if (!isBlackListed(scriptId)) {
- try {
- Object result;
- if (useJsSandbox()) {
- result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
- } else {
- result = ((Invocable)engine).invokeFunction(functionName, args);
- }
- return Futures.immediateFuture(result);
- } catch (Exception e) {
- blackListedFunctions.computeIfAbsent(scriptId, key -> new AtomicInteger(0)).incrementAndGet();
- return Futures.immediateFailedFuture(e);
- }
+ String message = "No compiled script found for scriptId: [" + scriptId + "]!";
+ log.warn(message);
+ return Futures.immediateFailedFuture(new RuntimeException(message));
+ }
+
+ BlackListInfo blackListInfo = blackListedFunctions.get(new BlackListKey(scriptId, entityId));
+ if (blackListInfo != null && blackListInfo.getCount() >= getMaxErrors()) {
+ RuntimeException throwable = new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!", blackListInfo.getCause());
+ throwable.printStackTrace();
+ return Futures.immediateFailedFuture(throwable);
+ }
+
+ try {
+ return invoke(functionName, args);
+ } catch (Exception e) {
+ BlackListKey blackListKey = new BlackListKey(scriptId, entityId);
+ blackListedFunctions.computeIfAbsent(blackListKey, key -> new BlackListInfo()).incrementWithReason(e);
+ return Futures.immediateFailedFuture(e);
+ }
+ }
+
+ private ListenableFuture<Object> invoke(String functionName, Object... args) throws ScriptException, NoSuchMethodException {
+ Object result;
+ if (useJsSandbox()) {
+ result = sandbox.getSandboxedInvocable().invokeFunction(functionName, args);
} else {
- return Futures.immediateFailedFuture(
- new RuntimeException("Script is blacklisted due to maximum error count " + getMaxErrors() + "!"));
+ result = ((Invocable) engine).invokeFunction(functionName, args);
}
+ return Futures.immediateFuture(result);
}
@Override
- public ListenableFuture<Void> release(UUID scriptId) {
- String functionName = functionsMap.get(scriptId);
- if (functionName != null) {
- try {
- if (useJsSandbox()) {
- sandbox.eval(functionName + " = undefined;");
- } else {
- engine.eval(functionName + " = undefined;");
+ public ListenableFuture<Void> release(UUID scriptId, EntityId entityId) {
+ ScriptInfo scriptInfo = scriptIdToInfo.get(scriptId);
+ if (scriptInfo == null) {
+ log.warn("Script release called for not existing script id [{}]", scriptId);
+ return Futures.immediateFuture(null);
+ }
+
+ synchronized (scriptInfo.getLock()) {
+ int remainingDuplicates = scriptInfo.getCount().decrementAndGet();
+ if (remainingDuplicates > 0) {
+ return Futures.immediateFuture(null);
+ }
+
+ String functionName = functionsMap.get(scriptId);
+ if (functionName != null) {
+ try {
+ if (useJsSandbox()) {
+ sandbox.eval(functionName + " = undefined;");
+ } else {
+ engine.eval(functionName + " = undefined;");
+ }
+ functionsMap.remove(scriptId);
+ blackListedFunctions.remove(new BlackListKey(scriptId, entityId));
+ } catch (ScriptException e) {
+ log.error("Could not release script [{}] [{}]", scriptId, remainingDuplicates);
+ return Futures.immediateFailedFuture(e);
}
- functionsMap.remove(scriptId);
- blackListedFunctions.remove(scriptId);
- } catch (ScriptException e) {
- return Futures.immediateFailedFuture(e);
+ } else {
+ log.warn("Function name do not exist for script [{}] [{}]", scriptId, remainingDuplicates);
}
}
return Futures.immediateFuture(null);
}
- private boolean isBlackListed(UUID scriptId) {
- if (blackListedFunctions.containsKey(scriptId)) {
- AtomicInteger errorCount = blackListedFunctions.get(scriptId);
- return errorCount.get() >= getMaxErrors();
- } else {
- return false;
- }
- }
private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) {
switch (scriptType) {
@@ -156,4 +194,67 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
}
}
+
+ private ScriptInfo deduplicate(JsScriptType scriptType, String scriptBody) {
+ ScriptInfo meta = ScriptInfo.preInit();
+ String key = deduplicateKey(scriptType, scriptBody);
+ ScriptInfo latestMeta = scriptKeyToInfo.computeIfAbsent(key, i -> meta);
+ return scriptIdToInfo.computeIfAbsent(latestMeta.getId(), i -> latestMeta);
+ }
+
+ private String deduplicateKey(JsScriptType scriptType, String scriptBody) {
+ return scriptType + "_" + scriptBody;
+ }
+
+ @Getter
+ private static class ScriptInfo {
+ private final UUID id;
+ private final Object lock;
+ private final AtomicInteger count;
+
+ ScriptInfo(UUID id, Object lock, AtomicInteger count) {
+ this.id = id;
+ this.lock = lock;
+ this.count = count;
+ }
+
+ static ScriptInfo preInit() {
+ UUID preId = UUID.randomUUID();
+ AtomicInteger preCount = new AtomicInteger();
+ Object preLock = new Object();
+ return new ScriptInfo(preId, preLock, preCount);
+ }
+ }
+
+ @EqualsAndHashCode
+ @Getter
+ @RequiredArgsConstructor
+ private static class BlackListKey {
+ private final UUID scriptId;
+ private final EntityId entityId;
+
+ }
+
+ @Data
+ private static class BlackListInfo {
+ private final AtomicInteger count;
+ private Exception ex;
+
+ BlackListInfo() {
+ this.count = new AtomicInteger(0);
+ }
+
+ void incrementWithReason(Exception e) {
+ count.incrementAndGet();
+ ex = e;
+ }
+
+ int getCount() {
+ return count.get();
+ }
+
+ Exception getCause() {
+ return ex;
+ }
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java b/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java
index ee86c62..5e1c676 100644
--- a/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java
+++ b/application/src/main/java/org/thingsboard/server/service/script/JsSandboxService.java
@@ -17,6 +17,7 @@
package org.thingsboard.server.service.script;
import com.google.common.util.concurrent.ListenableFuture;
+import org.thingsboard.server.common.data.id.EntityId;
import java.util.UUID;
@@ -24,8 +25,8 @@ public interface JsSandboxService {
ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames);
- ListenableFuture<Object> invokeFunction(UUID scriptId, Object... args);
+ ListenableFuture<Object> invokeFunction(UUID scriptId, EntityId entityId, Object... args);
- ListenableFuture<Void> release(UUID scriptId);
+ ListenableFuture<Void> release(UUID scriptId, EntityId entityId);
}
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 98b0e77..767dc05 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
@@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Sets;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
+import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
@@ -39,13 +40,15 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
private final JsSandboxService sandboxService;
private final UUID scriptId;
+ private final EntityId entityId;
- public RuleNodeJsScriptEngine(JsSandboxService sandboxService, String script, String... argNames) {
+ public RuleNodeJsScriptEngine(JsSandboxService sandboxService, EntityId entityId, String script, String... argNames) {
this.sandboxService = sandboxService;
+ this.entityId = entityId;
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);
}
}
@@ -162,20 +165,20 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
private JsonNode executeScript(TbMsg msg) throws ScriptException {
try {
String[] inArgs = prepareArgs(msg);
- String eval = sandboxService.invokeFunction(this.scriptId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
+ String eval = sandboxService.invokeFunction(this.scriptId, this.entityId, inArgs[0], inArgs[1], inArgs[2]).get().toString();
return mapper.readTree(eval);
} catch (ExecutionException e) {
if (e.getCause() instanceof ScriptException) {
throw (ScriptException)e.getCause();
} else {
- throw new ScriptException("Failed to execute js script: " + e.getMessage());
+ throw new ScriptException(e);
}
} catch (Exception e) {
- throw new ScriptException("Failed to execute js script: " + e.getMessage());
+ throw new ScriptException(e);
}
}
public void destroy() {
- sandboxService.release(this.scriptId);
+ sandboxService.release(this.scriptId, this.entityId);
}
}
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 263b484..548c417 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,24 +24,28 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
+import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
import org.thingsboard.rule.engine.api.util.DonAsynchron;
+import org.thingsboard.server.actors.service.ActorService;
import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
-import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
+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.timeseries.TimeseriesService;
@@ -101,6 +105,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
@Lazy
private DeviceStateService stateService;
+ @Autowired
+ @Lazy
+ private ActorService actorService;
+
private ExecutorService tsCallBackExecutor;
private ExecutorService wsCallBackExecutor;
@@ -204,6 +212,13 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
}
@Override
+ public void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> attributes) {
+ DeviceAttributesEventNotificationMsg notificationMsg = DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
+ deviceId, DataConstants.SHARED_SCOPE, new ArrayList<>(attributes));
+ actorService.onMsg(new SendToClusterMsg(deviceId, notificationMsg));
+ }
+
+ @Override
public void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data) {
ClusterAPIProtos.SubscriptionProto proto;
try {
@@ -355,9 +370,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor);
} else if (subscription.getType() == TelemetryFeature.TIMESERIES) {
long curTs = System.currentTimeMillis();
- List<TsKvQuery> queries = new ArrayList<>();
+ List<ReadTsKvQuery> queries = new ArrayList<>();
subscription.getKeyStates().entrySet().forEach(e -> {
- queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
+ queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
});
DonAsynchron.withCallback(tsService.findAll(entityId, queries),
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 3b2d690..2ff8e89 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<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
- List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
+ List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
.collect(Collectors.toList());
FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() {
@@ -337,7 +337,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), entityId);
startTs = cmd.getStartTs();
long endTs = cmd.getStartTs() + cmd.getTimeWindow();
- List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(),
+ List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, cmd.getInterval(),
getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys);
diff --git a/application/src/main/resources/logback.xml b/application/src/main/resources/logback.xml
index 978a570..dcfc930 100644
--- a/application/src/main/resources/logback.xml
+++ b/application/src/main/resources/logback.xml
@@ -17,7 +17,7 @@
-->
<!DOCTYPE configuration>
-<configuration>
+<configuration scan="true" scanPeriod="10 seconds">
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
diff --git a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
index ea70384..88961dc 100644
--- a/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
+++ b/application/src/test/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngineTest.java
@@ -21,12 +21,18 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.thingsboard.rule.engine.api.ScriptEngine;
+import org.thingsboard.server.common.data.id.EntityId;
+import org.thingsboard.server.common.data.id.RuleNodeId;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.TbMsgMetaData;
import javax.script.ScriptException;
-
+import java.util.Map;
import java.util.Set;
+import java.util.UUID;
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.atomic.AtomicInteger;
import static org.junit.Assert.*;
@@ -35,6 +41,8 @@ public class RuleNodeJsScriptEngineTest {
private ScriptEngine scriptEngine;
private TestNashornJsSandboxService jsSandboxService;
+ private EntityId ruleNodeId = new RuleNodeId(UUIDs.timeBased());
+
@Before
public void beforeTest() throws Exception {
jsSandboxService = new TestNashornJsSandboxService(false, 1, 100, 3);
@@ -48,7 +56,7 @@ public class RuleNodeJsScriptEngineTest {
@Test
public void msgCanBeUpdated() throws ScriptException {
String function = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
- scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+ scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
@@ -65,7 +73,7 @@ public class RuleNodeJsScriptEngineTest {
@Test
public void newAttributesCanBeAddedInMsg() throws ScriptException {
String function = "metadata.newAttr = metadata.humidity - msg.passed; return {metadata: metadata};";
- scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+ scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
@@ -81,7 +89,7 @@ public class RuleNodeJsScriptEngineTest {
@Test
public void payloadCanBeUpdated() throws ScriptException {
String function = "msg.passed = msg.passed * metadata.temp; msg.bigObj.newProp = 'Ukraine'; return {msg: msg};";
- scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+ scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
@@ -99,7 +107,7 @@ public class RuleNodeJsScriptEngineTest {
@Test
public void metadataAccessibleForFilter() throws ScriptException {
String function = "return metadata.humidity < 15;";
- scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+ scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
@@ -113,7 +121,7 @@ public class RuleNodeJsScriptEngineTest {
@Test
public void dataAccessibleForFilter() throws ScriptException {
String function = "return msg.passed < 15 && msg.name === 'Vit' && metadata.temp == 7 && msg.bigObj.prop == 42;";
- scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, function);
+ scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, function);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "7");
metaData.putValue("humidity", "99");
@@ -134,7 +142,7 @@ public class RuleNodeJsScriptEngineTest {
"};\n" +
"\n" +
"return nextRelation(metadata, msg);";
- scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, jsCode);
+ scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
@@ -156,7 +164,7 @@ public class RuleNodeJsScriptEngineTest {
"};\n" +
"\n" +
"return nextRelation(metadata, msg);";
- scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, jsCode);
+ scriptEngine = new RuleNodeJsScriptEngine(jsSandboxService, ruleNodeId, jsCode);
TbMsgMetaData metaData = new TbMsgMetaData();
metaData.putValue("temp", "10");
metaData.putValue("humidity", "99");
@@ -168,4 +176,75 @@ public class RuleNodeJsScriptEngineTest {
scriptEngine.destroy();
}
+ @Test
+ public void concurrentReleasedCorrectly() throws InterruptedException, ExecutionException {
+ String code = "metadata.temp = metadata.temp * 10; return {metadata: metadata};";
+
+ int repeat = 1000;
+ ExecutorService service = Executors.newFixedThreadPool(repeat);
+ Map<UUID, Object> scriptIds = new ConcurrentHashMap<>();
+ CountDownLatch startLatch = new CountDownLatch(repeat);
+ CountDownLatch finishLatch = new CountDownLatch(repeat);
+ AtomicInteger failedCount = new AtomicInteger(0);
+
+ for (int i = 0; i < repeat; i++) {
+ service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code));
+ }
+
+ finishLatch.await();
+ assertTrue(scriptIds.size() == 1);
+ assertTrue(failedCount.get() == 0);
+
+ CountDownLatch nextStart = new CountDownLatch(repeat);
+ CountDownLatch nextFinish = new CountDownLatch(repeat);
+ for (int i = 0; i < repeat; i++) {
+ service.submit(() -> runScript(nextStart, nextFinish, failedCount, scriptIds, code));
+ }
+
+ nextFinish.await();
+ assertTrue(scriptIds.size() == 1);
+ assertTrue(failedCount.get() == 0);
+ service.shutdownNow();
+ }
+
+ @Test
+ public void concurrentFailedEvaluationShouldThrowException() throws InterruptedException {
+ String code = "metadata.temp = metadata.temp * 10; urn {metadata: metadata};";
+
+ int repeat = 10000;
+ ExecutorService service = Executors.newFixedThreadPool(repeat);
+ Map<UUID, Object> scriptIds = new ConcurrentHashMap<>();
+ CountDownLatch startLatch = new CountDownLatch(repeat);
+ CountDownLatch finishLatch = new CountDownLatch(repeat);
+ AtomicInteger failedCount = new AtomicInteger(0);
+ for (int i = 0; i < repeat; i++) {
+ service.submit(() -> {
+ service.submit(() -> runScript(startLatch, finishLatch, failedCount, scriptIds, code));
+ });
+ }
+
+ finishLatch.await();
+ assertTrue(scriptIds.isEmpty());
+ assertEquals(repeat, failedCount.get());
+ service.shutdownNow();
+ }
+
+ private void runScript(CountDownLatch startLatch, CountDownLatch finishLatch, AtomicInteger failedCount,
+ Map<UUID, Object> scriptIds, String code) {
+ try {
+ for (int k = 0; k < 10; k++) {
+ startLatch.countDown();
+ startLatch.await();
+ UUID scriptId = jsSandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, code).get();
+ scriptIds.put(scriptId, new Object());
+ jsSandboxService.invokeFunction(scriptId, ruleNodeId, "{}", "{}", "TEXT").get();
+ jsSandboxService.release(scriptId, ruleNodeId).get();
+ }
+ } catch (Throwable th) {
+ failedCount.incrementAndGet();
+ } finally {
+ finishLatch.countDown();
+ }
+ }
+
}
\ No newline at end of file
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 0000000..11b95ca
--- /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 0000000..3c48adf
--- /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 0afe00b..c4fa69c 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 0000000..7607a59
--- /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 0000000..7d54745
--- /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 ca9f90c..9df514e 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 d88a1c4..55c5987 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
@@ -39,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;
@@ -92,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) {
@@ -106,10 +105,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
})
@Override
public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) {
@@ -120,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) {
@@ -134,10 +133,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
})
@Override
public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) {
@@ -148,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) {
@@ -162,10 +161,10 @@ public class BaseRelationService implements RelationService {
@Caching(evict = {
@CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}"),
- @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}"),
+ @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
})
@Override
public ListenableFuture<Boolean> deleteRelationAsync(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
@@ -248,26 +247,30 @@ public class BaseRelationService implements RelationService {
fromTypeAndTypeGroup.add(relation.getFrom());
fromTypeAndTypeGroup.add(relation.getType());
fromTypeAndTypeGroup.add(relation.getTypeGroup());
+ fromTypeAndTypeGroup.add(EntitySearchDirection.FROM.name());
cache.evict(fromTypeAndTypeGroup);
List<Object> fromAndTypeGroup = new ArrayList<>();
fromAndTypeGroup.add(relation.getFrom());
fromAndTypeGroup.add(relation.getTypeGroup());
+ fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
cache.evict(fromAndTypeGroup);
List<Object> toAndTypeGroup = new ArrayList<>();
toAndTypeGroup.add(relation.getTo());
toAndTypeGroup.add(relation.getTypeGroup());
+ toAndTypeGroup.add(EntitySearchDirection.TO.name());
cache.evict(toAndTypeGroup);
List<Object> toTypeAndTypeGroup = new ArrayList<>();
- fromTypeAndTypeGroup.add(relation.getTo());
- fromTypeAndTypeGroup.add(relation.getType());
- fromTypeAndTypeGroup.add(relation.getTypeGroup());
+ toTypeAndTypeGroup.add(relation.getTo());
+ toTypeAndTypeGroup.add(relation.getType());
+ toTypeAndTypeGroup.add(relation.getTypeGroup());
+ toTypeAndTypeGroup.add(EntitySearchDirection.TO.name());
cache.evict(toTypeAndTypeGroup);
}
- @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
+ @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}")
@Override
public List<EntityRelation> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
validate(from);
@@ -288,6 +291,7 @@ public class BaseRelationService implements RelationService {
List<Object> fromAndTypeGroup = new ArrayList<>();
fromAndTypeGroup.add(from);
fromAndTypeGroup.add(typeGroup);
+ fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
Cache cache = cacheManager.getCache(RELATIONS_CACHE);
List<EntityRelation> fromCache = cache.get(fromAndTypeGroup, List.class);
@@ -326,7 +330,7 @@ public class BaseRelationService implements RelationService {
});
}
- @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}")
+ @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}")
@Override
public List<EntityRelation> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
try {
@@ -345,7 +349,7 @@ public class BaseRelationService implements RelationService {
return relationDao.findAllByFromAndType(from, relationType, typeGroup);
}
- @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}")
+ @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}")
@Override
public List<EntityRelation> findByTo(EntityId to, RelationTypeGroup typeGroup) {
validate(to);
@@ -366,6 +370,7 @@ public class BaseRelationService implements RelationService {
List<Object> toAndTypeGroup = new ArrayList<>();
toAndTypeGroup.add(to);
toAndTypeGroup.add(typeGroup);
+ toAndTypeGroup.add(EntitySearchDirection.TO.name());
Cache cache = cacheManager.getCache(RELATIONS_CACHE);
List<EntityRelation> fromCache = cache.get(toAndTypeGroup, List.class);
@@ -415,7 +420,7 @@ public class BaseRelationService implements RelationService {
});
}
- @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
+ @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
@Override
public List<EntityRelation> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
try {
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 df72943..5bd9175 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
@@ -26,14 +26,16 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.data.domain.PageRequest;
+import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Component;
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;
@@ -103,7 +105,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
}
@Override
- public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
+ public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries
.stream()
.map(query -> findAllAsync(entityId, query))
@@ -122,7 +124,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
}, service);
}
- private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
+ private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(entityId, query);
} else {
@@ -229,7 +231,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
});
}
- private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
+ private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
return Futures.immediateFuture(
DaoUtil.convertDataList(
tsKvRepository.findAllWithLimit(
@@ -238,7 +240,9 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
query.getKey(),
query.getStartTs(),
query.getEndTs(),
- new PageRequest(0, query.getLimit()))));
+ new PageRequest(0, query.getLimit(),
+ new Sort(Sort.Direction.fromString(
+ query.getOrderBy()), "ts")))));
}
@Override
@@ -307,6 +311,36 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
});
}
+ @Override
+ public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
+ return service.submit(() -> {
+ tsKvRepository.delete(
+ fromTimeUUID(entityId.getId()),
+ entityId.getEntityType(),
+ query.getKey(),
+ query.getStartTs(),
+ query.getEndTs());
+ return null;
+ });
+ }
+
+ @Override
+ public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
+ TsKvLatestEntity latestEntity = new TsKvLatestEntity();
+ latestEntity.setEntityType(entityId.getEntityType());
+ latestEntity.setEntityId(fromTimeUUID(entityId.getId()));
+ latestEntity.setKey(query.getKey());
+ return service.submit(() -> {
+ tsKvLatestRepository.delete(latestEntity);
+ return null;
+ });
+ }
+
+ @Override
+ public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
+ return service.submit(() -> null);
+ }
+
@PreDestroy
void onDestroy() {
if (insertService != null) {
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 a1d1920..4c743e5 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;
@@ -33,7 +35,7 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
"AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
- "AND tskv.ts > :startTs AND tskv.ts < :endTs ORDER BY tskv.ts DESC")
+ "AND tskv.ts > :startTs AND tskv.ts < :endTs")
List<TsKvEntity> findAllWithLimit(@Param("entityId") String entityId,
@Param("entityType") EntityType entityType,
@Param("entityKey") String key,
@@ -41,6 +43,17 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
@Param("endTs") long endTs,
Pageable pageable);
+ @Transactional
+ @Modifying
+ @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
+ "AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
+ "AND tskv.ts > :startTs AND tskv.ts < :endTs")
+ void delete(@Param("entityId") String entityId,
+ @Param("entityType") EntityType entityType,
+ @Param("entityKey") String key,
+ @Param("startTs") long startTs,
+ @Param("endTs") long endTs);
+
@Async
@Query("SELECT new TsKvEntity(MAX(tskv.strValue), MAX(tskv.longValue), MAX(tskv.doubleValue)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
@@ -56,30 +69,30 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findMin(@Param("entityId") String entityId,
- @Param("entityType") EntityType entityType,
- @Param("entityKey") String entityKey,
- @Param("startTs") long startTs,
- @Param("endTs") long endTs);
+ @Param("entityType") EntityType entityType,
+ @Param("entityKey") String entityKey,
+ @Param("startTs") long startTs,
+ @Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(COUNT(tskv.booleanValue), COUNT(tskv.strValue), COUNT(tskv.longValue), COUNT(tskv.doubleValue)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findCount(@Param("entityId") String entityId,
- @Param("entityType") EntityType entityType,
- @Param("entityKey") String entityKey,
- @Param("startTs") long startTs,
- @Param("endTs") long endTs);
+ @Param("entityType") EntityType entityType,
+ @Param("entityKey") String entityKey,
+ @Param("startTs") long startTs,
+ @Param("endTs") long endTs);
@Async
@Query("SELECT new TsKvEntity(AVG(tskv.longValue), AVG(tskv.doubleValue)) FROM TsKvEntity tskv " +
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findAvg(@Param("entityId") String entityId,
- @Param("entityType") EntityType entityType,
- @Param("entityKey") String entityKey,
- @Param("startTs") long startTs,
- @Param("endTs") long endTs);
+ @Param("entityType") EntityType entityType,
+ @Param("entityKey") String entityKey,
+ @Param("startTs") long startTs,
+ @Param("endTs") long endTs);
@Async
@@ -87,8 +100,8 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
"WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
"AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
CompletableFuture<TsKvEntity> findSum(@Param("entityId") String entityId,
- @Param("entityType") EntityType entityType,
- @Param("entityKey") String entityKey,
- @Param("startTs") long startTs,
- @Param("endTs") long endTs);
+ @Param("entityType") EntityType entityType,
+ @Param("entityKey") String entityKey,
+ @Param("startTs") long startTs,
+ @Param("endTs") long endTs);
}
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 c981378..98e859f 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
@@ -22,8 +22,9 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
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 org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.Validator;
@@ -40,14 +41,15 @@ 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;
@Override
- public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries) {
+ public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) {
validate(entityId);
- queries.forEach(query -> validate(query));
+ queries.forEach(BaseTimeseriesService::validate);
return timeseriesDao.findAllAsync(entityId, queries);
}
@@ -95,17 +97,42 @@ public class BaseTimeseriesService implements TimeseriesService {
futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl));
}
+ @Override
+ public ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
+ validate(entityId);
+ deleteTsKvQueries.forEach(BaseTimeseriesService::validate);
+ List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY);
+ for (DeleteTsKvQuery tsKvQuery : deleteTsKvQueries) {
+ deleteAndRegisterFutures(futures, entityId, tsKvQuery);
+ }
+ return Futures.allAsList(futures);
+ }
+
+ private void deleteAndRegisterFutures(List<ListenableFuture<Void>> futures, EntityId entityId, DeleteTsKvQuery query) {
+ futures.add(timeseriesDao.remove(entityId, query));
+ futures.add(timeseriesDao.removeLatest(entityId, query));
+ futures.add(timeseriesDao.removePartition(entityId, query));
+ }
+
private static void validate(EntityId entityId) {
Validator.validateEntityId(entityId, "Incorrect entityId " + entityId);
}
- private static void validate(TsKvQuery query) {
+ private static void validate(ReadTsKvQuery query) {
if (query == null) {
- throw new IncorrectParameterException("TsKvQuery can't be null");
+ throw new IncorrectParameterException("ReadTsKvQuery can't be null");
} else if (isBlank(query.getKey())) {
- throw new IncorrectParameterException("Incorrect TsKvQuery. Key can't be empty");
+ throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Key can't be empty");
} else if (query.getAggregation() == null) {
- throw new IncorrectParameterException("Incorrect TsKvQuery. Aggregation can't be empty");
+ throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty");
+ }
+ }
+
+ private static void validate(DeleteTsKvQuery query) {
+ if (query == null) {
+ throw new IncorrectParameterException("DeleteTsKvQuery can't be null");
+ } else if (isBlank(query.getKey())) {
+ throw new IncorrectParameterException("Incorrect DeleteTsKvQuery. Key can't be empty");
}
}
}
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 be36d3b..709bfd5 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;
@@ -55,11 +57,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;
@@ -77,8 +79,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
public static final String GENERATED_QUERY_FOR_ENTITY_TYPE_AND_ENTITY_ID = "Generated query [{}] for entityType {} and entityId {}";
public static final String SELECT_PREFIX = "SELECT ";
public static final String EQUALS_PARAM = " = ? ";
-
-
+ public static final String ASC_ORDER = "ASC";
+ public static final String DESC_ORDER = "DESC";
private static List<Long> FIXED_PARTITION = Arrays.asList(new Long[]{0L});
@Autowired
@@ -97,9 +99,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");
@@ -109,7 +114,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
public void init() {
super.startExecutor();
if (!isInstall()) {
- getFetchStmt(Aggregation.NONE);
+ getFetchStmt(Aggregation.NONE, DESC_ORDER);
Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning);
if (partition.isPresent()) {
tsFormat = partition.get();
@@ -126,7 +131,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
@Override
- public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
+ public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityId, query)).collect(Collectors.toList());
return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() {
@Nullable
@@ -143,7 +148,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
- private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
+ private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
if (query.getAggregation() == Aggregation.NONE) {
return findAllAsyncWithLimit(entityId, query);
} else {
@@ -153,7 +158,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;
}
@@ -172,7 +177,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return tsFormat.getTruncateUnit().equals(TsPartitionDate.EPOCH_START);
}
- private ListenableFuture<List<Long>> getPartitionsFuture(TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
+ private ListenableFuture<List<Long>> getPartitionsFuture(ReadTsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
if (isFixedPartitioning()) { //no need to fetch partitions from DB
return Futures.immediateFuture(FIXED_PARTITION);
}
@@ -180,11 +185,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
}
- private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
-
+ private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
long minPartition = toPartitionTs(query.getStartTs());
long maxPartition = toPartitionTs(query.getEndTs());
-
final ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
final SimpleListenableFuture<List<TsKvEntry>> resultFuture = new SimpleListenableFuture<>();
@@ -213,7 +216,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());
@@ -238,14 +241,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
}
}
- private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, TsKvQuery query, long minPartition, long maxPartition) {
+ private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, ReadTsKvQuery query, long minPartition, long maxPartition) {
final Aggregation aggregation = query.getAggregation();
final String key = query.getKey();
final long startTs = query.getStartTs();
final long endTs = query.getEndTs();
final long ts = startTs + (endTs - startTs) / 2;
-
-
ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
ListenableFuture<List<ResultSet>> aggregationChunks = Futures.transformAsync(partitionsListFuture,
getFetchChunksAsyncFunction(entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor);
@@ -261,7 +262,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
private AsyncFunction<List<Long>, List<ResultSet>> getFetchChunksAsyncFunction(EntityId entityId, String key, Aggregation aggregation, long startTs, long endTs) {
return partitions -> {
try {
- PreparedStatement proto = getFetchStmt(aggregation);
+ PreparedStatement proto = getFetchStmt(aggregation, DESC_ORDER);
List<ResultSetFuture> futures = new ArrayList<>(partitions.size());
for (Long partition : partitions) {
log.trace("Fetching data for partition [{}] for entityType {} and entityId {}", partition, entityId.getEntityType(), entityId.getId());
@@ -364,6 +365,204 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
return getFuture(executeAsyncWrite(stmt), rs -> null);
}
+ @Override
+ public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
+ long minPartition = toPartitionTs(query.getStartTs());
+ long maxPartition = toPartitionTs(query.getEndTs());
+
+ ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
+
+ final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
+ final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
+
+ Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
+ @Override
+ public void onSuccess(@Nullable List<Long> partitions) {
+ QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitions);
+ deleteAsync(cursor, resultFuture);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
+ }
+ }, readResultsProcessingExecutor);
+ return resultFuture;
+ }
+
+ private void deleteAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
+ if (!cursor.hasNextPartition()) {
+ resultFuture.set(null);
+ } else {
+ PreparedStatement proto = getDeleteStmt();
+ BoundStatement stmt = proto.bind();
+ stmt.setString(0, cursor.getEntityType());
+ stmt.setUUID(1, cursor.getEntityId());
+ stmt.setString(2, cursor.getKey());
+ stmt.setLong(3, cursor.getNextPartition());
+ stmt.setLong(4, cursor.getStartTs());
+ stmt.setLong(5, cursor.getEndTs());
+
+ Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
+ @Override
+ public void onSuccess(@Nullable ResultSet result) {
+ deleteAsync(cursor, resultFuture);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
+ }
+ }, readResultsProcessingExecutor);
+ }
+ }
+
+ private PreparedStatement getDeleteStmt() {
+ if (deleteStmt == null) {
+ deleteStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_CF +
+ " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
+ + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ + "AND " + ModelConstants.TS_COLUMN + " > ? "
+ + "AND " + ModelConstants.TS_COLUMN + " <= ?");
+ }
+ return deleteStmt;
+ }
+
+ @Override
+ public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
+ ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(entityId, query.getKey());
+
+ ListenableFuture<Boolean> booleanFuture = Futures.transformAsync(latestEntryFuture, latestEntry -> {
+ long ts = latestEntry.getTs();
+ if (ts >= query.getStartTs() && ts <= query.getEndTs()) {
+ return Futures.immediateFuture(true);
+ } else {
+ log.trace("Won't be deleted latest value for [{}], key - {}", entityId, query.getKey());
+ }
+ return Futures.immediateFuture(false);
+ }, readResultsProcessingExecutor);
+
+ ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
+ if (isRemove) {
+ return deleteLatest(entityId, query.getKey());
+ }
+ return Futures.immediateFuture(null);
+ }, readResultsProcessingExecutor);
+
+ if (query.getRewriteLatestIfDeleted()) {
+ ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
+ if (isRemove) {
+ return getNewLatestEntryFuture(entityId, query);
+ }
+ return Futures.immediateFuture(null);
+ }, readResultsProcessingExecutor);
+
+ return Futures.transformAsync(Futures.allAsList(Arrays.asList(savedLatestFuture, removedLatestFuture)),
+ list -> Futures.immediateFuture(null), readResultsProcessingExecutor);
+ }
+ return removedLatestFuture;
+ }
+
+ private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
+ long startTs = 0;
+ long endTs = query.getStartTs() - 1;
+ ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1,
+ Aggregation.NONE, DESC_ORDER);
+ ListenableFuture<List<TsKvEntry>> future = findAllAsync(entityId, findNewLatestQuery);
+
+ return Futures.transformAsync(future, entryList -> {
+ if (entryList.size() == 1) {
+ return saveLatest(entityId, entryList.get(0));
+ } else {
+ log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey());
+ }
+ return Futures.immediateFuture(null);
+ }, readResultsProcessingExecutor);
+ }
+
+ private ListenableFuture<Void> deleteLatest(EntityId entityId, String key) {
+ Statement delete = QueryBuilder.delete().all().from(ModelConstants.TS_KV_LATEST_CF)
+ .where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityId.getEntityType()))
+ .and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId.getId()))
+ .and(eq(ModelConstants.KEY_COLUMN, key));
+ log.debug("Remove request: {}", delete.toString());
+ return getFuture(executeAsyncWrite(delete), rs -> null);
+ }
+
+ @Override
+ public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
+ long minPartition = toPartitionTs(query.getStartTs());
+ long maxPartition = toPartitionTs(query.getEndTs());
+ if (minPartition == maxPartition) {
+ return Futures.immediateFuture(null);
+ } else {
+ ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
+
+ final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
+ final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
+
+ Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
+ @Override
+ public void onSuccess(@Nullable List<Long> partitions) {
+ int index = 0;
+ if (minPartition != query.getStartTs()) {
+ index = 1;
+ }
+ List<Long> partitionsToDelete = new ArrayList<>();
+ for (int i = index; i < partitions.size() - 1; i++) {
+ partitionsToDelete.add(partitions.get(i));
+ }
+ QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitionsToDelete);
+ deletePartitionAsync(cursor, resultFuture);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
+ }
+ }, readResultsProcessingExecutor);
+ return resultFuture;
+ }
+ }
+
+ private void deletePartitionAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
+ if (!cursor.hasNextPartition()) {
+ resultFuture.set(null);
+ } else {
+ PreparedStatement proto = getDeletePartitionStmt();
+ BoundStatement stmt = proto.bind();
+ stmt.setString(0, cursor.getEntityType());
+ stmt.setUUID(1, cursor.getEntityId());
+ stmt.setLong(2, cursor.getNextPartition());
+ stmt.setString(3, cursor.getKey());
+
+ Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
+ @Override
+ public void onSuccess(@Nullable ResultSet result) {
+ deletePartitionAsync(cursor, resultFuture);
+ }
+
+ @Override
+ public void onFailure(Throwable t) {
+ log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
+ }
+ }, readResultsProcessingExecutor);
+ }
+ }
+
+ private PreparedStatement getDeletePartitionStmt() {
+ if (deletePartitionStmt == null) {
+ deletePartitionStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_PARTITIONS_CF +
+ " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
+ + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
+ + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
+ + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM);
+ }
+ return deletePartitionStmt;
+ }
+
private List<TsKvEntry> convertResultToTsKvEntryList(List<Row> rows) {
List<TsKvEntry> entries = new ArrayList<>(rows.size());
if (!rows.isEmpty()) {
@@ -459,28 +658,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 0000000..f210608
--- /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<Long> partitions;
+ private int partitionIndex;
+
+ public QueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
+ this.entityType = entityType;
+ this.entityId = entityId;
+ this.key = baseQuery.getKey();
+ this.startTs = baseQuery.getStartTs();
+ this.endTs = baseQuery.getEndTs();
+ this.partitions = partitions;
+ this.partitionIndex = partitions.size() - 1;
+ }
+
+ public boolean hasNextPartition() {
+ return partitionIndex >= 0;
+ }
+
+ public long getNextPartition() {
+ long partition = partitions.get(partitionIndex);
+ partitionIndex--;
+ return partition;
+ }
+
+}
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 1e3f4ce..a8cb547 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<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries);
+ ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries);
ListenableFuture<TsKvEntry> findLatest(EntityId entityId, String key);
@@ -38,4 +39,10 @@ public interface TimeseriesDao {
ListenableFuture<Void> savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl);
ListenableFuture<Void> saveLatest(EntityId entityId, TsKvEntry tsKvEntry);
+
+ ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query);
+
+ ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query);
+
+ ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query);
}
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 2cd2d8d..c5076d2 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<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries);
+ ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries);
ListenableFuture<List<TsKvEntry>> findLatest(EntityId entityId, Collection<String> keys);
@@ -37,4 +38,6 @@ public interface TimeseriesService {
ListenableFuture<List<Void>> save(EntityId entityId, TsKvEntry tsKvEntry);
ListenableFuture<List<Void>> save(EntityId entityId, List<TsKvEntry> tsKvEntry, long ttl);
+
+ ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> queries);
}
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 d6b6bbd..b56be56 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<Long> partitions;
+public class TsKvQueryCursor extends QueryCursor {
+
@Getter
private final List<TsKvEntry> data;
+ @Getter
+ private String orderBy;
private int partitionIndex;
private int currentLimit;
- public TsKvQueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
- this.entityType = entityType;
- this.entityId = entityId;
- this.key = baseQuery.getKey();
- this.startTs = baseQuery.getStartTs();
- this.endTs = baseQuery.getEndTs();
- this.partitions = partitions;
- this.partitionIndex = partitions.size() - 1;
+ public TsKvQueryCursor(String entityType, UUID entityId, ReadTsKvQuery baseQuery, List<Long> partitions) {
+ super(entityType, entityId, baseQuery, partitions);
+ this.orderBy = baseQuery.getOrderBy();
+ this.partitionIndex = isDesc() ? partitions.size() - 1 : 0;
this.data = new ArrayList<>();
this.currentLimit = baseQuery.getLimit();
}
+ @Override
public boolean hasNextPartition() {
- return partitionIndex >= 0;
+ return isDesc() ? partitionIndex >= 0 : partitionIndex <= partitions.size() - 1;
}
public boolean isFull() {
return currentLimit <= 0;
}
+ @Override
public long getNextPartition() {
long partition = partitions.get(partitionIndex);
- partitionIndex--;
+ if (isDesc()) {
+ partitionIndex--;
+ } else {
+ partitionIndex++;
+ }
return partition;
}
@@ -78,4 +74,8 @@ public class TsKvQueryCursor {
currentLimit -= newData.size();
data.addAll(newData);
}
+
+ private boolean isDesc() {
+ return orderBy.equals(DESC_ORDER);
+ }
}
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 f045bf1..4528d9a 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);
@@ -101,6 +103,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
}
@Test
+ public void testDeleteDeviceTsData() throws Exception {
+ DeviceId deviceId = new DeviceId(UUIDs.timeBased());
+
+ saveEntries(deviceId, 10000);
+ saveEntries(deviceId, 20000);
+ saveEntries(deviceId, 30000);
+ saveEntries(deviceId, 40000);
+
+ tsService.remove(deviceId, Collections.singletonList(
+ new BaseDeleteTsKvQuery(STRING_KEY, 15000, 45000))).get();
+
+ List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(
+ new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get();
+ Assert.assertEquals(1, list.size());
+
+ List<TsKvEntry> latest = tsService.findLatest(deviceId, Collections.singletonList(STRING_KEY)).get();
+ Assert.assertEquals(null, latest.get(0).getValueAsString());
+ }
+
+ @Test
public void testFindDeviceTsData() throws Exception {
DeviceId deviceId = new DeviceId(UUIDs.timeBased());
List<TsKvEntry> entries = new ArrayList<>();
@@ -114,7 +136,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
entries.add(save(deviceId, 45000, 500));
entries.add(save(deviceId, 55000, 600));
- List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+ List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.NONE))).get();
assertEquals(3, list.size());
assertEquals(55000, list.get(0).getTs());
@@ -126,7 +148,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(35000, list.get(2).getTs());
assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue());
- list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+ list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.AVG))).get();
assertEquals(3, list.size());
assertEquals(10000, list.get(0).getTs());
@@ -138,7 +160,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
- list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+ list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.SUM))).get();
assertEquals(3, list.size());
@@ -151,7 +173,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue());
- list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+ list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MIN))).get();
assertEquals(3, list.size());
@@ -164,7 +186,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue());
- list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+ list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.MAX))).get();
assertEquals(3, list.size());
@@ -177,7 +199,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
assertEquals(50000, list.get(2).getTs());
assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue());
- list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
+ list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
60000, 20000, 3, Aggregation.COUNT))).get();
assertEquals(3, list.size());
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 a59d83b..bcb8bd1 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<? extends Channel> channelClass = NioSocketChannel.class;
private boolean reconnect = true;
+ private long reconnectDelay = 1L;
+ private int maxBytesInMessage = 8092;
public MqttClientConfig() {
this(null);
@@ -146,4 +148,38 @@ public final class MqttClientConfig {
public void setReconnect(boolean reconnect) {
this.reconnect = reconnect;
}
+
+ public long getReconnectDelay() {
+ return reconnectDelay;
+ }
+
+ /**
+ * Sets the reconnect delay in seconds. Defaults to 1 second.
+ * @param reconnectDelay
+ * @throws IllegalArgumentException if reconnectDelay is smaller than 1.
+ */
+ public void setReconnectDelay(long reconnectDelay) {
+ if (reconnectDelay <= 0) {
+ throw new IllegalArgumentException("reconnectDelay must be > 0");
+ }
+ this.reconnectDelay = reconnectDelay;
+ }
+
+ public int getMaxBytesInMessage() {
+ return maxBytesInMessage;
+ }
+
+ /**
+ * Sets the maximum number of bytes in the message for the {@link io.netty.handler.codec.mqtt.MqttDecoder}.
+ * Default value is 8092 as specified by Netty. The absolute maximum size is 256MB as set by the MQTT spec.
+ *
+ * @param maxBytesInMessage
+ * @throws IllegalArgumentException if maxBytesInMessage is smaller than 1 or greater than 256_000_000.
+ */
+ public void setMaxBytesInMessage(int maxBytesInMessage) {
+ if (maxBytesInMessage <= 0 || maxBytesInMessage > 256_000_000) {
+ throw new IllegalArgumentException("maxBytesInMessage must be > 0 or < 256_000_000");
+ }
+ this.maxBytesInMessage = maxBytesInMessage;
+ }
}
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 a5df846..b9460b3 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 aa57a02..c70b906 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<Void> callback);
+ void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> 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 0000000..260669f
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
@@ -0,0 +1,169 @@
+/**
+ * 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.BaseReadTsKvQuery;
+import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
+import org.thingsboard.server.common.data.kv.TsKvEntry;
+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.*;
+import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
+
+/**
+ * Created by mshvayka on 04.09.18.
+ */
+@Slf4j
+@RuleNode(type = ComponentType.ENRICHMENT,
+ name = "originator telemetry",
+ configClazz = TbGetTelemetryNodeConfiguration.class,
+ nodeDescription = "Add Message Originator Telemetry for selected time range into Message Metadata\n",
+ nodeDetails = "The node allows you to select fetch mode <b>FIRST/LAST/ALL</b> to fetch telemetry of certain time range that are added into Message metadata without any prefix. " +
+ "If selected fetch mode <b>ALL</b> Telemetry will be added like array into Message Metadata where <b>key</b> is Timestamp and <b>value</b> is value of Telemetry. " +
+ "If selected fetch mode <b>FIRST</b> or <b>LAST</b> Telemetry will be added like string without Timestamp",
+ uiResources = {"static/rulenode/rulenode-core-config.js"},
+ configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase")
+public class TbGetTelemetryNode implements TbNode {
+
+ private TbGetTelemetryNodeConfiguration config;
+ private List<String> tsKeyNames;
+ private long startTsOffset;
+ private long endTsOffset;
+ private int limit;
+ private ObjectMapper mapper;
+ private String fetchMode;
+
+ @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;
+ fetchMode = config.getFetchMode();
+ 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<ReadTsKvQuery> queries = buildQueries();
+ ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(msg.getOriginator(), queries);
+ DonAsynchron.withCallback(list, data -> {
+ process(data, msg);
+ TbMsg newMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
+ ctx.tellNext(newMsg, SUCCESS);
+ }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor());
+ } catch (Exception e) {
+ ctx.tellFailure(msg, e);
+ }
+ }
+ }
+
+ private List<ReadTsKvQuery> buildQueries() {
+ long ts = System.currentTimeMillis();
+ long startTs = ts - startTsOffset;
+ long endTs = ts - endTsOffset;
+ String orderBy;
+ if (fetchMode.equals(FETCH_MODE_FIRST) || fetchMode.equals(FETCH_MODE_ALL)) {
+ orderBy = "ASC";
+ } else {
+ orderBy = "DESC";
+ }
+ return tsKeyNames.stream()
+ .map(key -> new BaseReadTsKvQuery(key, startTs, endTs, 1, limit, NONE, orderBy))
+ .collect(Collectors.toList());
+ }
+
+ private void process(List<TsKvEntry> entries, TbMsg msg) {
+ ObjectNode resultNode = mapper.createObjectNode();
+ if (limit == MAX_FETCH_SIZE) {
+ entries.forEach(entry -> processArray(resultNode, entry));
+ } else {
+ entries.forEach(entry -> processSingle(resultNode, entry));
+ }
+
+ for (String key : tsKeyNames) {
+ if (resultNode.has(key)) {
+ msg.getMetaData().putValue(key, resultNode.get(key).toString());
+ }
+ }
+ }
+
+ private void processSingle(ObjectNode node, TsKvEntry entry) {
+ node.put(entry.getKey(), entry.getValueAsString());
+ }
+
+ private void processArray(ObjectNode node, TsKvEntry entry) {
+ if (node.has(entry.getKey())) {
+ ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey());
+ ObjectNode obj = buildNode(entry);
+ arrayNode.add(obj);
+ } else {
+ ArrayNode arrayNode = mapper.createArrayNode();
+ ObjectNode obj = buildNode(entry);
+ arrayNode.add(obj);
+ node.set(entry.getKey(), arrayNode);
+ }
+ }
+
+ private ObjectNode buildNode(TsKvEntry entry) {
+ ObjectNode obj = mapper.createObjectNode()
+ .put("ts", entry.getTs());
+ switch (entry.getDataType()) {
+ case STRING:
+ obj.put("value", entry.getValueAsString());
+ break;
+ case LONG:
+ obj.put("value", entry.getLongValue().get());
+ break;
+ case BOOLEAN:
+ obj.put("value", entry.getBooleanValue().get());
+ break;
+ case DOUBLE:
+ obj.put("value", entry.getDoubleValue().get());
+ break;
+ }
+ return obj;
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
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 0000000..37ce0af
--- /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<TbGetTelemetryNodeConfiguration> {
+
+ public static final String FETCH_MODE_FIRST = "FIRST";
+ public static final String FETCH_MODE_LAST = "LAST";
+ public static final String FETCH_MODE_ALL = "ALL";
+ public static final int MAX_FETCH_SIZE = 1000;
+
+ private int startInterval;
+ private int endInterval;
+ private String startIntervalTimeUnit;
+ private String endIntervalTimeUnit;
+ private String fetchMode; //FIRST, LAST, LATEST
+
+ private List<String> latestTsKeyNames;
+
+
+
+ @Override
+ public TbGetTelemetryNodeConfiguration defaultConfiguration() {
+ TbGetTelemetryNodeConfiguration configuration = new TbGetTelemetryNodeConfiguration();
+ configuration.setLatestTsKeyNames(Collections.emptyList());
+ configuration.setFetchMode("FIRST");
+ configuration.setStartIntervalTimeUnit(TimeUnit.MINUTES.name());
+ configuration.setStartInterval(2);
+ configuration.setEndIntervalTimeUnit(TimeUnit.MINUTES.name());
+ configuration.setEndInterval(1);
+ return configuration;
+ }
+}
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 4f82884..5cdb04e 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<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes();
ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg));
+ if (msg.getOriginator().getEntityType() == EntityType.DEVICE && DataConstants.SHARED_SCOPE.equals(config.getScope())) {
+ ctx.getTelemetryService().onSharedAttributesUpdate(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId()), attributes);
+ }
}
@Override
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 e06d253..2e0ab78 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=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=msgDelayConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=0> <div ng-messages=msgDelayConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> ';
-},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},22,function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <tb-entity-type-select style=min-width:100px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px> </span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},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("<div></div>"),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<r.messageTypes.length;t++)e.push(r.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(r.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;a.html(d),r.selectedMessageType=null,r.messageTypeSearchText=null,r.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}r.transformMessageTypeChip=function(e){var n,r=t("filter")(c,{name:e},!0);return n=r&&r.length?angular.copy(r[0]):{name:e,value:e}},r.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},r.createMessageType=function(e,t){var n=angular.element(t,a)[0].firstElementChild,r=angular.element(n),i=r.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),r.scope().$mdChipsCtrl.appendChip(i.trim()),r.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){r.messageTypesWatch&&(r.messageTypesWatch(),r.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var a=0;a<e.messageTypes.length;a++){var i=e.messageTypes[a];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}r.messageTypes=t,r.messageTypesWatch=r.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(a.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:r}}a.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=a,n(3);var i=n(29),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.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],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(30),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,"filter",t.instant("tb.rulenode.filter")+"","Filter",["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(31),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,"switch",t.instant("tb.rulenode.switch")+"","Switch",["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(32),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){function i(e){e>-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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method",headers:"Headers","headers-hint":"Use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=msgDelayConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=0> <div ng-messages=msgDelayConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> ';
+},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=' <section class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <md-input-container style=margin-bottom:38px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit"> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},23,function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <tb-entity-type-select style=min-width:100px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:'...'}}" }\'>tb.rulenode.no-message-type-matching</span> <span> <a translate ng-click="createMessageType($event, \'#message_type_chips\')">tb.rulenode.create-new-message-type</a> </span> </div> </div> </md-not-found> </md-autocomplete> <md-chip-template> <span>{{$chip.name}}</span> </md-chip-template> </md-chips> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=messageTypes class=tb-error-message>tb.rulenode.message-types-required</div> </div> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" class=required>tb.rulenode.originator-types-filter</label> <tb-entity-type-list flex ng-model=configuration.originatorTypes allowed-entity-types=allowedEntityTypes ignore-authority-filter=true tb-required=true> </tb-entity-type-list> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.filter</label> <tb-js-func ng-model=configuration.jsScript function-name=Filter function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-filter-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.switch</label> <tb-js-func ng-model=configuration.jsScript function-name=Switch function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-switch-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-kv-map-config layout=column> <div class=header flex layout=row> <span class=cell flex translate>{{ keyText }}</span> <span class=cell flex translate>{{ valText }}</span> <span ng-show=!disabled style=width:52px> </span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},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("<div></div>"),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<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){a.messageTypesWatch&&(a.messageTypesWatch(),a.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.messageTypesWatch=a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(4);var i=n(31),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.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],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(32),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,"filter",t.instant("tb.rulenode.filter")+"","Filter",["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(33),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,"switch",t.instant("tb.rulenode.switch")+"","Switch",["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(34),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){function i(e){e>-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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method",headers:"Headers","headers-hint":"Use <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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 <code>${metaKeyName}</code> 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/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
index bac68ff..8a596d7 100644
--- a/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
+++ b/transport/coap/src/main/java/org/thingsboard/server/transport/coap/CoapTransportResource.java
@@ -113,6 +113,12 @@ public class CoapTransportResource extends CoapResource {
@Override
public void handlePOST(CoapExchange exchange) {
+ if(quotaService.isQuotaExceeded(exchange.getSourceAddress().getHostAddress())) {
+ log.warn("COAP Quota exceeded for [{}:{}] . Disconnect", exchange.getSourceAddress().getHostAddress(), exchange.getSourcePort());
+ exchange.respond(ResponseCode.BAD_REQUEST);
+ return;
+ }
+
Optional<FeatureType> featureType = getFeatureType(exchange.advanced().getRequest());
if (!featureType.isPresent()) {
log.trace("Missing feature type parameter");
ui/.stylelintrc 292(+292 -0)
diff --git a/ui/.stylelintrc b/ui/.stylelintrc
new file mode 100644
index 0000000..c145c8b
--- /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
+ }
+}
ui/package.json 12(+9 -3)
diff --git a/ui/package.json b/ui/package.json
index 1b8ceda..498de06 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": {
ui/src/app/alarm/alarm.scss 23(+12 -11)
diff --git a/ui/src/app/alarm/alarm.scss b/ui/src/app/alarm/alarm.scss
index faf7feb..2f6f459 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/alarm/alarm-details-dialog.scss b/ui/src/app/alarm/alarm-details-dialog.scss
index b7d3cd0..ddae1f1 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;
}
}
ui/src/app/api/subscription.js 8(+8 -0)
diff --git a/ui/src/app/api/subscription.js b/ui/src/app/api/subscription.js
index 350b1ef..a66c4b1 100644
--- a/ui/src/app/api/subscription.js
+++ b/ui/src/app/api/subscription.js
@@ -267,6 +267,14 @@ export default class Subscription {
} else {
this.startWatchingTimewindow();
}
+ registration = this.ctx.$scope.$watch(function () {
+ return subscription.alarmSearchStatus;
+ }, function (newAlarmSearchStatus, prevAlarmSearchStatus) {
+ if (!angular.equals(newAlarmSearchStatus, prevAlarmSearchStatus)) {
+ subscription.update();
+ }
+ }, true);
+ this.registrations.push(registration);
}
initDataSubscription() {
ui/src/app/audit/audit-log.scss 27(+15 -12)
diff --git a/ui/src/app/audit/audit-log.scss b/ui/src/app/audit/audit-log.scss
index 7cae107..671cdc2 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/audit/audit-log-details-dialog.scss b/ui/src/app/audit/audit-log-details-dialog.scss
index 1a1507c..e15fafe 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;
+}
ui/src/app/components/dashboard.scss 36(+20 -16)
diff --git a/ui/src/app/components/dashboard.scss b/ui/src/app/components/dashboard.scss
index ca99fcc..e0a9afd 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/dashboard-autocomplete.scss b/ui/src/app/components/dashboard-autocomplete.scss
index 9541fe1..8727fa6 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;
ui/src/app/components/dashboard-select.scss 30(+18 -12)
diff --git a/ui/src/app/components/dashboard-select.scss b/ui/src/app/components/dashboard-select.scss
index 1dee53d..9ca36b6 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/datakey-config.scss b/ui/src/app/components/datakey-config.scss
index 0de8b63..646a18a 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;
}
ui/src/app/components/datasource.scss 41(+23 -18)
diff --git a/ui/src/app/components/datasource.scss b/ui/src/app/components/datasource.scss
index bb5f827..d8096bd 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;
}
ui/src/app/components/datasource-entity.scss 16(+11 -5)
diff --git a/ui/src/app/components/datasource-entity.scss b/ui/src/app/components/datasource-entity.scss
index f054cab..681fed1 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 4fc4281..ce4ca2b 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/datetime-period.scss b/ui/src/app/components/datetime-period.scss
index 61009ad..8a690e4 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/datetime-period.tpl.html b/ui/src/app/components/datetime-period.tpl.html
index 605c3a8..7479576 100644
--- a/ui/src/app/components/datetime-period.tpl.html
+++ b/ui/src/app/components/datetime-period.tpl.html
@@ -18,14 +18,14 @@
<section layout="column" layout-align="start start">
<section layout="row" layout-align="start start">
<mdp-date-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.date-from' | translate }}"
- mdp-max-date="maxStartDate"></mdp-date-picker>
+ ></mdp-date-picker>
<mdp-time-picker ng-model="startDate" mdp-placeholder="{{ 'datetime.time-from' | translate }}"
- mdp-max-date="maxStartDate" mdp-auto-switch="true"></mdp-time-picker>
+ mdp-auto-switch="true"></mdp-time-picker>
</section>
<section layout="row" layout-align="start start">
<mdp-date-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.date-to' | translate }}"
- mdp-min-date="minEndDate" mdp-max-date="maxEndDate"></mdp-date-picker>
+ ></mdp-date-picker>
<mdp-time-picker ng-model="endDate" mdp-placeholder="{{ 'datetime.time-to' | translate }}"
- mdp-min-date="minEndDate" mdp-max-date="maxEndDate" mdp-auto-switch="true"></mdp-time-picker>
+ mdp-auto-switch="true"></mdp-time-picker>
</section>
</section>
\ No newline at end of file
ui/src/app/components/details-sidenav.scss 34(+19 -15)
diff --git a/ui/src/app/components/details-sidenav.scss b/ui/src/app/components/details-sidenav.scss
index c7e9919..0873cfd 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 d143933..d7f6278 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;
ui/src/app/components/expand-fullscreen.scss 29(+15 -14)
diff --git a/ui/src/app/components/expand-fullscreen.scss b/ui/src/app/components/expand-fullscreen.scss
index eef599c..ae7e8b8 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;
ui/src/app/components/grid.scss 13(+9 -4)
diff --git a/ui/src/app/components/grid.scss b/ui/src/app/components/grid.scss
index 17054d5..4313848 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;
}
ui/src/app/components/js-func.scss 23(+14 -9)
diff --git a/ui/src/app/components/js-func.scss b/ui/src/app/components/js-func.scss
index ade2830..affdc5b 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;
}
ui/src/app/components/json-content.scss 20(+12 -8)
diff --git a/ui/src/app/components/json-content.scss b/ui/src/app/components/json-content.scss
index 287c7e3..4f245af 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.directive.js b/ui/src/app/components/json-form.directive.js
index 5749271..b016f57 100644
--- a/ui/src/app/components/json-form.directive.js
+++ b/ui/src/app/components/json-form.directive.js
@@ -82,6 +82,7 @@ function JsonForm($compile, $templateCache, $mdColorPicker) {
val = undefined;
}
selectOrSet(key, scope.model, val);
+ scope.formProps.model = scope.model;
},
onColorClick: function(event, key, val) {
scope.showColorPicker(event, val);
ui/src/app/components/json-form.scss 5(+3 -2)
diff --git a/ui/src/app/components/json-form.scss b/ui/src/app/components/json-form.scss
index 66a5c1a..f102d4c 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 232d69a..a2bdb6a 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;
}
ui/src/app/components/kv-map.scss 8(+5 -3)
diff --git a/ui/src/app/components/kv-map.scss b/ui/src/app/components/kv-map.scss
index d51653c..35267d4 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
+}
ui/src/app/components/legend.scss 26(+18 -8)
diff --git a/ui/src/app/components/legend.scss b/ui/src/app/components/legend.scss
index 257a77f..e5e3f04 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;
}
}
}
ui/src/app/components/legend-config.scss 22(+14 -8)
diff --git a/ui/src/app/components/legend-config.scss b/ui/src/app/components/legend-config.scss
index fb10928..205d67d 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/material-icons-dialog.scss b/ui/src/app/components/material-icons-dialog.scss
index 3048fd1..e50caff 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
+}
ui/src/app/components/material-icon-select.scss 22(+12 -10)
diff --git a/ui/src/app/components/material-icon-select.scss b/ui/src/app/components/material-icon-select.scss
index e12e1d0..2814bb3 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;
+ }
+}
ui/src/app/components/menu-link.scss 6(+4 -2)
diff --git a/ui/src/app/components/menu-link.scss b/ui/src/app/components/menu-link.scss
index b21c0f4..299797c 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);
}
ui/src/app/components/react/json-form.scss 121(+63 -58)
diff --git a/ui/src/app/components/react/json-form.scss b/ui/src/app/components/react/json-form.scss
index f84fb85..cca9d07 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/react/json-form-ace-editor.scss b/ui/src/app/components/react/json-form-ace-editor.scss
index 42ab8c5..c08eba1 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 ee55ec3..7bc0826 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 {
ui/src/app/components/react/json-form-image.scss 91(+47 -44)
diff --git a/ui/src/app/components/react/json-form-image.scss b/ui/src/app/components/react/json-form-image.scss
index 8872b09..e602f52 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-rc-select.jsx b/ui/src/app/components/react/json-form-rc-select.jsx
index ffa0331..83fa929 100644
--- a/ui/src/app/components/react/json-form-rc-select.jsx
+++ b/ui/src/app/components/react/json-form-rc-select.jsx
@@ -27,39 +27,90 @@ class ThingsboardRcSelect extends React.Component {
this.onDeselect = this.onDeselect.bind(this);
this.onBlur = this.onBlur.bind(this);
this.onFocus = this.onFocus.bind(this);
- let emptyValue = this.props.form.schema.type === 'array'? [] : null;
this.state = {
- currentValue: this.props.value || emptyValue,
+ currentValue: this.keyToCurrentValue(this.props.value, this.props.form.schema.type === 'array'),
items: this.props.form.items,
focused: false
};
}
+ keyToCurrentValue(key, isArray) {
+ var currentValue = isArray ? [] : null;
+ if (isArray) {
+ var keys = key;
+ if (keys) {
+ for (var i = 0; i < keys.length; i++) {
+ currentValue.push({key: keys[i], label: this.labelFromKey(keys[i])});
+ }
+ }
+ } else {
+ currentValue = {key: key, label: this.labelFromKey(key)};
+ }
+ return currentValue;
+ }
+
+ labelFromKey(key) {
+ let label = key || '';
+ if (key) {
+ for (var i=0;i<this.props.form.items.length;i++) {
+ var item = this.props.form.items[i];
+ if (item.value === key) {
+ label = item.label;
+ break;
+ }
+ }
+ }
+ return label;
+ }
+
+ arrayValues(items) {
+ var v = [];
+ if (items) {
+ for (var i = 0; i < items.length; i++) {
+ v.push(items[i].key);
+ }
+ }
+ return v;
+ }
+
+ keyIndex(values, key) {
+ var index = -1;
+ if (values) {
+ for (var i = 0; i < values.length; i++) {
+ if (values[i].key === key) {
+ index = i;
+ break;
+ }
+ }
+ }
+ return index;
+ }
+
onSelect(value, option) {
if(this.props.form.schema.type === 'array') {
let v = this.state.currentValue;
- v.push(value);
+ v.push(this.keyToCurrentValue(value.key, false));
this.setState({
currentValue: v
});
- this.props.onChangeValidate(v);
+ this.props.onChangeValidate(this.arrayValues(v));
} else {
- this.setState({currentValue: value});
- this.props.onChangeValidate({target: {value: value}});
+ this.setState({currentValue: this.keyToCurrentValue(value.key, false)});
+ this.props.onChangeValidate({target: {value: value.key}});
}
}
onDeselect(value, option) {
if (this.props.form.schema.type === 'array') {
let v = this.state.currentValue;
- let index = v.indexOf(value);
+ let index = this.keyIndex(v, value.key);
if (index > -1) {
v.splice(index, 1);
}
this.setState({
currentValue: v
});
- this.props.onChangeValidate(v);
+ this.props.onChangeValidate(this.arrayValues(v));
}
}
@@ -105,6 +156,7 @@ class ThingsboardRcSelect extends React.Component {
combobox={this.props.form.combobox}
disabled={this.props.form.readonly}
value={this.state.currentValue}
+ labelInValue={true}
onSelect={this.onSelect}
onDeselect={this.onDeselect}
onFocus={this.onFocus}
diff --git a/ui/src/app/components/react/json-form-schema-form.jsx b/ui/src/app/components/react/json-form-schema-form.jsx
index e006eb9..09712f2 100644
--- a/ui/src/app/components/react/json-form-schema-form.jsx
+++ b/ui/src/app/components/react/json-form-schema-form.jsx
@@ -63,11 +63,15 @@ class ThingsboardSchemaForm extends React.Component {
this.onChange = this.onChange.bind(this);
this.onColorClick = this.onColorClick.bind(this);
+ this.hasConditions = false;
}
onChange(key, val) {
//console.log('SchemaForm.onChange', key, val);
this.props.onModelChange(key, val);
+ if (this.hasConditions) {
+ this.forceUpdate();
+ }
}
onColorClick(event, key, val) {
@@ -81,8 +85,11 @@ class ThingsboardSchemaForm extends React.Component {
console.log('Invalid field: \"' + form.key[0] + '\"!');
return null;
}
- if(form.condition && eval(form.condition) === false) {
- return null;
+ if(form.condition) {
+ this.hasConditions = true;
+ if (eval(form.condition) === false) {
+ return null;
+ }
}
return <Field model={model} form={form} key={index} onChange={onChange} onColorClick={onColorClick} mapper={mapper} builder={this.builder}/>
}
diff --git a/ui/src/app/components/related-entity-autocomplete.scss b/ui/src/app/components/related-entity-autocomplete.scss
index 8d4683a..74f652a 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;
ui/src/app/components/side-menu.scss 39(+21 -18)
diff --git a/ui/src/app/components/side-menu.scss b/ui/src/app/components/side-menu.scss
index 469c575..46da8db 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;
}
ui/src/app/components/timeinterval.scss 11(+9 -2)
diff --git a/ui/src/app/components/timeinterval.scss b/ui/src/app/components/timeinterval.scss
index 4bae01d..c5c0683 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;
}
ui/src/app/components/timewindow.scss 37(+25 -12)
diff --git a/ui/src/app/components/timewindow.scss b/ui/src/app/components/timewindow.scss
index 01ad5f9..8d4ed89 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 91d587b..69891cc 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;
}
}
}
ui/src/app/components/widget/widget.scss 11(+7 -4)
diff --git a/ui/src/app/components/widget/widget.scss b/ui/src/app/components/widget/widget.scss
index 347417b..3d5a9b4 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/widget/widget-config.scss b/ui/src/app/components/widget/widget-config.scss
index 7ff2051..4ac8561 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/widgets-bundle-select.scss b/ui/src/app/components/widgets-bundle-select.scss
index 41c4ee7..f5d1f22 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;
ui/src/app/dashboard/dashboard.scss 79(+46 -33)
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index 224dcf7..00a0653 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/dashboard-card.scss b/ui/src/app/dashboard/dashboard-card.scss
index 0dac213..8e6626c 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;
}
ui/src/app/dashboard/dashboard-settings.scss 26(+15 -11)
diff --git a/ui/src/app/dashboard/dashboard-settings.scss b/ui/src/app/dashboard/dashboard-settings.scss
index 37da008..e278240 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;
}
ui/src/app/dashboard/dashboard-toolbar.scss 81(+56 -25)
diff --git a/ui/src/app/dashboard/dashboard-toolbar.scss b/ui/src/app/dashboard/dashboard-toolbar.scss
index 40c17c4..80fd876 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/states/default-state-controller.scss b/ui/src/app/dashboard/states/default-state-controller.scss
index 5d6ae97..0fafd3b 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 4c2d0c3..5756182 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 2aa1ca2..bf139be 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 978bb3e..1244437 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 bd4cd51..c72854f 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
+}
ui/src/app/entity/alias/entity-aliases.scss 18(+13 -5)
diff --git a/ui/src/app/entity/alias/entity-aliases.scss b/ui/src/app/entity/alias/entity-aliases.scss
index 4b1fbeb..0f66d89 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;
}
}
}
ui/src/app/entity/attribute/attribute-table.scss 30(+16 -14)
diff --git a/ui/src/app/entity/attribute/attribute-table.scss b/ui/src/app/entity/attribute/attribute-table.scss
index 5d5c0be..ef42f4e 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 dad3d97..6617fcb 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;
ui/src/app/entity/entity-filter.scss 8(+5 -3)
diff --git a/ui/src/app/entity/entity-filter.scss b/ui/src/app/entity/entity-filter.scss
index bbaa108..950ed0a 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-filter-view.scss b/ui/src/app/entity/entity-filter-view.scss
index 69922c8..e85128a 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
+}
ui/src/app/entity/entity-list.scss 7(+5 -2)
diff --git a/ui/src/app/entity/entity-list.scss b/ui/src/app/entity/entity-list.scss
index 6e1c892..a3c1bc0 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
+}
+*/
ui/src/app/entity/entity-select.scss 6(+4 -2)
diff --git a/ui/src/app/entity/entity-select.scss b/ui/src/app/entity/entity-select.scss
index ab7bd3d..5d1b043 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 c8e3edc..6dc3ec6 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 f024956..001ca3a 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 15b3f2a..9b085e2 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 21a88ee..a270f38 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 3a60865..15f2d89 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 1c0bf2e..6dcfdfd 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;
}
}
ui/src/app/entity/relation/relation-filters.scss 29(+18 -11)
diff --git a/ui/src/app/entity/relation/relation-filters.scss b/ui/src/app/entity/relation/relation-filters.scss
index 34254d0..affea5d 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 2d618fb..873c9d7 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 2293d79..6a06aa3 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;
ui/src/app/event/event.scss 75(+40 -35)
diff --git a/ui/src/app/event/event.scss b/ui/src/app/event/event.scss
index b0fc46f..5a13d1f 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/extensions-forms/extension-form.scss b/ui/src/app/extension/extensions-forms/extension-form.scss
index c7fcbf4..2332cf5 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%;
+}
ui/src/app/extension/extension-table.scss 21(+13 -8)
diff --git a/ui/src/app/extension/extension-table.scss b/ui/src/app/extension/extension-table.scss
index c8946df..b809622 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;
+}
ui/src/app/help/help.scss 8(+6 -2)
diff --git a/ui/src/app/help/help.scss b/ui/src/app/help/help.scss
index b37dc31..0dd2bc3 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 {
}
+*/
ui/src/app/home/home-links.scss 13(+7 -6)
diff --git a/ui/src/app/home/home-links.scss b/ui/src/app/home/home-links.scss
index 8476eb4..677620d 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 c1561f3..511a910 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;
}
ui/src/app/jsonform/jsonform.scss 6(+4 -2)
diff --git a/ui/src/app/jsonform/jsonform.scss b/ui/src/app/jsonform/jsonform.scss
index 8205fa4..6b1c258 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; }
ui/src/app/layout/home.scss 29(+19 -10)
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
index b1bb554..48c1553 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;
}
ui/src/app/layout/user-menu.scss 20(+12 -8)
diff --git a/ui/src/app/layout/user-menu.scss b/ui/src/app/layout/user-menu.scss
index 9512bca..8b684ee 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/locale/locale.constant-en_US.json b/ui/src/app/locale/locale.constant-en_US.json
index 6321d63..2caf013 100644
--- a/ui/src/app/locale/locale.constant-en_US.json
+++ b/ui/src/app/locale/locale.constant-en_US.json
@@ -133,8 +133,13 @@
"min-polling-interval-message": "At least 1 sec polling interval is allowed.",
"aknowledge-alarms-title": "Acknowledge { count, plural, 1 {1 alarm} other {# alarms} }",
"aknowledge-alarms-text": "Are you sure you want to acknowledge { count, plural, 1 {1 alarm} other {# alarms} }?",
+ "aknowledge-alarm-title": "Acknowledge Alarm",
+ "aknowledge-alarm-text": "Are you sure you want to acknowledge Alarm?",
"clear-alarms-title": "Clear { count, plural, 1 {1 alarm} other {# alarms} }",
- "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?"
+ "clear-alarms-text": "Are you sure you want to clear { count, plural, 1 {1 alarm} other {# alarms} }?",
+ "clear-alarm-title": "Clear Alarm",
+ "clear-alarm-text": "Are you sure you want to clear Alarm?",
+ "alarm-status-filter": "Alarm Status Filter"
},
"alias": {
"add": "Add alias",
@@ -748,7 +753,8 @@
"entity-name": "Entity name",
"details": "Entity details",
"no-entities-prompt": "No entities found",
- "no-data": "No data to display"
+ "no-data": "No data to display",
+ "columns-to-display": "Columns to Display"
},
"event": {
"event-type": "Event type",
ui/src/app/login/login.scss 5(+4 -1)
diff --git a/ui/src/app/login/login.scss b/ui/src/app/login/login.scss
index 373a68f..2930ab9 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;
}
ui/src/app/rulechain/link.scss 4(+3 -1)
diff --git a/ui/src/app/rulechain/link.scss b/ui/src/app/rulechain/link.scss
index 3e86619..b37eaf3 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 4cad9cb..fc702d4 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;
ui/src/app/rulechain/rulechain.scss 272(+166 -106)
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index 5999b7e..8f77300 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
+}
ui/src/app/rulechain/rulenode.scss 6(+3 -3)
diff --git a/ui/src/app/rulechain/rulenode.scss b/ui/src/app/rulechain/rulenode.scss
index 0466673..0bdb3ab 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 a75ae59..1dd174d 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%;
}
}
-
}
ui/src/app/services/toast.scss 13(+7 -6)
diff --git a/ui/src/app/services/toast.scss b/ui/src/app/services/toast.scss
index 428e94e..952a42d 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;
}
ui/src/app/user/user-fieldset.scss 6(+4 -2)
diff --git a/ui/src/app/user/user-fieldset.scss b/ui/src/app/user/user-fieldset.scss
index cf21abc..93b8b51 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
+}
ui/src/app/widget/lib/alarms-table-widget.js 190(+176 -14)
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 0696a7b..6ae17e0 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -14,11 +14,15 @@
* limitations under the License.
*/
import './alarms-table-widget.scss';
+import './display-columns-panel.scss';
+import './alarm-status-filter-panel.scss';
/* eslint-disable import/no-unresolved, import/default */
import alarmsTableWidgetTemplate from './alarms-table-widget.tpl.html';
import alarmDetailsDialogTemplate from '../../alarm/alarm-details-dialog.tpl.html';
+import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
+import alarmStatusFilterPanelTemplate from './alarm-status-filter-panel.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -45,7 +49,7 @@ function AlarmsTableWidget() {
}
/*@ngInject*/
-function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, $timeout, alarmService, utils, types) {
+function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $mdPanel, $document, $translate, $q, $timeout, alarmService, utils, types) {
var vm = this;
vm.stylesInfo = {};
@@ -60,6 +64,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.selectedAlarms = []
vm.alarmSource = null;
+ vm.alarmSearchStatus = null;
vm.allAlarms = [];
vm.currentAlarm = null;
@@ -95,14 +100,20 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.onPaginate = onPaginate;
vm.onRowClick = onRowClick;
vm.onActionButtonClick = onActionButtonClick;
+ vm.actionEnabled = actionEnabled;
vm.isCurrent = isCurrent;
vm.openAlarmDetails = openAlarmDetails;
vm.ackAlarms = ackAlarms;
+ vm.ackAlarm = ackAlarm;
vm.clearAlarms = clearAlarms;
+ vm.clearAlarm = clearAlarm;
vm.cellStyle = cellStyle;
vm.cellContent = cellContent;
+ vm.editAlarmStatusFilter = editAlarmStatusFilter;
+ vm.editColumnsToDisplay = editColumnsToDisplay;
+
$scope.$watch('vm.ctx', function() {
if (vm.ctx) {
vm.settings = vm.ctx.settings;
@@ -158,7 +169,41 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.ctx.widgetActions = [ vm.searchAction ];
- vm.actionCellDescriptors = vm.ctx.actionsApi.getActionDescriptors('actionCellButton');
+ vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
+ vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
+ vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
+
+ if (vm.displayDetails) {
+ vm.actionCellDescriptors.push(
+ {
+ displayName: $translate.instant('alarm.details'),
+ icon: 'more_horiz',
+ details: true
+ }
+ );
+ }
+
+ if (vm.allowAcknowledgment) {
+ vm.actionCellDescriptors.push(
+ {
+ displayName: $translate.instant('alarm.acknowledge'),
+ icon: 'done',
+ acknowledge: true
+ }
+ );
+ }
+
+ if (vm.allowClear) {
+ vm.actionCellDescriptors.push(
+ {
+ displayName: $translate.instant('alarm.clear'),
+ icon: 'clear',
+ clear: true
+ }
+ );
+ }
+
+ vm.actionCellDescriptors = vm.actionCellDescriptors.concat(vm.ctx.actionsApi.getActionDescriptors('actionCellButton'));
if (vm.settings.alarmsTitle && vm.settings.alarmsTitle.length) {
vm.alarmsTitle = utils.customTranslation(vm.settings.alarmsTitle, vm.settings.alarmsTitle);
@@ -170,9 +215,6 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
vm.enableSelection = angular.isDefined(vm.settings.enableSelection) ? vm.settings.enableSelection : true;
vm.searchAction.show = angular.isDefined(vm.settings.enableSearch) ? vm.settings.enableSearch : true;
- vm.displayDetails = angular.isDefined(vm.settings.displayDetails) ? vm.settings.displayDetails : true;
- vm.allowAcknowledgment = angular.isDefined(vm.settings.allowAcknowledgment) ? vm.settings.allowAcknowledgment : true;
- vm.allowClear = angular.isDefined(vm.settings.allowClear) ? vm.settings.allowClear : true;
if (!vm.allowAcknowledgment && !vm.allowClear) {
vm.enableSelection = false;
}
@@ -305,16 +347,35 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
}
function onActionButtonClick($event, alarm, actionDescriptor) {
- if ($event) {
- $event.stopPropagation();
+ if (actionDescriptor.details) {
+ vm.openAlarmDetails($event, alarm);
+ } else if (actionDescriptor.acknowledge) {
+ vm.ackAlarm($event, alarm);
+ } else if (actionDescriptor.clear) {
+ vm.clearAlarm($event, alarm);
+ } else {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ var entityId;
+ var entityName;
+ if (alarm && alarm.originator) {
+ entityId = alarm.originator;
+ entityName = alarm.originatorName;
+ }
+ vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, {alarm: alarm});
}
- var entityId;
- var entityName;
- if (alarm && alarm.originator) {
- entityId = alarm.originator;
- entityName = alarm.originatorName;
+ }
+
+ function actionEnabled(alarm, actionDescriptor) {
+ if (actionDescriptor.acknowledge) {
+ return (alarm.status == types.alarmStatus.activeUnack ||
+ alarm.status == types.alarmStatus.clearedUnack);
+ } else if (actionDescriptor.clear) {
+ return (alarm.status == types.alarmStatus.activeAck ||
+ alarm.status == types.alarmStatus.activeUnack);
}
- vm.ctx.actionsApi.handleWidgetAction($event, actionDescriptor, entityId, entityName, { alarm: alarm });
+ return true;
}
function isCurrent(alarm) {
@@ -387,6 +448,25 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
}
}
+ function ackAlarm($event, alarm) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ var confirm = $mdDialog.confirm()
+ .targetEvent($event)
+ .title($translate.instant('alarm.aknowledge-alarm-title'))
+ .htmlContent($translate.instant('alarm.aknowledge-alarm-text'))
+ .ariaLabel($translate.instant('alarm.acknowledge'))
+ .cancel($translate.instant('action.no'))
+ .ok($translate.instant('action.yes'));
+ $mdDialog.show(confirm).then(function () {
+ alarmService.ackAlarm(alarm.id.id).then(function () {
+ vm.selectedAlarms = [];
+ vm.subscription.update();
+ });
+ });
+ }
+
function clearAlarms($event) {
if ($event) {
$event.stopPropagation();
@@ -420,6 +500,24 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
}
}
+ function clearAlarm($event, alarm) {
+ if ($event) {
+ $event.stopPropagation();
+ }
+ var confirm = $mdDialog.confirm()
+ .targetEvent($event)
+ .title($translate.instant('alarm.clear-alarm-title'))
+ .htmlContent($translate.instant('alarm.clear-alarm-text'))
+ .ariaLabel($translate.instant('alarm.clear'))
+ .cancel($translate.instant('action.no'))
+ .ok($translate.instant('action.yes'));
+ $mdDialog.show(confirm).then(function () {
+ alarmService.clearAlarm(alarm.id.id).then(function () {
+ vm.selectedAlarms = [];
+ vm.subscription.update();
+ });
+ });
+ }
function updateAlarms(preserveSelections) {
if (!preserveSelections) {
@@ -558,6 +656,54 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
}
}
+ function editAlarmStatusFilter($event) {
+ var element = angular.element($event.target);
+ var position = $mdPanel.newPanelPosition()
+ .relativeTo(element)
+ .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+ var config = {
+ attachTo: angular.element($document[0].body),
+ controller: AlarmStatusFilterPanelController,
+ controllerAs: 'vm',
+ templateUrl: alarmStatusFilterPanelTemplate,
+ panelClass: 'tb-alarm-status-filter-panel',
+ position: position,
+ fullscreen: false,
+ locals: {
+ 'subscription': vm.subscription
+ },
+ openFrom: $event,
+ clickOutsideToClose: true,
+ escapeToClose: true,
+ focusOnOpen: false
+ };
+ $mdPanel.open(config);
+ }
+
+ function editColumnsToDisplay($event) {
+ var element = angular.element($event.target);
+ var position = $mdPanel.newPanelPosition()
+ .relativeTo(element)
+ .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+ var config = {
+ attachTo: angular.element($document[0].body),
+ controller: DisplayColumnsPanelController,
+ controllerAs: 'vm',
+ templateUrl: displayColumnsPanelTemplate,
+ panelClass: 'tb-display-columns-panel',
+ position: position,
+ fullscreen: false,
+ locals: {
+ 'columns': vm.alarmSource.dataKeys
+ },
+ openFrom: $event,
+ clickOutsideToClose: true,
+ escapeToClose: true,
+ focusOnOpen: false
+ };
+ $mdPanel.open(config);
+ }
+
function updateAlarmSource() {
vm.ctx.widgetTitle = utils.createLabelFromDatasource(vm.alarmSource, vm.alarmsTitle);
@@ -570,6 +716,7 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
var dataKey = vm.alarmSource.dataKeys[d];
dataKey.title = utils.customTranslation(dataKey.label, dataKey.label);
+ dataKey.display = true;
var keySettings = dataKey.settings;
@@ -618,4 +765,19 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
}
}
-}
\ No newline at end of file
+}
+
+/*@ngInject*/
+function DisplayColumnsPanelController(columns) { //eslint-disable-line
+
+ var vm = this;
+ vm.columns = columns;
+}
+
+/*@ngInject*/
+function AlarmStatusFilterPanelController(subscription, types) { //eslint-disable-line
+
+ var vm = this;
+ vm.types = types;
+ vm.subscription = subscription;
+}
diff --git a/ui/src/app/widget/lib/alarms-table-widget.scss b/ui/src/app/widget/lib/alarms-table-widget.scss
index 6a71ccd..a031523 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,45 @@
}
&.tb-data-table {
- table.md-table, table.md-table.md-row-select {
+ table.md-table,
+ table.md-table.md-row-select {
+ th.md-column {
+ &.tb-action-cell {
+ .md-button {
+ /* stylelint-disable-next-line selector-max-class */
+ &.md-icon-button {
+ width: 36px;
+ height: 36px;
+ padding: 6px;
+ margin: 0;
+ /* stylelint-disable-next-line selector-max-class */
+ md-icon {
+ width: 24px;
+ height: 24px;
+ font-size: 24px !important;
+ line-height: 24px !important;
+ }
+ }
+ }
+ }
+ }
+
tbody {
tr {
td {
&.tb-action-cell {
+ width: 36px;
min-width: 36px;
max-width: 36px;
- width: 36px;
+
+ .md-button[disabled] {
+ &.md-icon-button {
+ /* stylelint-disable-next-line selector-max-class */
+ md-icon {
+ color: rgba(0, 0, 0, .38);
+ }
+ }
+ }
}
}
}
diff --git a/ui/src/app/widget/lib/alarms-table-widget.tpl.html b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
index 8480058..39843e1 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -62,33 +62,45 @@
<table md-table md-row-select="vm.enableSelection" multiple="" ng-model="vm.selectedAlarms">
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
- <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
- <th md-column ng-if="vm.displayDetails"><span> </span></th>
- <th md-column ng-if="vm.actionCellDescriptors.length"><span> </span></th>
+ <th ng-if="key.display" md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.alarmSource.dataKeys"><span>{{ key.title }}</span></th>
+ <th md-column class="tb-action-cell" layout="row" layout-align="end center">
+ <md-button class="md-icon-button"
+ aria-label="{{'alarm.alarm-status-filter' | translate}}"
+ ng-click="vm.editAlarmStatusFilter($event)">
+ <md-icon aria-label="{{'alarm.alarm-status-filter' | translate}}"
+ class="material-icons">filter_list
+ </md-icon>
+ <md-tooltip md-direction="top">
+ {{'alarm.alarm-status-filter' | translate}}
+ </md-tooltip>
+ </md-button>
+ <md-button class="md-icon-button"
+ aria-label="{{'entity.columns-to-display' | translate}}"
+ ng-click="vm.editColumnsToDisplay($event)">
+ <md-icon aria-label="{{'entity.columns-to-display' | translate}}"
+ class="material-icons">view_column
+ </md-icon>
+ <md-tooltip md-direction="top">
+ {{'entity.columns-to-display' | translate}}
+ </md-tooltip>
+ </md-button>
+ </th>
</tr>
</thead>
<tbody md-body>
<tr ng-show="vm.alarms.length" md-row md-select="alarm"
md-select-id="id.id" md-auto-select="false" ng-repeat="alarm in vm.alarms"
ng-click="vm.onRowClick($event, alarm)" ng-class="{'tb-current': vm.isCurrent(alarm)}">
- <td md-cell flex ng-repeat="key in vm.alarmSource.dataKeys"
+ <td ng-if="key.display" md-cell flex ng-repeat="key in vm.alarmSource.dataKeys"
ng-style="vm.cellStyle(alarm, key)"
ng-bind-html="vm.cellContent(alarm, key)">
</td>
- <td md-cell ng-if="vm.displayDetails" class="tb-action-cell">
- <md-button class="md-icon-button" aria-label="{{ 'alarm.details' | translate }}"
- ng-click="vm.openAlarmDetails($event, alarm)" ng-disabled="$root.loading">
- <md-icon aria-label="{{ 'alarm.details' | translate }}" class="material-icons">more_horiz</md-icon>
- <md-tooltip md-direction="top">
- {{ 'alarm.details' | translate }}
- </md-tooltip>
- </md-button>
- </td>
- <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
+ <td md-cell class="tb-action-cell"
ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
maxWidth: vm.actionCellDescriptors.length*36+'px',
width: vm.actionCellDescriptors.length*36+'px'}">
<md-button class="md-icon-button" ng-repeat="actionDescriptor in vm.actionCellDescriptors"
+ ng-disabled="!vm.actionEnabled(alarm, actionDescriptor)"
aria-label="{{ actionDescriptor.displayName }}"
ng-click="vm.onActionButtonClick($event, alarm, actionDescriptor)" ng-disabled="$root.loading">
<md-icon aria-label="{{ actionDescriptor.displayName }}" class="material-icons">{{actionDescriptor.icon}}</md-icon>
diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.scss b/ui/src/app/widget/lib/alarm-status-filter-panel.scss
new file mode 100644
index 0000000..4bf6eee
--- /dev/null
+++ b/ui/src/app/widget/lib/alarm-status-filter-panel.scss
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+.tb-alarm-status-filter-panel {
+ min-width: 300px;
+ overflow: hidden;
+ 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);
+
+ md-content {
+ overflow: hidden;
+ background-color: #fff;
+ }
+}
diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html
new file mode 100644
index 0000000..45e8414
--- /dev/null
+++ b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html
@@ -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.
+
+-->
+
+<md-content style="height: 100%" flex layout="column" class="md-padding">
+ <label class="tb-title" translate>alarm.alarm-status-filter</label>
+ <md-radio-group ng-model="vm.subscription.alarmSearchStatus" class="md-primary">
+ <md-radio-button ng-value="searchStatus"
+ aria-label="{{ ('alarm.search-status.' + searchStatus) | translate }}"
+ class="md-primary md-align-top-left md-radio-interactive" ng-repeat="searchStatus in vm.types.alarmSearchStatus">
+ {{ ('alarm.search-status.' + searchStatus) | translate }}
+ </md-radio-button>
+ </md-radio-group>
+</md-content>
diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js
index ee8b0ed..331ce6c 100644
--- a/ui/src/app/widget/lib/CanvasDigitalGauge.js
+++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js
@@ -209,7 +209,7 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
this.elementValueClone.renderedValue = this._value;
}
if (angular.isUndefined(this.elementValueClone.renderedValue)) {
- this.elementValueClone.renderedValue = options.minValue;
+ this.elementValueClone.renderedValue = this.value;
}
let context = this.contextValueClone;
// clear the cache
diff --git a/ui/src/app/widget/lib/display-columns-panel.scss b/ui/src/app/widget/lib/display-columns-panel.scss
new file mode 100644
index 0000000..2e518cb
--- /dev/null
+++ b/ui/src/app/widget/lib/display-columns-panel.scss
@@ -0,0 +1,31 @@
+/**
+ * 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.
+ */
+
+.tb-display-columns-panel {
+ min-width: 300px;
+ overflow: hidden;
+ 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);
+
+ md-content {
+ overflow: hidden;
+ background-color: #fff;
+ }
+}
diff --git a/ui/src/app/widget/lib/display-columns-panel.tpl.html b/ui/src/app/widget/lib/display-columns-panel.tpl.html
new file mode 100644
index 0000000..42e2471
--- /dev/null
+++ b/ui/src/app/widget/lib/display-columns-panel.tpl.html
@@ -0,0 +1,24 @@
+<!--
+
+ Copyright © 2016-2018 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<md-content style="height: 100%" flex layout="column" class="md-padding">
+ <label class="tb-title" translate>entity.columns-to-display</label>
+ <md-checkbox aria-label="{{ 'entity.columns-to-display' | translate }}" ng-repeat="column in vm.columns"
+ ng-model="column.display">{{ column.title }}
+ </md-checkbox>
+</md-content>
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index d0b629d..620f313 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/ui/src/app/widget/lib/entities-table-widget.js
@@ -14,11 +14,13 @@
* limitations under the License.
*/
import './entities-table-widget.scss';
+import './display-columns-panel.scss';
/* eslint-disable import/no-unresolved, import/default */
import entitiesTableWidgetTemplate from './entities-table-widget.tpl.html';
//import entityDetailsDialogTemplate from './entitiy-details-dialog.tpl.html';
+import displayColumnsPanelTemplate from './display-columns-panel.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
@@ -45,7 +47,7 @@ function EntitiesTableWidget() {
}
/*@ngInject*/
-function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, $timeout, utils, types) {
+function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $mdPanel, $document, $translate, $timeout, utils, types) {
var vm = this;
vm.stylesInfo = {};
@@ -98,6 +100,8 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.cellStyle = cellStyle;
vm.cellContent = cellContent;
+ vm.editColumnsToDisplay = editColumnsToDisplay;
+
$scope.$watch('vm.ctx', function() {
if (vm.ctx && vm.ctx.defaultSubscription) {
vm.settings = vm.ctx.settings;
@@ -414,12 +418,37 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
}
}
+ function editColumnsToDisplay($event) {
+ var element = angular.element($event.target);
+ var position = $mdPanel.newPanelPosition()
+ .relativeTo(element)
+ .addPanelPosition($mdPanel.xPosition.ALIGN_END, $mdPanel.yPosition.BELOW);
+ var config = {
+ attachTo: angular.element($document[0].body),
+ controller: DisplayColumnsPanelController,
+ controllerAs: 'vm',
+ templateUrl: displayColumnsPanelTemplate,
+ panelClass: 'tb-display-columns-panel',
+ position: position,
+ fullscreen: false,
+ locals: {
+ 'columns': vm.columns
+ },
+ openFrom: $event,
+ clickOutsideToClose: true,
+ escapeToClose: true,
+ focusOnOpen: false
+ };
+ $mdPanel.open(config);
+ }
+
function updateDatasources() {
vm.stylesInfo = {};
vm.contentsInfo = {};
vm.columnWidth = {};
vm.dataKeys = [];
+ vm.columns = [];
vm.allEntities = [];
var datasource;
@@ -429,6 +458,42 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
vm.ctx.widgetTitle = utils.createLabelFromDatasource(datasource, vm.entitiesTitle);
+ if (vm.displayEntityName) {
+ vm.columns.push(
+ {
+ name: 'entityName',
+ label: 'entityName',
+ title: vm.entityNameColumnTitle,
+ display: true
+ }
+ );
+ vm.contentsInfo['entityName'] = {
+ useCellContentFunction: false
+ };
+ vm.stylesInfo['entityName'] = {
+ useCellStyleFunction: false
+ };
+ vm.columnWidth['entityName'] = '0px';
+ }
+
+ if (vm.displayEntityType) {
+ vm.columns.push(
+ {
+ name: 'entityType',
+ label: 'entityType',
+ title: $translate.instant('entity.entity-type'),
+ display: true
+ }
+ );
+ vm.contentsInfo['entityType'] = {
+ useCellContentFunction: false
+ };
+ vm.stylesInfo['entityType'] = {
+ useCellStyleFunction: false
+ };
+ vm.columnWidth['entityType'] = '0px';
+ }
+
for (var d = 0; d < datasource.dataKeys.length; d++ ) {
dataKey = angular.copy(datasource.dataKeys[d]);
if (dataKey.type == types.dataKeyType.function) {
@@ -482,6 +547,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
var columnWidth = angular.isDefined(keySettings.columnWidth) ? keySettings.columnWidth : '0px';
vm.columnWidth[dataKey.label] = columnWidth;
+
+ dataKey.display = true;
+ vm.columns.push(dataKey);
}
for (var i=0;i<vm.datasources.length;i++) {
@@ -511,4 +579,11 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
}
-}
\ No newline at end of file
+}
+
+/*@ngInject*/
+function DisplayColumnsPanelController(columns) { //eslint-disable-line
+
+ var vm = this;
+ vm.columns = columns;
+}
diff --git a/ui/src/app/widget/lib/entities-table-widget.scss b/ui/src/app/widget/lib/entities-table-widget.scss
index 62a1f97..d745529 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,45 @@
}
&.tb-data-table {
- table.md-table, table.md-table.md-row-select {
+ table.md-table,
+ table.md-table.md-row-select {
+ th.md-column {
+ &.tb-action-cell {
+ .md-button {
+ /* stylelint-disable-next-line selector-max-class */
+ &.md-icon-button {
+ width: 36px;
+ height: 36px;
+ padding: 6px;
+ margin: 0;
+ /* stylelint-disable-next-line selector-max-class */
+ md-icon {
+ width: 24px;
+ height: 24px;
+ font-size: 24px !important;
+ line-height: 24px !important;
+ }
+ }
+ }
+ }
+ }
+
tbody {
tr {
td {
&.tb-action-cell {
+ width: 36px;
min-width: 36px;
max-width: 36px;
- width: 36px;
+
+ .md-button[disabled] {
+ &.md-icon-button {
+ /* stylelint-disable-next-line selector-max-class */
+ md-icon {
+ color: rgba(0, 0, 0, .38);
+ }
+ }
+ }
}
}
}
diff --git a/ui/src/app/widget/lib/entities-table-widget.tpl.html b/ui/src/app/widget/lib/entities-table-widget.tpl.html
index 474d536..66932a0 100644
--- a/ui/src/app/widget/lib/entities-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/entities-table-widget.tpl.html
@@ -41,23 +41,30 @@
<table md-table>
<thead md-head md-order="vm.query.order" md-on-reorder="vm.onReorder">
<tr md-row>
- <th md-column ng-if="vm.displayEntityName" md-order-by="entityName"><span>{{vm.entityNameColumnTitle}}</span></th>
- <th md-column ng-if="vm.displayEntityType" md-order-by="entityType"><span translate>entity.entity-type</span></th>
- <th md-column md-order-by="{{ key.name }}" ng-repeat="key in vm.dataKeys"><span>{{ key.title }}</span></th>
- <th md-column ng-if="vm.actionCellDescriptors.length"><span> </span></th>
+ <th ng-if="column.display" md-column md-order-by="{{ column.name }}" ng-repeat="column in vm.columns"><span>{{ column.title }}</span></th>
+ <th md-column class="tb-action-cell" layout="row" layout-align="end center">
+ <md-button class="md-icon-button"
+ aria-label="{{'entity.columns-to-display' | translate}}"
+ ng-click="vm.editColumnsToDisplay($event)">
+ <md-icon aria-label="{{'entity.columns-to-display' | translate}}"
+ class="material-icons">view_column
+ </md-icon>
+ <md-tooltip md-direction="top">
+ {{'entity.columns-to-display' | translate}}
+ </md-tooltip>
+ </md-button>
+ </th>
</tr>
</thead>
<tbody md-body>
<tr ng-show="vm.entities.length" md-row md-select="entity"
md-select-id="id.id" md-auto-select="false" ng-repeat="entity in vm.entities"
ng-click="vm.onRowClick($event, entity)" ng-class="{'tb-current': vm.isCurrent(entity)}">
- <td md-cell flex ng-if="vm.displayEntityName">{{entity.entityName}}</td>
- <td md-cell flex ng-if="vm.displayEntityType">{{entity.entityType}}</td>
- <td md-cell flex ng-repeat="key in vm.dataKeys"
- ng-style="vm.cellStyle(entity, key)"
- ng-bind-html="vm.cellContent(entity, key)">
+ <td ng-if="column.display" md-cell flex ng-repeat="column in vm.columns"
+ ng-style="vm.cellStyle(entity, column)"
+ ng-bind-html="vm.cellContent(entity, column)">
</td>
- <td md-cell ng-if="vm.actionCellDescriptors.length" class="tb-action-cell"
+ <td md-cell class="tb-action-cell"
ng-style="{minWidth: vm.actionCellDescriptors.length*36+'px',
maxWidth: vm.actionCellDescriptors.length*36+'px',
width: vm.actionCellDescriptors.length*36+'px'}">
diff --git a/ui/src/app/widget/lib/extensions-table-widget.scss b/ui/src/app/widget/lib/extensions-table-widget.scss
index f5f228d..42fcf6d 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
+}
ui/src/app/widget/lib/rpc/knob.scss 141(+78 -63)
diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss
index c41db43..bf2d3e0 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;
}
}
}
ui/src/app/widget/lib/rpc/led-indicator.scss 48(+26 -22)
diff --git a/ui/src/app/widget/lib/rpc/led-indicator.scss b/ui/src/app/widget/lib/rpc/led-indicator.scss
index 9b68c42..b1da5c6 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));
}
}
}
ui/src/app/widget/lib/rpc/round-switch.scss 214(+118 -96)
diff --git a/ui/src/app/widget/lib/rpc/round-switch.scss b/ui/src/app/widget/lib/rpc/round-switch.scss
index 9396abc..2e37f63 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));
}
-
}
}
}
ui/src/app/widget/lib/rpc/switch.scss 73(+42 -31)
diff --git a/ui/src/app/widget/lib/rpc/switch.scss b/ui/src/app/widget/lib/rpc/switch.scss
index dc0b8ee..53893a0 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 cd8f8a9..12fbf61 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;
+ }
}
ui/src/app/widget/widget-editor.scss 60(+32 -28)
diff --git a/ui/src/app/widget/widget-editor.scss b/ui/src/app/widget/widget-editor.scss
index 077f68c..cd374e3 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;
}
}
}
ui/src/scss/animations.scss 6(+5 -1)
diff --git a/ui/src/scss/animations.scss b/ui/src/scss/animations.scss
index 719b6f9..4ecfc34 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
+}
ui/src/scss/constants.scss 20(+10 -10)
diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss
index 5b6eab4..b462c66 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
ui/src/scss/fonts.scss 8(+4 -4)
diff --git a/ui/src/scss/fonts.scss b/ui/src/scss/fonts.scss
index 95089eb..6348d35 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");
+}
ui/src/scss/main.scss 309(+181 -128)
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 2852a7b..27c2d3b 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;
}
ui/src/scss/mixins.scss 26(+16 -10)
diff --git a/ui/src/scss/mixins.scss b/ui/src/scss/mixins.scss
index cb66171..a754538 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;
}
}
ui/webpack.config.dev.js 3(+3 -0)
diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js
index 242ad9d..cfff811 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', {