thingsboard-memoizeit
Changes
application/pom.xml 2(+1 -1)
application/src/main/java/org/thingsboard/server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java 48(+48 -0)
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java 38(+32 -6)
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java 8(+7 -1)
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java 2(+1 -1)
application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java 31(+27 -4)
application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java 2(+2 -0)
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java 2(+1 -1)
common/data/pom.xml 2(+1 -1)
common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java 6(+6 -0)
common/message/pom.xml 2(+1 -1)
common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleChainAwareMsg.java 24(+24 -0)
common/pom.xml 2(+1 -1)
common/transport/pom.xml 2(+1 -1)
dao/pom.xml 2(+1 -1)
docker/cassandra/Makefile 2(+1 -1)
docker/cassandra-setup/Makefile 2(+1 -1)
docker/cassandra-upgrade/Dockerfile 24(+24 -0)
docker/cassandra-upgrade/Makefile 12(+12 -0)
docker/cassandra-upgrade/upgrade.sh 28(+28 -0)
docker/docker-compose.yml 2(+1 -1)
docker/k8s/cassandra.yaml 2(+1 -1)
docker/k8s/cassandra-setup.yaml 2(+1 -1)
docker/k8s/cassandra-upgrade.yaml 43(+43 -0)
docker/k8s/tb.yaml 2(+1 -1)
docker/k8s/zookeeper.yaml 2(+1 -1)
docker/tb/Makefile 2(+1 -1)
docker/zookeeper/Makefile 2(+1 -1)
netty-mqtt/pom.xml 4(+2 -2)
pom.xml 2(+1 -1)
rule-engine/pom.xml 2(+1 -1)
rule-engine/rule-engine-api/pom.xml 2(+1 -1)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java 27(+20 -7)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java 86(+86 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeConfiguration.java 35(+35 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java 54(+54 -0)
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java 38(+38 -0)
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js 6(+3 -3)
tools/pom.xml 2(+1 -1)
transport/coap/pom.xml 2(+1 -1)
transport/http/pom.xml 2(+1 -1)
transport/mqtt/pom.xml 2(+1 -1)
transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java 3(+2 -1)
transport/pom.xml 2(+1 -1)
ui/package.json 22(+12 -10)
ui/pom.xml 2(+1 -1)
ui/src/app/api/alias-controller.js 2(+2 -0)
ui/src/app/api/dashboard.service.js 2(+1 -1)
ui/src/app/api/entity.service.js 2(+1 -1)
ui/src/app/api/rule-chain.service.js 26(+15 -11)
ui/src/app/api/time.service.js 163(+85 -78)
ui/src/app/api/user.service.js 7(+4 -3)
ui/src/app/app.config.js 76(+37 -39)
ui/src/app/app.js 4(+2 -2)
ui/src/app/app.run.js 7(+5 -2)
ui/src/app/common/types.constant.js 106(+106 -0)
ui/src/app/components/dashboard.scss 8(+4 -4)
ui/src/app/components/details-sidenav.scss 10(+0 -10)
ui/src/app/dashboard/dashboard.controller.js 11(+10 -1)
ui/src/app/dashboard/dashboard.routes.js 18(+18 -0)
ui/src/app/entity/entity-select.directive.js 21(+18 -3)
ui/src/app/locale/locale.constant-en_US.json 2922(+1459 -1463)
ui/src/app/locale/locale.constant-es_ES.json 2624(+1304 -1320)
ui/src/app/locale/locale.constant-it_IT.json 1445(+1445 -0)
ui/src/app/locale/locale.constant-ko_KR.json 2665(+1325 -1340)
ui/src/app/locale/locale.constant-ru_RU.json 2724(+1354 -1370)
ui/src/app/locale/locale.constant-zh_CN.json 2851(+1434 -1417)
ui/src/app/profile/profile.tpl.html 4(+2 -2)
ui/src/app/rulechain/index.js 2(+2 -0)
ui/src/app/rulechain/link.directive.js 117(+89 -28)
ui/src/app/rulechain/link.scss 30(+30 -0)
ui/src/app/rulechain/link-fieldset.tpl.html 57(+39 -18)
ui/src/app/rulechain/rulechain.controller.js 86(+62 -24)
ui/src/app/rulechain/rulechain.scss 4(+4 -0)
ui/src/app/services/item-buffer.service.js 11(+10 -1)
ui/src/scss/main.scss 7(+7 -0)
ui/src/scss/mixins.scss 27(+26 -1)
ui/webpack.config.dev.js 30(+27 -3)
ui/webpack.config.prod.js 38(+35 -3)
Details
application/pom.xml 2(+1 -1)
diff --git a/application/pom.xml b/application/pom.xml
index 7f11d0d..0e65c76 100644
--- a/application/pom.xml
+++ b/application/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>application</artifactId>
diff --git a/application/src/main/conf/thingsboard.conf b/application/src/main/conf/thingsboard.conf
index a569549..2a80158 100644
--- a/application/src/main/conf/thingsboard.conf
+++ b/application/src/main/conf/thingsboard.conf
@@ -15,7 +15,7 @@
#
export JAVA_OPTS="$JAVA_OPTS -Dplatform=@pkg.platform@ -Dinstall.data_dir=@pkg.installFolder@/data"
-export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
+export JAVA_OPTS="$JAVA_OPTS -Xloggc:@pkg.logFolder@/gc.log -XX:+IgnoreUnrecognizedVMOptions -XX:+HeapDumpOnOutOfMemoryError -XX:+PrintGCDetails -XX:+PrintGCDateStamps"
export JAVA_OPTS="$JAVA_OPTS -XX:+PrintHeapAtGC -XX:+PrintTenuringDistribution -XX:+PrintGCApplicationStoppedTime -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10"
export JAVA_OPTS="$JAVA_OPTS -XX:GCLogFileSize=10M -XX:-UseBiasedLocking -XX:+UseTLAB -XX:+ResizeTLAB -XX:+PerfDisableSharedMem -XX:+UseCondCardMark"
export JAVA_OPTS="$JAVA_OPTS -XX:CMSWaitDuration=10000 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:+CMSParallelRemarkEnabled -XX:+CMSParallelInitialMarkEnabled"
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 d453e59..8e7a9ae 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
@@ -111,6 +111,7 @@ public class AppActor extends RuleChainManagerActor {
case DEVICE_NAME_OR_TYPE_UPDATE_TO_DEVICE_ACTOR_MSG:
case DEVICE_RPC_REQUEST_TO_DEVICE_ACTOR_MSG:
case SERVER_RPC_RESPONSE_TO_DEVICE_ACTOR_MSG:
+ case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
onToDeviceActorMsg((TenantAwareMsg) msg);
break;
case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
diff --git a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
index 3f3f70b..9e38c17 100644
--- a/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/rpc/RpcManagerActor.java
@@ -29,11 +29,7 @@ import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.gen.cluster.ClusterAPIProtos;
import org.thingsboard.server.service.cluster.discovery.ServerInstance;
-import java.util.HashMap;
-import java.util.LinkedList;
-import java.util.Map;
-import java.util.Queue;
-import java.util.UUID;
+import java.util.*;
/**
* @author Andrew Shvayka
@@ -88,7 +84,17 @@ public class RpcManagerActor extends ContextAwareActor {
private void onMsg(RpcBroadcastMsg msg) {
log.debug("Forwarding msg to session actors {}", msg);
- sessionActors.keySet().forEach(address -> onMsg(msg.getMsg()));
+ sessionActors.keySet().forEach(address -> {
+ ClusterAPIProtos.ClusterMessage msgWithServerAddress = msg.getMsg()
+ .toBuilder()
+ .setServerAddress(ClusterAPIProtos.ServerAddress
+ .newBuilder()
+ .setHost(address.getHost())
+ .setPort(address.getPort())
+ .build())
+ .build();
+ onMsg(msgWithServerAddress);
+ });
pendingMsgs.values().forEach(queue -> queue.add(msg.getMsg()));
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java
new file mode 100644
index 0000000..fe0c2ef
--- /dev/null
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RemoteToRuleChainTellNextMsg.java
@@ -0,0 +1,48 @@
+/**
+ * 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.actors.ruleChain;
+
+import lombok.Data;
+import org.thingsboard.server.common.data.id.RuleChainId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.msg.MsgType;
+import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
+import org.thingsboard.server.common.msg.aware.TenantAwareMsg;
+
+import java.io.Serializable;
+
+/**
+ * Created by ashvayka on 19.03.18.
+ */
+@Data
+final class RemoteToRuleChainTellNextMsg extends RuleNodeToRuleChainTellNextMsg implements TenantAwareMsg, RuleChainAwareMsg, Serializable {
+
+ private static final long serialVersionUID = 2459605482321657447L;
+ private final TenantId tenantId;
+ private final RuleChainId ruleChainId;
+
+ public RemoteToRuleChainTellNextMsg(RuleNodeToRuleChainTellNextMsg original, TenantId tenantId, RuleChainId ruleChainId) {
+ super(original.getOriginator(), original.getRelationTypes(), original.getMsg());
+ this.tenantId = tenantId;
+ this.ruleChainId = ruleChainId;
+ }
+
+ @Override
+ public MsgType getMsgType() {
+ return MsgType.REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG;
+ }
+
+}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
index 3ba646a..c1a55fb 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActor.java
@@ -49,6 +49,7 @@ public class RuleChainActor extends ComponentActor<RuleChainId, RuleChainActorMe
processor.onDeviceActorToRuleEngineMsg((DeviceActorToRuleEngineMsg) msg);
break;
case RULE_TO_RULE_CHAIN_TELL_NEXT_MSG:
+ case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
processor.onTellNext((RuleNodeToRuleChainTellNextMsg) msg);
break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
index 7d560db..fe02335 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
@@ -20,6 +20,9 @@ import akka.actor.ActorRef;
import akka.actor.Props;
import akka.event.LoggingAdapter;
import com.datastax.driver.core.utils.UUIDs;
+
+import java.util.Optional;
+
import org.thingsboard.server.actors.ActorSystemContext;
import org.thingsboard.server.actors.device.DeviceActorToRuleEngineMsg;
import org.thingsboard.server.actors.device.RuleEngineQueuePutAckMsg;
@@ -37,6 +40,7 @@ import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.data.rule.RuleNode;
import org.thingsboard.server.common.msg.TbMsg;
import org.thingsboard.server.common.msg.cluster.ClusterEventMsg;
+import org.thingsboard.server.common.msg.cluster.ServerAddress;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import org.thingsboard.server.dao.rule.RuleChainService;
@@ -217,16 +221,36 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
void onTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
checkActive();
- RuleNodeId originator = envelope.getOriginator();
- List<RuleNodeRelation> relations = nodeRoutes.get(originator).stream()
- .filter(r -> contains(envelope.getRelationTypes(), r.getType()))
- .collect(Collectors.toList());
+ TbMsg msg = envelope.getMsg();
+ EntityId originatorEntityId = msg.getOriginator();
+ Optional<ServerAddress> address = systemContext.getRoutingService().resolveById(originatorEntityId);
+
+ if (address.isPresent()) {
+ onRemoteTellNext(address.get(), envelope);
+ } else {
+ onLocalTellNext(envelope);
+ }
+ }
+
+ private void onRemoteTellNext(ServerAddress serverAddress, RuleNodeToRuleChainTellNextMsg envelope) {
+ TbMsg msg = envelope.getMsg();
+ logger.debug("Forwarding [{}] msg to remote server [{}] due to changed originator id: [{}]", msg.getId(), serverAddress, msg.getOriginator());
+ envelope = new RemoteToRuleChainTellNextMsg(envelope, tenantId, entityId);
+ systemContext.getRpcService().tell(systemContext.getEncodingService().convertToProtoDataMessage(serverAddress, envelope));
+ }
+ private void onLocalTellNext(RuleNodeToRuleChainTellNextMsg envelope) {
TbMsg msg = envelope.getMsg();
+ RuleNodeId originatorNodeId = envelope.getOriginator();
+ List<RuleNodeRelation> relations = nodeRoutes.get(originatorNodeId).stream()
+ .filter(r -> contains(envelope.getRelationTypes(), r.getType()))
+ .collect(Collectors.toList());
int relationsCount = relations.size();
EntityId ackId = msg.getRuleNodeId() != null ? msg.getRuleNodeId() : msg.getRuleChainId();
if (relationsCount == 0) {
- queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
+ if (ackId != null) {
+ queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
+ }
} else if (relationsCount == 1) {
for (RuleNodeRelation relation : relations) {
pushToTarget(msg, relation.getOut(), relation.getType());
@@ -244,7 +268,9 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
}
}
//TODO: Ideally this should happen in async way when all targets confirm that the copied messages are successfully written to corresponding target queues.
- queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
+ if (ackId != null) {
+ queue.ack(tenantId, msg, ackId.getId(), msg.getClusterPartition());
+ }
}
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
index 8b13747..ae4ae45 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainToRuleChainMsg.java
@@ -20,12 +20,13 @@ import org.thingsboard.server.common.data.id.RuleChainId;
import org.thingsboard.server.common.msg.MsgType;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
/**
* Created by ashvayka on 19.03.18.
*/
@Data
-public final class RuleChainToRuleChainMsg implements TbActorMsg {
+public final class RuleChainToRuleChainMsg implements TbActorMsg, RuleChainAwareMsg {
private final RuleChainId target;
private final RuleChainId source;
@@ -34,6 +35,11 @@ public final class RuleChainToRuleChainMsg implements TbActorMsg {
private final boolean enqueue;
@Override
+ public RuleChainId getRuleChainId() {
+ return target;
+ }
+
+ @Override
public MsgType getMsgType() {
return MsgType.RULE_CHAIN_TO_RULE_CHAIN_MSG;
}
diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java
index c0a475c..9414892 100644
--- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java
+++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeToRuleChainTellNextMsg.java
@@ -27,7 +27,7 @@ import java.util.Set;
* Created by ashvayka on 19.03.18.
*/
@Data
-final class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
+class RuleNodeToRuleChainTellNextMsg implements TbActorMsg {
private final RuleNodeId originator;
private final Set<String> relationTypes;
diff --git a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
index 7a3127d..b4ab0d2 100644
--- a/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
+++ b/application/src/main/java/org/thingsboard/server/actors/tenant/TenantActor.java
@@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.rule.RuleChain;
import org.thingsboard.server.common.msg.TbActorMsg;
import org.thingsboard.server.common.msg.aware.DeviceAwareMsg;
+import org.thingsboard.server.common.msg.aware.RuleChainAwareMsg;
import org.thingsboard.server.common.msg.plugin.ComponentLifecycleMsg;
import org.thingsboard.server.common.msg.system.ServiceToRuleEngineMsg;
import scala.concurrent.duration.Duration;
@@ -94,7 +95,8 @@ public class TenantActor extends RuleChainManagerActor {
onToDeviceActorMsg((DeviceAwareMsg) msg);
break;
case RULE_CHAIN_TO_RULE_CHAIN_MSG:
- onRuleChainMsg((RuleChainToRuleChainMsg) msg);
+ case REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG:
+ onRuleChainMsg((RuleChainAwareMsg) msg);
break;
default:
return false;
@@ -109,15 +111,19 @@ public class TenantActor extends RuleChainManagerActor {
}
private void onServiceToRuleEngineMsg(ServiceToRuleEngineMsg msg) {
+ if (ruleChainManager.getRootChainActor()!=null)
ruleChainManager.getRootChainActor().tell(msg, self());
+ else logger.info("[{}] No Root Chain", msg);
}
private void onDeviceActorToRuleEngineMsg(DeviceActorToRuleEngineMsg msg) {
+ if (ruleChainManager.getRootChainActor()!=null)
ruleChainManager.getRootChainActor().tell(msg, self());
+ else logger.info("[{}] No Root Chain", msg);
}
- private void onRuleChainMsg(RuleChainToRuleChainMsg msg) {
- ruleChainManager.getOrCreateActor(context(), msg.getTarget()).tell(msg, self());
+ private void onRuleChainMsg(RuleChainAwareMsg msg) {
+ ruleChainManager.getOrCreateActor(context(), msg.getRuleChainId()).tell(msg, self());
}
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 28cc2fd..51ef566 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java
@@ -26,6 +26,7 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@@ -33,6 +34,7 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery;
import org.thingsboard.server.common.data.alarm.AlarmSearchStatus;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
+import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
@@ -53,7 +55,6 @@ public class AlarmController extends BaseController {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
-
return checkAlarmId(alarmId);
} catch (Exception e) {
throw handleException(e);
@@ -79,8 +80,14 @@ public class AlarmController extends BaseController {
public Alarm saveAlarm(@RequestBody Alarm alarm) throws ThingsboardException {
try {
alarm.setTenantId(getCurrentUser().getTenantId());
- return checkNotNull(alarmService.createOrUpdateAlarm(alarm));
+ Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm));
+ logEntityAction(savedAlarm.getId(), savedAlarm,
+ getCurrentUser().getCustomerId(),
+ savedAlarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, null);
+ return savedAlarm;
} catch (Exception e) {
+ logEntityAction(emptyId(EntityType.ASSET), alarm,
+ null, alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED, e);
throw handleException(e);
}
}
@@ -92,8 +99,9 @@ public class AlarmController extends BaseController {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
- checkAlarmId(alarmId);
+ Alarm alarm = checkAlarmId(alarmId);
alarmService.ackAlarm(alarmId, System.currentTimeMillis()).get();
+ logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_ACK, null);
} catch (Exception e) {
throw handleException(e);
}
@@ -106,8 +114,9 @@ public class AlarmController extends BaseController {
checkParameter(ALARM_ID, strAlarmId);
try {
AlarmId alarmId = new AlarmId(toUUID(strAlarmId));
- checkAlarmId(alarmId);
+ Alarm alarm = checkAlarmId(alarmId);
alarmService.clearAlarm(alarmId, null, System.currentTimeMillis()).get();
+ logEntityAction(alarmId, alarm, getCurrentUser().getCustomerId(), ActionType.ALARM_CLEAR, null);
} catch (Exception e) {
throw handleException(e);
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
index f044228..de73fe0 100644
--- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java
@@ -529,18 +529,16 @@ public abstract class BaseController {
return baseUrl;
}
- protected <I extends UUIDBased & EntityId> I emptyId(EntityType entityType) {
+ protected <I extends EntityId> I emptyId(EntityType entityType) {
return (I)EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID);
}
- protected <E extends BaseData<I> & HasName,
- I extends UUIDBased & EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
+ protected <E extends HasName, I extends EntityId> void logEntityAction(I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
logEntityAction(getCurrentUser(), entityId, entity, customerId, actionType, e, additionalInfo);
}
- protected <E extends BaseData<I> & HasName,
- I extends UUIDBased & EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
+ protected <E extends HasName, I extends EntityId> void logEntityAction(User user, I entityId, E entity, CustomerId customerId,
ActionType actionType, Exception e, Object... additionalInfo) throws ThingsboardException {
if (customerId == null || customerId.isNullUid()) {
customerId = user.getCustomerId();
@@ -556,8 +554,7 @@ public abstract class BaseController {
return error != null ? (Exception.class.isInstance(error) ? (Exception) error : new Exception(error)) : null;
}
- private <E extends BaseData<I> & HasName,
- I extends UUIDBased & EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
+ private <E extends HasName, I extends EntityId> void pushEntityActionToRuleEngine(I entityId, E entity, User user, CustomerId customerId,
ActionType actionType, Object... additionalInfo) {
String msgType = null;
switch (actionType) {
diff --git a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
index 844dbd3..70dd8a6 100644
--- a/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/EntityRelationController.java
@@ -24,10 +24,13 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
+import org.thingsboard.server.common.data.alarm.Alarm;
+import org.thingsboard.server.common.data.audit.ActionType;
import org.thingsboard.server.common.data.exception.ThingsboardErrorCode;
import org.thingsboard.server.common.data.exception.ThingsboardException;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityIdFactory;
+import org.thingsboard.server.common.data.id.UUIDBased;
import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.relation.EntityRelationInfo;
import org.thingsboard.server.common.data.relation.EntityRelationsQuery;
@@ -58,7 +61,15 @@ public class EntityRelationController extends BaseController {
relation.setTypeGroup(RelationTypeGroup.COMMON);
}
relationService.saveRelation(relation);
+ logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_ADD_OR_UPDATE, null, relation);
+ logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_ADD_OR_UPDATE, null, relation);
} catch (Exception e) {
+ logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_ADD_OR_UPDATE, e, relation);
+ logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_ADD_OR_UPDATE, e, relation);
throw handleException(e);
}
}
@@ -81,12 +92,21 @@ public class EntityRelationController extends BaseController {
checkEntityId(fromId);
checkEntityId(toId);
RelationTypeGroup relationTypeGroup = parseRelationTypeGroup(strRelationTypeGroup, RelationTypeGroup.COMMON);
+ EntityRelation relation = new EntityRelation(fromId, toId, strRelationType, relationTypeGroup);
try {
Boolean found = relationService.deleteRelation(fromId, toId, strRelationType, relationTypeGroup);
if (!found) {
throw new ThingsboardException("Requested item wasn't found!", ThingsboardErrorCode.ITEM_NOT_FOUND);
}
+ logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_DELETED, null, relation);
+ logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_DELETED, null, relation);
} catch (Exception e) {
+ logEntityAction(relation.getFrom(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_DELETED, e, relation);
+ logEntityAction(relation.getTo(), null, getCurrentUser().getCustomerId(),
+ ActionType.RELATION_DELETED, e, relation);
throw handleException(e);
}
}
@@ -102,7 +122,9 @@ public class EntityRelationController extends BaseController {
checkEntityId(entityId);
try {
relationService.deleteEntityRelations(entityId);
+ logEntityAction(entityId, null, getCurrentUser().getCustomerId(), ActionType.RELATIONS_DELETED, null);
} catch (Exception e) {
+ logEntityAction(entityId, null, getCurrentUser().getCustomerId(), ActionType.RELATIONS_DELETED, e);
throw handleException(e);
}
}
@@ -210,8 +232,8 @@ public class EntityRelationController extends BaseController {
@RequestMapping(value = "/relations/info", method = RequestMethod.GET, params = {TO_ID, TO_TYPE})
@ResponseBody
public List<EntityRelationInfo> findInfoByTo(@RequestParam(TO_ID) String strToId,
- @RequestParam(TO_TYPE) String strToType,
- @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
+ @RequestParam(TO_TYPE) String strToType,
+ @RequestParam(value = "relationTypeGroup", required = false) String strRelationTypeGroup) throws ThingsboardException {
checkParameter(TO_ID, strToId);
checkParameter(TO_TYPE, strToType);
EntityId entityId = EntityIdFactory.getByTypeAndId(strToType, strToId);
@@ -276,10 +298,11 @@ public class EntityRelationController extends BaseController {
private RelationTypeGroup parseRelationTypeGroup(String strRelationTypeGroup, RelationTypeGroup defaultValue) {
RelationTypeGroup result = defaultValue;
- if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length()>0) {
+ if (strRelationTypeGroup != null && strRelationTypeGroup.trim().length() > 0) {
try {
result = RelationTypeGroup.valueOf(strRelationTypeGroup);
- } catch (IllegalArgumentException e) { }
+ } catch (IllegalArgumentException e) {
+ }
}
return result;
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
index b771ee2..136bbfd 100644
--- a/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
+++ b/application/src/main/java/org/thingsboard/server/controller/plugin/TbWebSocketHandler.java
@@ -46,7 +46,6 @@ public class TbWebSocketHandler extends TextWebSocketHandler implements Telemetr
private static final ConcurrentMap<String, String> externalSessionMap = new ConcurrentHashMap<>();
@Autowired
- @Lazy
private TelemetryWebSocketService webSocketService;
@Override
diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
index 8e86661..64f5213 100644
--- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
+++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultDeviceRpcService.java
@@ -22,6 +22,7 @@ import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.protobuf.InvalidProtocolBufferException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.rule.engine.api.msg.ToDeviceActorNotificationMsg;
@@ -67,6 +68,7 @@ public class DefaultDeviceRpcService implements DeviceRpcService {
private ClusterRpcService rpcService;
@Autowired
+ @Lazy
private ActorService actorService;
private ScheduledExecutorService rpcCallBackExecutor;
diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
index a41fdee..f4e37db 100644
--- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
+++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java
@@ -28,6 +28,7 @@ import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.thingsboard.rule.engine.api.RpcError;
import org.thingsboard.server.actors.service.ActorService;
@@ -102,6 +103,7 @@ public class DefaultDeviceStateService implements DeviceStateService {
private AttributesService attributesService;
@Autowired
+ @Lazy
private ActorService actorService;
@Autowired
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 ef1606a..263b484 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
@@ -432,7 +432,7 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
deviceSubscriptions.stream().filter(filter).forEach(s -> {
String sessionId = s.getWsSessionId();
List<TsKvEntry> subscriptionUpdate = f.apply(s);
- if (subscriptionUpdate == null || !subscriptionUpdate.isEmpty()) {
+ if (subscriptionUpdate != null && !subscriptionUpdate.isEmpty()) {
SubscriptionUpdate update = new SubscriptionUpdate(s.getSubscriptionId(), subscriptionUpdate);
if (s.isLocal()) {
updateSubscriptionState(sessionId, s, update);
diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml
index 5653193..9f6192b 100644
--- a/application/src/main/resources/thingsboard.yml
+++ b/application/src/main/resources/thingsboard.yml
@@ -225,7 +225,7 @@ sql:
# Actor system parameters
actors:
tenant:
- create_components_on_init: true
+ create_components_on_init: "${ACTORS_TENANT_CREATE_COMPONENTS_ON_INIT:true}"
session:
max_concurrent_sessions_per_device: "${ACTORS_MAX_CONCURRENT_SESSION_PER_DEVICE:1}"
sync:
@@ -328,6 +328,13 @@ spring.mvc.cors:
max-age: "1800"
allow-credentials: "true"
+# spring serve gzip compressed static resources
+spring.resources.chain:
+ gzipped: "true"
+ strategy:
+ content:
+ enabled: "true"
+
# HSQLDB DAO Configuration
spring:
data:
@@ -378,6 +385,7 @@ audit_log:
"customer": "${AUDIT_LOG_MASK_CUSTOMER:W}"
"user": "${AUDIT_LOG_MASK_USER:W}"
"rule_chain": "${AUDIT_LOG_MASK_RULE_CHAIN:W}"
+ "alarm": "${AUDIT_LOG_MASK_ALARM:W}"
sink:
# Type of external sink. possible options: none, elasticsearch
type: "${AUDIT_LOG_SINK_TYPE:none}"
diff --git a/application/src/main/scripts/install/install_dev_db.sh b/application/src/main/scripts/install/install_dev_db.sh
index ba93479..212a2ac 100644
--- a/application/src/main/scripts/install/install_dev_db.sh
+++ b/application/src/main/scripts/install/install_dev_db.sh
@@ -30,13 +30,13 @@ export SQL_DATA_FOLDER=${SQL_DATA_FOLDER:-/tmp}
run_user=thingsboard
-su -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
+sudo -u "$run_user" -s /bin/sh -c "java -cp ${jarfile} $JAVA_OPTS -Dloader.main=org.thingsboard.server.ThingsboardInstallApplication \
-Dinstall.data_dir=${installDir} \
-Dinstall.load_demo=${loadDemo} \
-Dspring.jpa.hibernate.ddl-auto=none \
-Dinstall.upgrade=false \
-Dlogging.config=logback.xml \
- org.springframework.boot.loader.PropertiesLauncher" "$run_user"
+ org.springframework.boot.loader.PropertiesLauncher"
if [ $? -ne 0 ]; then
echo "ThingsBoard DB installation failed!"
common/data/pom.xml 2(+1 -1)
diff --git a/common/data/pom.xml b/common/data/pom.xml
index 7953b48..1cbd1b5 100644
--- a/common/data/pom.xml
+++ b/common/data/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
index ea442f0..c37d460 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/audit/ActionType.java
@@ -31,7 +31,12 @@ public enum ActionType {
ACTIVATED(false), // log string id
SUSPENDED(false), // log string id
CREDENTIALS_READ(true), // log device id
- ATTRIBUTES_READ(true); // log attributes
+ ATTRIBUTES_READ(true), // log attributes
+ RELATION_ADD_OR_UPDATE (false),
+ RELATION_DELETED (false),
+ RELATIONS_DELETED (false),
+ ALARM_ACK (false),
+ ALARM_CLEAR (false);
private final boolean isRead;
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
index 0ecc7c6..ed4cf2f 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java
@@ -60,4 +60,5 @@ public class EntityIdFactory {
}
throw new IllegalArgumentException("EntityType " + type + " is not supported!");
}
+
}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
index 2e976f2..beccc04 100644
--- a/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/relation/RelationsSearchParameters.java
@@ -33,13 +33,19 @@ public class RelationsSearchParameters {
private UUID rootId;
private EntityType rootType;
private EntitySearchDirection direction;
+ private RelationTypeGroup relationTypeGroup;
private int maxLevel = 1;
public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel) {
+ this(entityId, direction, maxLevel, RelationTypeGroup.COMMON);
+ }
+
+ public RelationsSearchParameters(EntityId entityId, EntitySearchDirection direction, int maxLevel, RelationTypeGroup relationTypeGroup) {
this.rootId = entityId.getId();
this.rootType = entityId.getEntityType();
this.direction = direction;
this.maxLevel = maxLevel;
+ this.relationTypeGroup = relationTypeGroup;
}
public EntityId getEntityId() {
common/message/pom.xml 2(+1 -1)
diff --git a/common/message/pom.xml b/common/message/pom.xml
index 91e617e..e1af6d1 100644
--- a/common/message/pom.xml
+++ b/common/message/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleChainAwareMsg.java b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleChainAwareMsg.java
new file mode 100644
index 0000000..e261cbb
--- /dev/null
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/aware/RuleChainAwareMsg.java
@@ -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.
+ */
+package org.thingsboard.server.common.msg.aware;
+
+import org.thingsboard.server.common.data.id.RuleChainId;
+
+public interface RuleChainAwareMsg {
+
+ RuleChainId getRuleChainId();
+
+}
diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
index 7702788..c8b5c4e 100644
--- a/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
+++ b/common/message/src/main/java/org/thingsboard/server/common/msg/MsgType.java
@@ -63,6 +63,11 @@ public enum MsgType {
RULE_TO_RULE_CHAIN_TELL_NEXT_MSG,
/**
+ * Message forwarded from original rule chain to remote rule chain due to change in the cluster structure or originator entity of the TbMsg.
+ */
+ REMOTE_TO_RULE_CHAIN_TELL_NEXT_MSG,
+
+ /**
* Message that is sent by RuleActor implementation to RuleActor itself to log the error.
*/
RULE_TO_SELF_ERROR_MSG,
@@ -101,6 +106,10 @@ public enum MsgType {
/**
* Message that is sent from Rule Engine to the Device Actor when message is successfully pushed to queue.
*/
- RULE_ENGINE_QUEUE_PUT_ACK_MSG, ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG, TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG, SESSION_TIMEOUT_MSG, SESSION_CTRL_MSG;
+ RULE_ENGINE_QUEUE_PUT_ACK_MSG,
+ ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG,
+ TRANSPORT_TO_DEVICE_SESSION_ACTOR_MSG,
+ SESSION_TIMEOUT_MSG,
+ SESSION_CTRL_MSG;
}
common/pom.xml 2(+1 -1)
diff --git a/common/pom.xml b/common/pom.xml
index 4f555e0..8c2416d 100644
--- a/common/pom.xml
+++ b/common/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
common/transport/pom.xml 2(+1 -1)
diff --git a/common/transport/pom.xml b/common/transport/pom.xml
index b7a4255..07c4d34 100644
--- a/common/transport/pom.xml
+++ b/common/transport/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>common</artifactId>
</parent>
<groupId>org.thingsboard.common</groupId>
dao/pom.xml 2(+1 -1)
diff --git a/dao/pom.xml b/dao/pom.xml
index f6050d1..00173bd 100644
--- a/dao/pom.xml
+++ b/dao/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>dao</artifactId>
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
index d2e6f05..b9db338 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogService.java
@@ -40,15 +40,14 @@ public interface AuditLogService {
TimePageData<AuditLog> findAuditLogsByTenantId(TenantId tenantId, TimePageLink pageLink);
- <E extends BaseData<I> & HasName,
- I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(
- TenantId tenantId,
- CustomerId customerId,
- UserId userId,
- String userName,
- I entityId,
- E entity,
- ActionType actionType,
- Exception e, Object... additionalInfo);
+ <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(
+ TenantId tenantId,
+ CustomerId customerId,
+ UserId userId,
+ String userName,
+ I entityId,
+ E entity,
+ ActionType actionType,
+ Exception e, Object... additionalInfo);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
index a30c1b4..ecb2bd5 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java
@@ -43,6 +43,7 @@ import org.thingsboard.server.common.data.id.UserId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
import org.thingsboard.server.common.data.page.TimePageData;
import org.thingsboard.server.common.data.page.TimePageLink;
+import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.common.data.rule.RuleChainMetaData;
import org.thingsboard.server.common.data.security.DeviceCredentials;
import org.thingsboard.server.dao.audit.sink.AuditLogSink;
@@ -115,7 +116,7 @@ public class AuditLogServiceImpl implements AuditLogService {
}
@Override
- public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>>
+ public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>>
logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity,
ActionType actionType, Exception e, Object... additionalInfo) {
if (canLog(entityId.getEntityType(), actionType)) {
@@ -156,14 +157,16 @@ public class AuditLogServiceImpl implements AuditLogService {
}
}
- private <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> JsonNode constructActionData(I entityId,
- E entity,
+ private <E extends HasName, I extends EntityId> JsonNode constructActionData(I entityId, E entity,
ActionType actionType,
Object... additionalInfo) {
ObjectNode actionData = objectMapper.createObjectNode();
switch(actionType) {
case ADDED:
case UPDATED:
+ case ALARM_ACK:
+ case ALARM_CLEAR:
+ case RELATIONS_DELETED:
if (entity != null) {
ObjectNode entityNode = objectMapper.valueToTree(entity);
if (entityId.getEntityType() == EntityType.DASHBOARD) {
@@ -240,6 +243,11 @@ public class AuditLogServiceImpl implements AuditLogService {
actionData.put("unassignedCustomerId", strCustomerId);
actionData.put("unassignedCustomerName", strCustomerName);
break;
+ case RELATION_ADD_OR_UPDATE:
+ case RELATION_DELETED:
+ EntityRelation relation = extractParameter(EntityRelation.class, 0, additionalInfo);
+ actionData.set("relation", objectMapper.valueToTree(relation));
+ break;
}
return actionData;
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
index b14d6b1..6f9ea36 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/audit/DummyAuditLogServiceImpl.java
@@ -57,7 +57,7 @@ public class DummyAuditLogServiceImpl implements AuditLogService {
}
@Override
- public <E extends BaseData<I> & HasName, I extends UUIDBased & EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) {
+ public <E extends HasName, I extends EntityId> ListenableFuture<List<Void>> logEntityAction(TenantId tenantId, CustomerId customerId, UserId userId, String userName, I entityId, E entity, ActionType actionType, Exception e, Object... additionalInfo) {
return null;
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java b/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java
index 2dc624f..688dfb3 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/cache/CaffeineCacheConfiguration.java
@@ -16,8 +16,10 @@
package org.thingsboard.server.dao.cache;
import com.github.benmanes.caffeine.cache.Caffeine;
+import com.github.benmanes.caffeine.cache.RemovalCause;
import com.github.benmanes.caffeine.cache.Ticker;
import lombok.Data;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.cache.CacheManager;
@@ -28,6 +30,7 @@ import org.springframework.cache.support.SimpleCacheManager;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@@ -38,12 +41,14 @@ import java.util.stream.Collectors;
@ConfigurationProperties(prefix = "caffeine")
@EnableCaching
@Data
+@Slf4j
public class CaffeineCacheConfiguration {
private Map<String, CacheSpecs> specs;
@Bean
public CacheManager cacheManager() {
+ log.trace("Initializing cache: {}", Arrays.toString(RemovalCause.values()));
SimpleCacheManager manager = new SimpleCacheManager();
if (specs != null) {
List<CaffeineCache> caches =
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 ea29f6f..9b89fe2 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
@@ -402,7 +402,7 @@ public class BaseRelationService implements RelationService {
int maxLvl = params.getMaxLevel() > 0 ? params.getMaxLevel() : Integer.MAX_VALUE;
try {
- ListenableFuture<Set<EntityRelation>> relationSet = findRelationsRecursively(params.getEntityId(), params.getDirection(), maxLvl, new ConcurrentHashMap<>());
+ ListenableFuture<Set<EntityRelation>> relationSet = findRelationsRecursively(params.getEntityId(), params.getDirection(), params.getRelationTypeGroup(), maxLvl, new ConcurrentHashMap<>());
return Futures.transform(relationSet, input -> {
List<EntityRelation> relations = new ArrayList<>();
if (filters == null || filters.isEmpty()) {
@@ -518,14 +518,15 @@ public class BaseRelationService implements RelationService {
}
}
- private ListenableFuture<Set<EntityRelation>> findRelationsRecursively(final EntityId rootId, final EntitySearchDirection direction, int lvl,
+ private ListenableFuture<Set<EntityRelation>> findRelationsRecursively(final EntityId rootId, final EntitySearchDirection direction,
+ RelationTypeGroup relationTypeGroup, int lvl,
final ConcurrentHashMap<EntityId, Boolean> uniqueMap) throws Exception {
if (lvl == 0) {
return Futures.immediateFuture(Collections.emptySet());
}
lvl--;
//TODO: try to remove this blocking operation
- Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction).get());
+ Set<EntityRelation> children = new HashSet<>(findRelations(rootId, direction, relationTypeGroup).get());
Set<EntityId> childrenIds = new HashSet<>();
for (EntityRelation childRelation : children) {
log.trace("Found Relation: {}", childRelation);
@@ -544,7 +545,7 @@ public class BaseRelationService implements RelationService {
}
List<ListenableFuture<Set<EntityRelation>>> futures = new ArrayList<>();
for (EntityId entityId : childrenIds) {
- futures.add(findRelationsRecursively(entityId, direction, lvl, uniqueMap));
+ futures.add(findRelationsRecursively(entityId, direction, relationTypeGroup, lvl, uniqueMap));
}
//TODO: try to remove this blocking operation
List<Set<EntityRelation>> relations = Futures.successfulAsList(futures).get();
@@ -552,12 +553,15 @@ public class BaseRelationService implements RelationService {
return Futures.immediateFuture(children);
}
- private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction) {
+ private ListenableFuture<List<EntityRelation>> findRelations(final EntityId rootId, final EntitySearchDirection direction, RelationTypeGroup relationTypeGroup) {
ListenableFuture<List<EntityRelation>> relations;
+ if (relationTypeGroup == null) {
+ relationTypeGroup = RelationTypeGroup.COMMON;
+ }
if (direction == EntitySearchDirection.FROM) {
- relations = findByFromAsync(rootId, RelationTypeGroup.COMMON);
+ relations = findByFromAsync(rootId, relationTypeGroup);
} else {
- relations = findByToAsync(rootId, RelationTypeGroup.COMMON);
+ relations = findByToAsync(rootId, relationTypeGroup);
}
return relations;
}
docker/cassandra/Makefile 2(+1 -1)
diff --git a/docker/cassandra/Makefile b/docker/cassandra/Makefile
index 29941f5..d1fb677 100644
--- a/docker/cassandra/Makefile
+++ b/docker/cassandra/Makefile
@@ -1,4 +1,4 @@
-VERSION=2.0.3
+VERSION=2.1.0
PROJECT=thingsboard
APP=cassandra
docker/cassandra-setup/Makefile 2(+1 -1)
diff --git a/docker/cassandra-setup/Makefile b/docker/cassandra-setup/Makefile
index e0bb541..7990a3e 100644
--- a/docker/cassandra-setup/Makefile
+++ b/docker/cassandra-setup/Makefile
@@ -1,4 +1,4 @@
-VERSION=2.0.3
+VERSION=2.1.0
PROJECT=thingsboard
APP=cassandra-setup
docker/cassandra-upgrade/Dockerfile 24(+24 -0)
diff --git a/docker/cassandra-upgrade/Dockerfile b/docker/cassandra-upgrade/Dockerfile
new file mode 100644
index 0000000..312db0d
--- /dev/null
+++ b/docker/cassandra-upgrade/Dockerfile
@@ -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.
+#
+
+FROM openjdk:8-jre
+
+ADD upgrade.sh /upgrade.sh
+ADD thingsboard.deb /thingsboard.deb
+
+RUN apt-get update \
+ && apt-get install -y nmap \
+ && chmod +x /upgrade.sh
docker/cassandra-upgrade/Makefile 12(+12 -0)
diff --git a/docker/cassandra-upgrade/Makefile b/docker/cassandra-upgrade/Makefile
new file mode 100644
index 0000000..b2e8501
--- /dev/null
+++ b/docker/cassandra-upgrade/Makefile
@@ -0,0 +1,12 @@
+VERSION=2.1.0
+PROJECT=thingsboard
+APP=cassandra-upgrade
+
+build:
+ cp ../../application/target/thingsboard.deb .
+ docker build --pull -t ${PROJECT}/${APP}:${VERSION} -t ${PROJECT}/${APP}:latest .
+ rm thingsboard.deb
+
+push: build
+ docker push ${PROJECT}/${APP}:${VERSION}
+ docker push ${PROJECT}/${APP}:latest
docker/cassandra-upgrade/upgrade.sh 28(+28 -0)
diff --git a/docker/cassandra-upgrade/upgrade.sh b/docker/cassandra-upgrade/upgrade.sh
new file mode 100755
index 0000000..dac4919
--- /dev/null
+++ b/docker/cassandra-upgrade/upgrade.sh
@@ -0,0 +1,28 @@
+#!/bin/bash
+#
+# 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.
+#
+
+
+dpkg -i /thingsboard.deb
+
+until nmap $CASSANDRA_HOST -p $CASSANDRA_PORT | grep "$CASSANDRA_PORT/tcp open"
+do
+ echo "Wait for cassandra db to start..."
+ sleep 10
+done
+
+echo "Upgrading 'Thingsboard' schema..."
+/usr/share/thingsboard/bin/install/upgrade.sh --fromVersion=$UPGRADE_FROM_VERSION
docker/docker-compose.yml 2(+1 -1)
diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml
index 4b34e3a..89a8369 100644
--- a/docker/docker-compose.yml
+++ b/docker/docker-compose.yml
@@ -18,7 +18,7 @@ version: '2'
services:
tb:
- image: "thingsboard/application:2.0.3"
+ image: "thingsboard/application:2.1.0"
ports:
- "8080:8080"
- "1883:1883"
docker/k8s/cassandra.yaml 2(+1 -1)
diff --git a/docker/k8s/cassandra.yaml b/docker/k8s/cassandra.yaml
index 14ac8aa..3165bb1 100644
--- a/docker/k8s/cassandra.yaml
+++ b/docker/k8s/cassandra.yaml
@@ -54,7 +54,7 @@ spec:
topologyKey: "kubernetes.io/hostname"
containers:
- name: cassandra
- image: thingsboard/cassandra:2.0.3
+ image: thingsboard/cassandra:2.1.0
imagePullPolicy: Always
ports:
- containerPort: 7000
docker/k8s/cassandra-setup.yaml 2(+1 -1)
diff --git a/docker/k8s/cassandra-setup.yaml b/docker/k8s/cassandra-setup.yaml
index f3d2a43..381df77 100644
--- a/docker/k8s/cassandra-setup.yaml
+++ b/docker/k8s/cassandra-setup.yaml
@@ -22,7 +22,7 @@ spec:
containers:
- name: cassandra-setup
imagePullPolicy: Always
- image: thingsboard/cassandra-setup:2.0.3
+ image: thingsboard/cassandra-setup:2.1.0
env:
- name: ADD_DEMO_DATA
value: "true"
docker/k8s/cassandra-upgrade.yaml 43(+43 -0)
diff --git a/docker/k8s/cassandra-upgrade.yaml b/docker/k8s/cassandra-upgrade.yaml
new file mode 100644
index 0000000..a78136e
--- /dev/null
+++ b/docker/k8s/cassandra-upgrade.yaml
@@ -0,0 +1,43 @@
+#
+# 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.
+#
+
+apiVersion: v1
+kind: Pod
+metadata:
+ name: cassandra-upgrade
+spec:
+ containers:
+ - name: cassandra-upgrade
+ imagePullPolicy: Always
+ image: thingsboard/cassandra-upgrade:2.1.0
+ env:
+ - name: ADD_DEMO_DATA
+ value: "true"
+ - name : CASSANDRA_HOST
+ value: "cassandra-headless"
+ - name : CASSANDRA_PORT
+ value: "9042"
+ - name : DATABASE_TYPE
+ value: "cassandra"
+ - name : CASSANDRA_URL
+ value: "cassandra-headless:9042"
+ - name : UPGRADE_FROM_VERSION
+ value: "1.4.0"
+ command:
+ - sh
+ - -c
+ - /upgrade.sh
+ restartPolicy: Never
docker/k8s/tb.yaml 2(+1 -1)
diff --git a/docker/k8s/tb.yaml b/docker/k8s/tb.yaml
index 8f507e1..f38e1f1 100644
--- a/docker/k8s/tb.yaml
+++ b/docker/k8s/tb.yaml
@@ -84,7 +84,7 @@ spec:
containers:
- name: tb
imagePullPolicy: Always
- image: thingsboard/application:2.0.3
+ image: thingsboard/application:2.1.0
ports:
- containerPort: 8080
name: ui
docker/k8s/zookeeper.yaml 2(+1 -1)
diff --git a/docker/k8s/zookeeper.yaml b/docker/k8s/zookeeper.yaml
index f9948a8..ae33ea2 100644
--- a/docker/k8s/zookeeper.yaml
+++ b/docker/k8s/zookeeper.yaml
@@ -87,7 +87,7 @@ spec:
containers:
- name: zk
imagePullPolicy: Always
- image: thingsboard/zk:2.0.3
+ image: thingsboard/zk:2.1.0
ports:
- containerPort: 2181
name: client
docker/tb/Makefile 2(+1 -1)
diff --git a/docker/tb/Makefile b/docker/tb/Makefile
index 90793ed..96803ab 100644
--- a/docker/tb/Makefile
+++ b/docker/tb/Makefile
@@ -1,4 +1,4 @@
-VERSION=2.0.3
+VERSION=2.1.0
PROJECT=thingsboard
APP=application
docker/zookeeper/Makefile 2(+1 -1)
diff --git a/docker/zookeeper/Makefile b/docker/zookeeper/Makefile
index c96a6ea..5c58a74 100644
--- a/docker/zookeeper/Makefile
+++ b/docker/zookeeper/Makefile
@@ -1,4 +1,4 @@
-VERSION=2.0.3
+VERSION=2.1.0
PROJECT=thingsboard
APP=zk
netty-mqtt/pom.xml 4(+2 -2)
diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml
index a86fc58..e5904fd 100644
--- a/netty-mqtt/pom.xml
+++ b/netty-mqtt/pom.xml
@@ -19,12 +19,12 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
<artifactId>netty-mqtt</artifactId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Netty MQTT Client</name>
pom.xml 2(+1 -1)
diff --git a/pom.xml b/pom.xml
index e8ed5ad..4be0a7f 100755
--- a/pom.xml
+++ b/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>org.thingsboard</groupId>
<artifactId>thingsboard</artifactId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>Thingsboard</name>
rule-engine/pom.xml 2(+1 -1)
diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml
index f53eadf..7d769ce 100644
--- a/rule-engine/pom.xml
+++ b/rule-engine/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<artifactId>rule-engine</artifactId>
rule-engine/rule-engine-api/pom.xml 2(+1 -1)
diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml
index f067865..fce5562 100644
--- a/rule-engine/rule-engine-api/pom.xml
+++ b/rule-engine/rule-engine-api/pom.xml
@@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>
diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml
index 1759921..aed7efd 100644
--- a/rule-engine/rule-engine-components/pom.xml
+++ b/rule-engine/rule-engine-components/pom.xml
@@ -22,7 +22,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>rule-engine</artifactId>
</parent>
<groupId>org.thingsboard.rule-engine</groupId>
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
index 5a30dcb..e7a54af 100644
--- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java
@@ -47,11 +47,12 @@ import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
public class TbMsgGeneratorNode implements TbNode {
- public static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg";
+ private static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg";
private TbMsgGeneratorNodeConfiguration config;
private ScriptEngine jsEngine;
private long delay;
+ private long lastScheduledTs;
private EntityId originatorId;
private UUID nextTickId;
private TbMsg prevMsg;
@@ -66,28 +67,40 @@ public class TbMsgGeneratorNode implements TbNode {
originatorId = ctx.getSelfId();
}
this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType");
- sentTickMsg(ctx);
+ scheduleTickMsg(ctx);
}
@Override
public void onMsg(TbContext ctx, TbMsg msg) {
if (msg.getType().equals(TB_MSG_GENERATOR_NODE_MSG) && msg.getId().equals(nextTickId)) {
withCallback(generate(ctx),
- m -> {ctx.tellNext(m, SUCCESS); sentTickMsg(ctx);},
- t -> {ctx.tellFailure(msg, t); sentTickMsg(ctx);});
+ m -> {
+ ctx.tellNext(m, SUCCESS);
+ scheduleTickMsg(ctx);
+ },
+ t -> {
+ ctx.tellFailure(msg, t);
+ scheduleTickMsg(ctx);
+ });
}
}
- private void sentTickMsg(TbContext ctx) {
+ private void scheduleTickMsg(TbContext ctx) {
+ long curTs = System.currentTimeMillis();
+ if (lastScheduledTs == 0L) {
+ lastScheduledTs = curTs;
+ }
+ lastScheduledTs = lastScheduledTs + delay;
+ long curDelay = Math.max(0L, (lastScheduledTs - curTs));
TbMsg tickMsg = ctx.newMsg(TB_MSG_GENERATOR_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), "");
nextTickId = tickMsg.getId();
- ctx.tellSelf(tickMsg, delay);
+ ctx.tellSelf(tickMsg, curDelay);
}
private ListenableFuture<TbMsg> generate(TbContext ctx) {
return ctx.getJsExecutor().executeAsync(() -> {
if (prevMsg == null) {
- prevMsg = ctx.newMsg( "", originatorId, new TbMsgMetaData(), "{}");
+ prevMsg = ctx.newMsg("", originatorId, new TbMsgMetaData(), "{}");
}
TbMsg generated = jsEngine.executeGenerate(prevMsg);
prevMsg = ctx.newMsg(generated.getType(), originatorId, generated.getMetaData(), generated.getData());
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java
new file mode 100644
index 0000000..702e10f
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNode.java
@@ -0,0 +1,86 @@
+/**
+ * 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.delay;
+
+import lombok.extern.slf4j.Slf4j;
+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.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+import org.thingsboard.server.common.msg.TbMsgMetaData;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+import static org.thingsboard.rule.engine.api.TbRelationTypes.FAILURE;
+import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
+
+@Slf4j
+@RuleNode(
+ type = ComponentType.ACTION,
+ name = "delay",
+ configClazz = TbMsgDelayNodeConfiguration.class,
+ nodeDescription = "Delays incoming message",
+ nodeDetails = "Delays messages for configurable period.",
+ icon = "pause",
+ uiResources = {"static/rulenode/rulenode-core-config.js"},
+ configDirective = "tbActionNodeMsgDelayConfig"
+)
+
+public class TbMsgDelayNode implements TbNode {
+
+ private static final String TB_MSG_DELAY_NODE_MSG = "TbMsgDelayNodeMsg";
+
+ private TbMsgDelayNodeConfiguration config;
+ private long delay;
+ private Map<UUID, TbMsg> pendingMsgs;
+
+ @Override
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+ this.config = TbNodeUtils.convert(configuration, TbMsgDelayNodeConfiguration.class);
+ this.delay = TimeUnit.SECONDS.toMillis(config.getPeriodInSeconds());
+ this.pendingMsgs = new HashMap<>();
+ }
+
+ @Override
+ public void onMsg(TbContext ctx, TbMsg msg) {
+ if (msg.getType().equals(TB_MSG_DELAY_NODE_MSG)) {
+ TbMsg pendingMsg = pendingMsgs.remove(UUID.fromString(msg.getData()));
+ if (pendingMsg != null) {
+ ctx.tellNext(pendingMsg, SUCCESS);
+ }
+ } else {
+ if(pendingMsgs.size() < config.getMaxPendingMsgs()) {
+ pendingMsgs.put(msg.getId(), msg);
+ TbMsg tickMsg = ctx.newMsg(TB_MSG_DELAY_NODE_MSG, ctx.getSelfId(), new TbMsgMetaData(), msg.getId().toString());
+ ctx.tellSelf(tickMsg, delay);
+ } else {
+ ctx.tellNext(msg, FAILURE, new RuntimeException("Max limit of pending messages reached!"));
+ }
+ }
+ }
+
+ @Override
+ public void destroy() {
+ pendingMsgs.clear();
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeConfiguration.java
new file mode 100644
index 0000000..411a1a5
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/delay/TbMsgDelayNodeConfiguration.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.rule.engine.delay;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.EntityType;
+
+@Data
+public class TbMsgDelayNodeConfiguration implements NodeConfiguration<TbMsgDelayNodeConfiguration> {
+
+ private int periodInSeconds;
+ private int maxPendingMsgs;
+
+ @Override
+ public TbMsgDelayNodeConfiguration defaultConfiguration() {
+ TbMsgDelayNodeConfiguration configuration = new TbMsgDelayNodeConfiguration();
+ configuration.setPeriodInSeconds(60);
+ configuration.setMaxPendingMsgs(1000);
+ return configuration;
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java
new file mode 100644
index 0000000..043cb80
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNode.java
@@ -0,0 +1,54 @@
+/**
+ * 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.filter;
+
+import lombok.extern.slf4j.Slf4j;
+import org.thingsboard.rule.engine.api.util.TbNodeUtils;
+import org.thingsboard.rule.engine.api.*;
+import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.plugin.ComponentType;
+import org.thingsboard.server.common.msg.TbMsg;
+
+@Slf4j
+@RuleNode(
+ type = ComponentType.FILTER,
+ name = "originator type",
+ configClazz = TbOriginatorTypeFilterNodeConfiguration.class,
+ relationTypes = {"True", "False"},
+ nodeDescription = "Filter incoming messages by message Originator Type",
+ nodeDetails = "If Originator Type of incoming message is expected - send Message via <b>True</b> chain, otherwise <b>False</b> chain is used.",
+ uiResources = {"static/rulenode/rulenode-core-config.js", "static/rulenode/rulenode-core-config.css"},
+ configDirective = "tbFilterNodeOriginatorTypeConfig")
+public class TbOriginatorTypeFilterNode implements TbNode {
+
+ TbOriginatorTypeFilterNodeConfiguration config;
+
+ @Override
+ public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
+ this.config = TbNodeUtils.convert(configuration, TbOriginatorTypeFilterNodeConfiguration.class);
+ }
+
+ @Override
+ public void onMsg(TbContext ctx, TbMsg msg) throws TbNodeException {
+ EntityType originatorType = msg.getOriginator().getEntityType();
+ ctx.tellNext(msg, config.getOriginatorTypes().contains(originatorType) ? "True" : "False");
+ }
+
+ @Override
+ public void destroy() {
+
+ }
+}
diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java
new file mode 100644
index 0000000..83d0a99
--- /dev/null
+++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbOriginatorTypeFilterNodeConfiguration.java
@@ -0,0 +1,38 @@
+/**
+ * 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.filter;
+
+import lombok.Data;
+import org.thingsboard.rule.engine.api.NodeConfiguration;
+import org.thingsboard.server.common.data.EntityType;
+
+import java.util.Arrays;
+import java.util.List;
+
+@Data
+public class TbOriginatorTypeFilterNodeConfiguration implements NodeConfiguration<TbOriginatorTypeFilterNodeConfiguration> {
+
+ private List<EntityType> originatorTypes;
+
+ @Override
+ public TbOriginatorTypeFilterNodeConfiguration defaultConfiguration() {
+ TbOriginatorTypeFilterNodeConfiguration configuration = new TbOriginatorTypeFilterNodeConfiguration();
+ configuration.setOriginatorTypes(Arrays.asList(
+ EntityType.DEVICE
+ ));
+ return configuration;
+ }
+}
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 c5e057e..e06d253 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(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(72)},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=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> "},21,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\">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(5),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(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.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(8),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(51),i=a(r),o=n(36),l=a(o),s=n(39),u=a(s),d=n(38),c=a(d),m=n(37),g=a(m),p=n(42),f=a(p),b=n(46),v=a(b),y=n(47),q=a(y),h=n(45),T=a(h),$=n(41),k=a($),w=n(49),C=a(w),_=n(50),x=a(_),E=n(44),M=a(E),S=n(43),N=a(S),V=n(48),P=a(V);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",T.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",C.default).directive("tbActionNodeSqsConfig",x.default).directive("tbActionNodeRabbitMqConfig",M.default).directive("tbActionNodeMqttConfig",N.default).directive("tbActionNodeSendEmailConfig",P.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(9),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(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.$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(11),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(12),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(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.$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(14),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(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.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(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,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(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.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(20),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(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);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(22),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(57),i=a(r),o=n(58),l=a(o),s=n(55),u=a(s),d=n(59),c=a(d),m=n(54),g=a(m),p=n(60),f=a(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 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(23),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(24),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(25),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(26),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(27),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(64),i=a(r),o=n(63),l=a(o),s=n(65),u=a(s),d=n(61),c=a(d);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.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(){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.$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","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(3);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,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(29),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(30),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(31),o=a(i);n(4)},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(32),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(33),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(68),i=a(r),o=n(70),l=a(o),s=n(71),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(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){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(35),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(62),l=a(o),s=n(56),u=a(s),d=n(69),c=a(d),m=n(40),g=a(m),p=n(53),f=a(p),b=n(67),v=a(b),y=n(52),q=a(y),h=n(66),T=a(h),$=n(74),k=a($);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",T.default).config(k.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"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};angular.merge(e.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,t){(0,o.default)(t);for(var n in t){var a=t[n];e.translations(n,a)}}r.$inject=["$translateProvider","locales"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(73),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{messageType:{POST_ATTRIBUTES_REQUEST:{name:"Post attributes",value:"POST_ATTRIBUTES_REQUEST"},POST_TELEMETRY_REQUEST:{name:"Post telemetry",value:"POST_TELEMETRY_REQUEST"},TO_SERVER_RPC_REQUEST:{name:"RPC Request from Device",value:"TO_SERVER_RPC_REQUEST"},RPC_CALL_FROM_SERVER_TO_DEVICE:{name:"RPC Request to Device",value:"RPC_CALL_FROM_SERVER_TO_DEVICE"},ACTIVITY_EVENT:{name:"Activity Event",value:"ACTIVITY_EVENT"},INACTIVITY_EVENT:{name:"Inactivity Event",value:"INACTIVITY_EVENT"},CONNECT_EVENT:{name:"Connect Event",value:"CONNECT_EVENT"},DISCONNECT_EVENT:{name:"Disconnect Event",value:"DISCONNECT_EVENT"},ENTITY_CREATED:{name:"Entity Created",value:"ENTITY_CREATED"},ENTITY_UPDATED:{name:"Entity Updated",value:"ENTITY_UPDATED"},ENTITY_DELETED:{name:"Entity Deleted",value:"ENTITY_DELETED"},ENTITY_ASSIGNED:{name:"Entity Assigned",value:"ENTITY_ASSIGNED"},ENTITY_UNASSIGNED:{name:"Entity Unassigned",value:"ENTITY_UNASSIGNED"},ATTRIBUTES_UPDATED:{name:"Attributes Updated",value:"ATTRIBUTES_UPDATED"},ATTRIBUTES_DELETED:{name:"Attributes Deleted",value:"ATTRIBUTES_DELETED"}},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(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}]));
//# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
tools/pom.xml 2(+1 -1)
diff --git a/tools/pom.xml b/tools/pom.xml
index e5d443a..054eab0 100644
--- a/tools/pom.xml
+++ b/tools/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
transport/coap/pom.xml 2(+1 -1)
diff --git a/transport/coap/pom.xml b/transport/coap/pom.xml
index 9b37184..61e455a 100644
--- a/transport/coap/pom.xml
+++ b/transport/coap/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>
transport/http/pom.xml 2(+1 -1)
diff --git a/transport/http/pom.xml b/transport/http/pom.xml
index d9c46d2..3978620 100644
--- a/transport/http/pom.xml
+++ b/transport/http/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>
transport/mqtt/pom.xml 2(+1 -1)
diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml
index e0af4c0..0bdb9a8 100644
--- a/transport/mqtt/pom.xml
+++ b/transport/mqtt/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>transport</artifactId>
</parent>
<groupId>org.thingsboard.transport</groupId>
diff --git a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
index cfba944..69ed17f 100644
--- a/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
+++ b/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/session/GatewaySessionCtx.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.transport.mqtt.session;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
+import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
import io.netty.channel.ChannelHandlerContext;
@@ -240,7 +241,7 @@ public class GatewaySessionCtx {
private String getDeviceType(JsonElement json) throws AdaptorException {
JsonElement type = json.getAsJsonObject().get("type");
- return type == null ? DEFAULT_DEVICE_TYPE : type.getAsString();
+ return type == null || type instanceof JsonNull ? DEFAULT_DEVICE_TYPE : type.getAsString();
}
private JsonElement getJson(MqttPublishMessage mqttMsg) throws AdaptorException {
transport/pom.xml 2(+1 -1)
diff --git a/transport/pom.xml b/transport/pom.xml
index bb1420f..03f3d11 100644
--- a/transport/pom.xml
+++ b/transport/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
ui/package.json 22(+12 -10)
diff --git a/ui/package.json b/ui/package.json
index 328f47b..06b7fee 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -1,7 +1,7 @@
{
"name": "thingsboard",
"private": true,
- "version": "2.0.3",
+ "version": "2.1.0",
"description": "Thingsboard UI",
"licenses": [
{
@@ -15,7 +15,6 @@
},
"dependencies": {
"@flowjs/ng-flow": "^2.7.1",
- "ace-builds": "1.3.1",
"angular": "1.5.8",
"angular-animate": "1.5.8",
"angular-aria": "1.5.8",
@@ -37,17 +36,17 @@
"angular-socialshare": "^2.3.8",
"angular-storage": "0.0.15",
"angular-touch": "1.5.8",
- "angular-translate": "2.13.1",
- "angular-translate-handler-log": "2.13.1",
- "angular-translate-interpolation-messageformat": "2.13.1",
- "angular-translate-loader-static-files": "2.13.1",
- "angular-translate-storage-cookie": "2.13.1",
- "angular-translate-storage-local": "2.13.1",
+ "angular-translate": "2.18.1",
+ "angular-translate-handler-log": "2.18.1",
+ "angular-translate-interpolation-messageformat": "2.18.1",
+ "angular-translate-loader-static-files": "2.18.1",
+ "angular-translate-storage-cookie": "2.18.1",
+ "angular-translate-storage-local": "2.18.1",
"angular-ui-ace": "^0.2.3",
"angular-ui-router": "^0.3.1",
"angular-websocket": "^2.0.1",
"base64-js": "^1.2.1",
- "brace": "^0.8.0",
+ "brace": "^0.10.0",
"canvas-gauges": "^2.0.9",
"clipboard": "^1.5.15",
"compass-sass-mixins": "^0.12.7",
@@ -96,6 +95,7 @@
"babel-loader": "^6.2.5",
"babel-preset-es2015": "^6.14.0",
"babel-preset-react": "^6.16.0",
+ "compression-webpack-plugin": "^1.1.11",
"connect-history-api-fallback": "^1.3.0",
"copy-webpack-plugin": "^3.0.1",
"cross-env": "^3.2.4",
@@ -127,7 +127,9 @@
"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"
+ "webpack-material-design-icons": "^0.1.0",
+ "directory-tree": "^2.1.0",
+ "jsonminify": "^0.4.1"
},
"engine": "node >= 5.9.0",
"nyc": {
ui/pom.xml 2(+1 -1)
diff --git a/ui/pom.xml b/ui/pom.xml
index b1c0919..8003eee 100644
--- a/ui/pom.xml
+++ b/ui/pom.xml
@@ -20,7 +20,7 @@
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.thingsboard</groupId>
- <version>2.0.3</version>
+ <version>2.1.0-SNAPSHOT</version>
<artifactId>thingsboard</artifactId>
</parent>
<groupId>org.thingsboard</groupId>
ui/src/app/api/alias-controller.js 2(+2 -0)
diff --git a/ui/src/app/api/alias-controller.js b/ui/src/app/api/alias-controller.js
index 10b1107..66781a8 100644
--- a/ui/src/app/api/alias-controller.js
+++ b/ui/src/app/api/alias-controller.js
@@ -146,6 +146,7 @@ export default class AliasController {
newDatasource.entityId = resolvedEntity.id;
newDatasource.entityType = resolvedEntity.entityType;
newDatasource.entityName = resolvedEntity.name;
+ newDatasource.entityDescription = resolvedEntity.entityDescription
newDatasource.name = resolvedEntity.name;
newDatasource.generated = i > 0 ? true : false;
datasources.push(newDatasource);
@@ -167,6 +168,7 @@ export default class AliasController {
datasource.entityType = entity.entityType;
datasource.entityName = entity.name;
datasource.name = entity.name;
+ datasource.entityDescription = entity.entityDescription;
deferred.resolve([datasource]);
} else {
if (aliasInfo.stateEntity) {
ui/src/app/api/dashboard.service.js 2(+1 -1)
diff --git a/ui/src/app/api/dashboard.service.js b/ui/src/app/api/dashboard.service.js
index 507fe97..adab48f 100644
--- a/ui/src/app/api/dashboard.service.js
+++ b/ui/src/app/api/dashboard.service.js
@@ -252,7 +252,7 @@ function DashboardService($rootScope, $http, $q, $location, $filter) {
if (port != 80 && port != 443) {
url += ":" + port;
}
- url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
+ url += "/dashboard/" + dashboard.id.id + "?publicId=" + dashboard.publicCustomerId;
return url;
}
ui/src/app/api/entity.service.js 2(+1 -1)
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index 762bf1a..2e29238 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -329,7 +329,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device
}
function entityToEntityInfo(entity) {
- return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id };
+ return { name: entity.name, entityType: entity.id.entityType, id: entity.id.id, entityDescription: entity.additionalInfo?entity.additionalInfo.description:"" };
}
function entityRelationInfoToEntityInfo(entityRelationInfo, direction) {
ui/src/app/api/rule-chain.service.js 26(+15 -11)
diff --git a/ui/src/app/api/rule-chain.service.js b/ui/src/app/api/rule-chain.service.js
index e7436de..186e31c 100644
--- a/ui/src/app/api/rule-chain.service.js
+++ b/ui/src/app/api/rule-chain.service.js
@@ -32,6 +32,7 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
getRuleNodeComponents: getRuleNodeComponents,
getRuleNodeComponentByClazz: getRuleNodeComponentByClazz,
getRuleNodeSupportedLinks: getRuleNodeSupportedLinks,
+ ruleNodeAllowCustomLinks: ruleNodeAllowCustomLinks,
resolveTargetRuleChains: resolveTargetRuleChains,
testScript: testScript,
getLatestRuleNodeDebugInput: getLatestRuleNodeDebugInput
@@ -127,21 +128,21 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
function getRuleNodeSupportedLinks(component) {
var relationTypes = component.configurationDescriptor.nodeDefinition.relationTypes;
- var customRelations = component.configurationDescriptor.nodeDefinition.customRelations;
- var linkLabels = [];
+ var linkLabels = {};
for (var i=0;i<relationTypes.length;i++) {
- linkLabels.push({
- name: relationTypes[i], custom: false
- });
- }
- if (customRelations) {
- linkLabels.push(
- { name: 'Custom', custom: true }
- );
+ var label = relationTypes[i];
+ linkLabels[label] = {
+ name: label,
+ value: label
+ };
}
return linkLabels;
}
+ function ruleNodeAllowCustomLinks(component) {
+ return component.configurationDescriptor.nodeDefinition.customRelations;
+ }
+
function getRuleNodeComponents() {
var deferred = $q.defer();
if (ruleNodeComponents) {
@@ -226,7 +227,10 @@ function RuleChainService($http, $q, $filter, $ocLazyLoad, $translate, types, co
if (res && res.length) {
return res[0];
}
- return null;
+ var unknownComponent = angular.copy(types.unknownNodeComponent);
+ unknownComponent.clazz = clazz;
+ unknownComponent.configurationDescriptor.nodeDefinition.details = "Unknown Rule Node class: " + clazz;
+ return unknownComponent;
}
function resolveTargetRuleChains(ruleChainConnections) {
ui/src/app/api/time.service.js 163(+85 -78)
diff --git a/ui/src/app/api/time.service.js b/ui/src/app/api/time.service.js
index 8742b58..b8d0c41 100644
--- a/ui/src/app/api/time.service.js
+++ b/ui/src/app/api/time.service.js
@@ -32,84 +32,7 @@ const MAX_LIMIT = 500;
/*@ngInject*/
function TimeService($translate, types) {
- var predefIntervals = [
- {
- name: $translate.instant('timeinterval.seconds-interval', {seconds: 1}, 'messageformat'),
- value: 1 * SECOND
- },
- {
- name: $translate.instant('timeinterval.seconds-interval', {seconds: 5}, 'messageformat'),
- value: 5 * SECOND
- },
- {
- name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
- value: 10 * SECOND
- },
- {
- name: $translate.instant('timeinterval.seconds-interval', {seconds: 15}, 'messageformat'),
- value: 15 * SECOND
- },
- {
- name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
- value: 30 * SECOND
- },
- {
- name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
- value: 1 * MINUTE
- },
- {
- name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
- value: 2 * MINUTE
- },
- {
- name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
- value: 5 * MINUTE
- },
- {
- name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
- value: 10 * MINUTE
- },
- {
- name: $translate.instant('timeinterval.minutes-interval', {minutes: 15}, 'messageformat'),
- value: 15 * MINUTE
- },
- {
- name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
- value: 30 * MINUTE
- },
- {
- name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
- value: 1 * HOUR
- },
- {
- name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
- value: 2 * HOUR
- },
- {
- name: $translate.instant('timeinterval.hours-interval', {hours: 5}, 'messageformat'),
- value: 5 * HOUR
- },
- {
- name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
- value: 10 * HOUR
- },
- {
- name: $translate.instant('timeinterval.hours-interval', {hours: 12}, 'messageformat'),
- value: 12 * HOUR
- },
- {
- name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
- value: 1 * DAY
- },
- {
- name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
- value: 7 * DAY
- },
- {
- name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
- value: 30 * DAY
- }
- ];
+ var predefIntervals;
var service = {
minIntervalLimit: minIntervalLimit,
@@ -166,6 +89,7 @@ function TimeService($translate, types) {
min = boundMinInterval(min);
max = boundMaxInterval(max);
var intervals = [];
+ initPredefIntervals();
for (var i in predefIntervals) {
var interval = predefIntervals[i];
if (interval.value >= min && interval.value <= max) {
@@ -175,6 +99,89 @@ function TimeService($translate, types) {
return intervals;
}
+ function initPredefIntervals() {
+ if (!predefIntervals) {
+ predefIntervals = [
+ {
+ name: $translate.instant('timeinterval.seconds-interval', {seconds: 1}, 'messageformat'),
+ value: 1 * SECOND
+ },
+ {
+ name: $translate.instant('timeinterval.seconds-interval', {seconds: 5}, 'messageformat'),
+ value: 5 * SECOND
+ },
+ {
+ name: $translate.instant('timeinterval.seconds-interval', {seconds: 10}, 'messageformat'),
+ value: 10 * SECOND
+ },
+ {
+ name: $translate.instant('timeinterval.seconds-interval', {seconds: 15}, 'messageformat'),
+ value: 15 * SECOND
+ },
+ {
+ name: $translate.instant('timeinterval.seconds-interval', {seconds: 30}, 'messageformat'),
+ value: 30 * SECOND
+ },
+ {
+ name: $translate.instant('timeinterval.minutes-interval', {minutes: 1}, 'messageformat'),
+ value: 1 * MINUTE
+ },
+ {
+ name: $translate.instant('timeinterval.minutes-interval', {minutes: 2}, 'messageformat'),
+ value: 2 * MINUTE
+ },
+ {
+ name: $translate.instant('timeinterval.minutes-interval', {minutes: 5}, 'messageformat'),
+ value: 5 * MINUTE
+ },
+ {
+ name: $translate.instant('timeinterval.minutes-interval', {minutes: 10}, 'messageformat'),
+ value: 10 * MINUTE
+ },
+ {
+ name: $translate.instant('timeinterval.minutes-interval', {minutes: 15}, 'messageformat'),
+ value: 15 * MINUTE
+ },
+ {
+ name: $translate.instant('timeinterval.minutes-interval', {minutes: 30}, 'messageformat'),
+ value: 30 * MINUTE
+ },
+ {
+ name: $translate.instant('timeinterval.hours-interval', {hours: 1}, 'messageformat'),
+ value: 1 * HOUR
+ },
+ {
+ name: $translate.instant('timeinterval.hours-interval', {hours: 2}, 'messageformat'),
+ value: 2 * HOUR
+ },
+ {
+ name: $translate.instant('timeinterval.hours-interval', {hours: 5}, 'messageformat'),
+ value: 5 * HOUR
+ },
+ {
+ name: $translate.instant('timeinterval.hours-interval', {hours: 10}, 'messageformat'),
+ value: 10 * HOUR
+ },
+ {
+ name: $translate.instant('timeinterval.hours-interval', {hours: 12}, 'messageformat'),
+ value: 12 * HOUR
+ },
+ {
+ name: $translate.instant('timeinterval.days-interval', {days: 1}, 'messageformat'),
+ value: 1 * DAY
+ },
+ {
+ name: $translate.instant('timeinterval.days-interval', {days: 7}, 'messageformat'),
+ value: 7 * DAY
+ },
+ {
+ name: $translate.instant('timeinterval.days-interval', {days: 30}, 'messageformat'),
+ value: 30 * DAY
+ }
+ ];
+ }
+ }
+
function matchesExistingInterval(min, max, intervalMs) {
var intervals = getIntervals(min, max);
for (var i in intervals) {
ui/src/app/api/user.service.js 7(+4 -3)
diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js
index cb85709..48c811b 100644
--- a/ui/src/app/api/user.service.js
+++ b/ui/src/app/api/user.service.js
@@ -488,7 +488,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
} else {
return true;
}
- } else if (to.name === 'home.dashboards.dashboard' && allowedDashboardIds.indexOf(params.dashboardId) > -1) {
+ } else if ((to.name === 'home.dashboards.dashboard' || to.name === 'dashboard')
+ && allowedDashboardIds.indexOf(params.dashboardId) > -1) {
return false;
} else {
return true;
@@ -504,10 +505,10 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi
var place = 'home.links';
if (currentUser.authority === 'TENANT_ADMIN' || currentUser.authority === 'CUSTOMER_USER') {
if (userHasDefaultDashboard()) {
- place = 'home.dashboards.dashboard';
+ place = $rootScope.forceFullscreen ? 'dashboard' : 'home.dashboards.dashboard';
params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId};
} else if (isPublic()) {
- place = 'home.dashboards.dashboard';
+ place = 'dashboard';
params = {dashboardId: lastPublicDashboardId};
}
} else if (currentUser.authority === 'SYS_ADMIN') {
ui/src/app/app.config.js 76(+37 -39)
diff --git a/ui/src/app/app.config.js b/ui/src/app/app.config.js
index a84bdde..edc2c2a 100644
--- a/ui/src/app/app.config.js
+++ b/ui/src/app/app.config.js
@@ -15,10 +15,6 @@
*/
import injectTapEventPlugin from 'react-tap-event-plugin';
import UrlHandler from './url.handler';
-import addLocaleKorean from './locale/locale.constant-ko';
-import addLocaleChinese from './locale/locale.constant-zh';
-import addLocaleRussian from './locale/locale.constant-ru';
-import addLocaleSpanish from './locale/locale.constant-es';
/* eslint-disable import/no-unresolved, import/default */
@@ -38,46 +34,28 @@ export default function AppConfig($provide,
$mdThemingProvider,
$httpProvider,
$translateProvider,
- storeProvider,
- locales) {
+ storeProvider) {
injectTapEventPlugin();
$locationProvider.html5Mode(true);
$urlRouterProvider.otherwise(UrlHandler);
storeProvider.setCaching(false);
-
- $translateProvider.useSanitizeValueStrategy(null);
- $translateProvider.useMissingTranslationHandler('tbMissingTranslationHandler');
- $translateProvider.addInterpolation('$translateMessageFormatInterpolation');
- $translateProvider.fallbackLanguage('en_US');
-
- addLocaleKorean(locales);
- addLocaleChinese(locales);
- addLocaleRussian(locales);
- addLocaleSpanish(locales);
-
- for (var langKey in locales) {
- var translationTable = locales[langKey];
- $translateProvider.translations(langKey, translationTable);
- }
-
- var lang = $translateProvider.resolveClientLocale();
- if (lang) {
- lang = lang.toLowerCase();
- if (lang.startsWith('ko')) {
- $translateProvider.preferredLanguage('ko_KR');
- } else if (lang.startsWith('zh')) {
- $translateProvider.preferredLanguage('zh_CN');
- } else if (lang.startsWith('es')) {
- $translateProvider.preferredLanguage('es_ES');
- } else if (lang.startsWith('ru')) {
- $translateProvider.preferredLanguage('ru_RU');
- } else {
- $translateProvider.preferredLanguage('en_US');
- }
- } else {
- $translateProvider.preferredLanguage('en_US');
- }
+
+ $translateProvider.useSanitizeValueStrategy(null)
+ .useMissingTranslationHandler('tbMissingTranslationHandler')
+ .addInterpolation('$translateMessageFormatInterpolation')
+ .useStaticFilesLoader({
+ files: [
+ {
+ prefix: PUBLIC_PATH + 'locale/locale.constant-', //eslint-disable-line
+ suffix: '.json'
+ }
+ ]
+ })
+ .registerAvailableLanguageKeys(SUPPORTED_LANGS, getLanguageAliases(SUPPORTED_LANGS)) //eslint-disable-line
+ .fallbackLanguage('en_US') // must be before determinePreferredLanguage
+ .uniformLanguageTag('java') // must be before determinePreferredLanguage
+ .determinePreferredLanguage();
$httpProvider.interceptors.push('globalInterceptor');
@@ -168,4 +146,24 @@ export default function AppConfig($provide,
//$mdThemingProvider.alwaysWatchTheme(true);
}
+ function getLanguageAliases(supportedLangs) {
+ var aliases = {};
+
+ supportedLangs.sort().forEach(function(item, index, array) {
+ if (item.length === 2) {
+ aliases[item] = item;
+ aliases[item + '_*'] = item;
+ } else {
+ var key = item.slice(0, 2);
+ if (index === 0 || key !== array[index - 1].slice(0, 2)) {
+ aliases[key] = item;
+ aliases[key + '_*'] = item;
+ } else {
+ aliases[item] = item;
+ }
+ }
+ });
+
+ return aliases;
+ }
}
\ No newline at end of file
ui/src/app/app.js 4(+2 -2)
diff --git a/ui/src/app/app.js b/ui/src/app/app.js
index f021efb..c8cdeb0 100644
--- a/ui/src/app/app.js
+++ b/ui/src/app/app.js
@@ -51,7 +51,7 @@ import react from 'ngreact';
import '@flowjs/ng-flow/dist/ng-flow-standalone.min';
import 'ngFlowchart/dist/ngFlowchart';
-import thingsboardLocales from './locale/locale.constant';
+import thingsboardTranslateHandler from './locale/translate-handler';
import thingsboardLogin from './login';
import thingsboardDialogs from './components/datakey-config-dialog.controller';
import thingsboardMenu from './services/menu.service';
@@ -117,7 +117,7 @@ angular.module('thingsboard', [
react.name,
'flow',
'flowchart',
- thingsboardLocales,
+ thingsboardTranslateHandler,
thingsboardLogin,
thingsboardDialogs,
thingsboardMenu,
ui/src/app/app.run.js 7(+5 -2)
diff --git a/ui/src/app/app.run.js b/ui/src/app/app.run.js
index f3886b8..4667b09 100644
--- a/ui/src/app/app.run.js
+++ b/ui/src/app/app.run.js
@@ -113,7 +113,10 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
showForbiddenDialog();
} else if (to.redirectTo) {
evt.preventDefault();
- $state.go(to.redirectTo, params)
+ $state.go(to.redirectTo, params);
+ } else if (to.name === 'home.dashboards.dashboard' && $rootScope.forceFullscreen) {
+ evt.preventDefault();
+ $state.go('dashboard', params);
}
}
} else {
@@ -138,7 +141,7 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
$rootScope.pageTitle = 'ThingsBoard';
$rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
- if (userService.isPublic() && to.name === 'home.dashboards.dashboard') {
+ if (userService.isPublic() && to.name === 'dashboard') {
$location.search('publicId', userService.getPublicId());
userService.updateLastPublicDashboardId(params.dashboardId);
}
ui/src/app/common/types.constant.js 106(+106 -0)
diff --git a/ui/src/app/common/types.constant.js b/ui/src/app/common/types.constant.js
index cf1023e..1e34577 100644
--- a/ui/src/app/common/types.constant.js
+++ b/ui/src/app/common/types.constant.js
@@ -195,6 +195,21 @@ export default angular.module('thingsboard.types', [])
},
"ATTRIBUTES_READ": {
name: "audit-log.type-attributes-read"
+ },
+ "RELATION_ADD_OR_UPDATE": {
+ name: "audit-log.type-relation-add-or-update"
+ },
+ "RELATION_DELETED": {
+ name: "audit-log.type-relation-delete"
+ },
+ "RELATIONS_DELETED": {
+ name: "audit-log.type-relations-delete"
+ },
+ "ALARM_ACK": {
+ name: "audit-log.type-alarm-ack"
+ },
+ "ALARM_CLEAR": {
+ name: "audit-log.type-alarm-clear"
}
},
auditLogActionStatus: {
@@ -366,6 +381,12 @@ export default angular.module('thingsboard.types', [])
list: 'entity.list-of-rulechains',
nameStartsWith: 'entity.rulechain-name-starts-with'
},
+ "RULE_NODE": {
+ type: 'entity.type-rulenode',
+ typePlural: 'entity.type-rulenodes',
+ list: 'entity.list-of-rulenodes',
+ nameStartsWith: 'entity.rulenode-name-starts-with'
+ },
"CURRENT_CUSTOMER": {
type: 'entity.type-current-customer',
list: 'entity.type-current-customer'
@@ -510,6 +531,22 @@ export default angular.module('thingsboard.types', [])
}
}
},
+ unknownNodeComponent: {
+ type: 'UNKNOWN',
+ name: 'unknown',
+ clazz: 'tb.internal.Unknown',
+ configurationDescriptor: {
+ nodeDefinition: {
+ description: "",
+ details: "",
+ inEnabled: true,
+ outEnabled: true,
+ relationTypes: [],
+ customRelations: false,
+ defaultConfiguration: {}
+ }
+ }
+ },
inputNodeComponent: {
type: 'INPUT',
name: 'Input',
@@ -565,6 +602,75 @@ export default angular.module('thingsboard.types', [])
nodeClass: "tb-input-type",
icon: "input",
special: true
+ },
+ UNKNOWN: {
+ value: "UNKNOWN",
+ name: "rulenode.type-unknown",
+ details: "rulenode.type-unknown-details",
+ nodeClass: "tb-unknown-type",
+ icon: "help_outline"
+ }
+ },
+ messageType: {
+ 'POST_ATTRIBUTES_REQUEST': {
+ name: 'Post attributes',
+ value: 'POST_ATTRIBUTES_REQUEST'
+ },
+ 'POST_TELEMETRY_REQUEST': {
+ name: 'Post telemetry',
+ value: 'POST_TELEMETRY_REQUEST'
+ },
+ 'TO_SERVER_RPC_REQUEST': {
+ name: 'RPC Request from Device',
+ value: 'TO_SERVER_RPC_REQUEST'
+ },
+ 'RPC_CALL_FROM_SERVER_TO_DEVICE': {
+ name: 'RPC Request to Device',
+ value: 'RPC_CALL_FROM_SERVER_TO_DEVICE'
+ },
+ 'ACTIVITY_EVENT': {
+ name: 'Activity Event',
+ value: 'ACTIVITY_EVENT'
+ },
+ 'INACTIVITY_EVENT': {
+ name: 'Inactivity Event',
+ value: 'INACTIVITY_EVENT'
+ },
+ 'CONNECT_EVENT': {
+ name: 'Connect Event',
+ value: 'CONNECT_EVENT'
+ },
+ 'DISCONNECT_EVENT': {
+ name: 'Disconnect Event',
+ value: 'DISCONNECT_EVENT'
+ },
+ 'ENTITY_CREATED': {
+ name: 'Entity Created',
+ value: 'ENTITY_CREATED'
+ },
+ 'ENTITY_UPDATED': {
+ name: 'Entity Updated',
+ value: 'ENTITY_UPDATED'
+ },
+ 'ENTITY_DELETED': {
+ name: 'Entity Deleted',
+ value: 'ENTITY_DELETED'
+ },
+ 'ENTITY_ASSIGNED': {
+ name: 'Entity Assigned',
+ value: 'ENTITY_ASSIGNED'
+ },
+ 'ENTITY_UNASSIGNED': {
+ name: 'Entity Unassigned',
+ value: 'ENTITY_UNASSIGNED'
+ },
+ 'ATTRIBUTES_UPDATED': {
+ name: 'Attributes Updated',
+ value: 'ATTRIBUTES_UPDATED'
+ },
+ 'ATTRIBUTES_DELETED': {
+ name: 'Attributes Deleted',
+ value: 'ATTRIBUTES_DELETED'
}
},
valueType: {
ui/src/app/components/dashboard.scss 8(+4 -4)
diff --git a/ui/src/app/components/dashboard.scss b/ui/src/app/components/dashboard.scss
index 9f76347..ca99fcc 100644
--- a/ui/src/app/components/dashboard.scss
+++ b/ui/src/app/components/dashboard.scss
@@ -40,12 +40,12 @@ div.tb-widget {
position: absolute;
top: 8px;
right: 8px;
- z-index: 1;
- margin: 0px;
+ z-index: 19;
+ margin: 0;
.md-button.md-icon-button {
- margin: 0px !important;
- padding: 0px !important;
+ margin: 0 !important;
+ padding: 0 !important;
line-height: 20px;
width: 32px;
height: 32px;
diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html
index 484164f..db6fd3b 100644
--- a/ui/src/app/components/datasource-entity.tpl.html
+++ b/ui/src/app/components/datasource-entity.tpl.html
@@ -60,7 +60,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
- <div layout="row" flex>
+ <div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
@@ -112,7 +112,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
- <div layout="row" flex>
+ <div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
@@ -164,7 +164,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
- <div layout="row" flex>
+ <div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
diff --git a/ui/src/app/components/datasource-func.tpl.html b/ui/src/app/components/datasource-func.tpl.html
index 134dcd7..6bf49ba 100644
--- a/ui/src/app/components/datasource-func.tpl.html
+++ b/ui/src/app/components/datasource-func.tpl.html
@@ -61,7 +61,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
- <div layout="row" flex>
+ <div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
@@ -112,7 +112,7 @@
<div class="tb-color-preview" ng-click="showColorPicker($event, $chip, $index)" style="margin-right: 5px;">
<div class="tb-color-result" ng-style="{background: $chip.color}"></div>
</div>
- <div layout="row" flex>
+ <div layout="row">
<div class="tb-chip-label">
{{$chip.label}}
</div>
ui/src/app/components/details-sidenav.scss 10(+0 -10)
diff --git a/ui/src/app/components/details-sidenav.scss b/ui/src/app/components/details-sidenav.scss
index 360b133..c7e9919 100644
--- a/ui/src/app/components/details-sidenav.scss
+++ b/ui/src/app/components/details-sidenav.scss
@@ -59,14 +59,4 @@ md-sidenav.tb-sidenav-details {
background-color: $primary-hue-3;
}
}
-
- md-tab-content.md-active > div {
- height: 100%;
- & > *:first-child {
- height: 100%;
- }
- md-content {
- height: 100%;
- }
- }
}
diff --git a/ui/src/app/components/json-content.directive.js b/ui/src/app/components/json-content.directive.js
index e945079..1027486 100644
--- a/ui/src/app/components/json-content.directive.js
+++ b/ui/src/app/components/json-content.directive.js
@@ -18,8 +18,8 @@ import './json-content.scss';
import 'brace/ext/language_tools';
import 'brace/mode/json';
import 'brace/mode/text';
-import 'ace-builds/src-min-noconflict/snippets/json';
-import 'ace-builds/src-min-noconflict/snippets/text';
+import 'brace/snippets/json';
+import 'brace/snippets/text';
import fixAceEditor from './ace-editor-fix';
diff --git a/ui/src/app/components/json-object-edit.directive.js b/ui/src/app/components/json-object-edit.directive.js
index 215b7b9..9364689 100644
--- a/ui/src/app/components/json-object-edit.directive.js
+++ b/ui/src/app/components/json-object-edit.directive.js
@@ -17,7 +17,7 @@ import './json-object-edit.scss';
import 'brace/ext/language_tools';
import 'brace/mode/json';
-import 'ace-builds/src-min-noconflict/snippets/json';
+import 'brace/snippets/json';
import fixAceEditor from './ace-editor-fix';
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.directive.js b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
index 81c40cd..88a37a8 100644
--- a/ui/src/app/components/widget/action/manage-widget-actions.directive.js
+++ b/ui/src/app/components/widget/action/manage-widget-actions.directive.js
@@ -111,8 +111,15 @@ function ManageWidgetActionsController($rootScope, $scope, $document, $mdDialog,
}
});
- function enterFilterMode () {
+ function enterFilterMode (event) {
+ let $button = angular.element(event.currentTarget);
+ let $toolbarsContainer = $button.closest('.toolbarsContainer');
+
vm.query.search = '';
+
+ $timeout(()=>{
+ $toolbarsContainer.find('.searchInput').focus();
+ })
}
function exitFilterMode () {
diff --git a/ui/src/app/components/widget/action/manage-widget-actions.tpl.html b/ui/src/app/components/widget/action/manage-widget-actions.tpl.html
index fc9262e..07f76f2 100644
--- a/ui/src/app/components/widget/action/manage-widget-actions.tpl.html
+++ b/ui/src/app/components/widget/action/manage-widget-actions.tpl.html
@@ -15,7 +15,7 @@
limitations under the License.
-->
-<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1" layout="column">
+<div ng-form="manageWidgetActionsForm" class="tb-manage-widget-actions md-whiteframe-z1 toolbarsContainer" layout="column">
<md-toolbar class="md-table-toolbar md-default" ng-show="vm.query.search === null">
<div class="md-toolbar-tools">
<span translate>widget-config.actions</span>
@@ -26,7 +26,7 @@
{{ 'widget-config.add-action' | translate }}
</md-tooltip>
</md-button>
- <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+ <md-button class="md-icon-button" ng-click="vm.enterFilterMode($event)">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
@@ -44,7 +44,7 @@
</md-button>
<md-input-container flex>
<label> </label>
- <input ng-model="vm.query.search" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | translate }}"/>
+ <input ng-model="vm.query.search" class="searchInput" name="querySearchInput" placeholder="{{ 'widget-config.search-actions' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
diff --git a/ui/src/app/components/widget/widget.controller.js b/ui/src/app/components/widget/widget.controller.js
index 9feb40d..36e5bee 100644
--- a/ui/src/app/components/widget/widget.controller.js
+++ b/ui/src/app/components/widget/widget.controller.js
@@ -479,7 +479,11 @@ export default function WidgetController($scope, $state, $timeout, $window, $ele
dashboardId: targetDashboardId,
state: utils.objToBase64([ stateObject ])
}
- $state.go('home.dashboards.dashboard', stateParams);
+ if ($state.current.name === 'dashboard') {
+ $state.go('dashboard', stateParams);
+ } else {
+ $state.go('home.dashboards.dashboard', stateParams);
+ }
break;
case types.widgetActionTypes.custom.value:
var customFunction = descriptor.customFunction;
diff --git a/ui/src/app/components/widget/widget-config.tpl.html b/ui/src/app/components/widget/widget-config.tpl.html
index ce796d4..e8762bd 100644
--- a/ui/src/app/components/widget/widget-config.tpl.html
+++ b/ui/src/app/components/widget/widget-config.tpl.html
@@ -187,17 +187,17 @@
</div>
<div layout='column' layout-align="center" layout-gt-sm='row' layout-align-gt-sm="start center">
<div layout="row" layout-padding>
- <md-checkbox flex aria-label="{{ 'widget-config.display-title' | translate }}"
+ <md-checkbox aria-label="{{ 'widget-config.display-title' | translate }}"
ng-model="showTitle">{{ 'widget-config.display-title' | translate }}
</md-checkbox>
</div>
<div layout="row" layout-padding>
- <md-checkbox flex aria-label="{{ 'widget-config.drop-shadow' | translate }}"
+ <md-checkbox aria-label="{{ 'widget-config.drop-shadow' | translate }}"
ng-model="dropShadow">{{ 'widget-config.drop-shadow' | translate }}
</md-checkbox>
</div>
<div layout="row" layout-padding>
- <md-checkbox flex aria-label="{{ 'widget-config.enable-fullscreen' | translate }}"
+ <md-checkbox aria-label="{{ 'widget-config.enable-fullscreen' | translate }}"
ng-model="enableFullscreen">{{ 'widget-config.enable-fullscreen' | translate }}
</md-checkbox>
</div>
ui/src/app/dashboard/dashboard.controller.js 11(+10 -1)
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index f672f3f..6df48df 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -196,6 +196,7 @@ export default function DashboardController(types, utils, dashboardUtils, widget
vm.displayDashboardTimewindow = displayDashboardTimewindow;
vm.displayDashboardsSelect = displayDashboardsSelect;
vm.displayEntitiesSelect = displayEntitiesSelect;
+ vm.hideFullscreenButton = hideFullscreenButton;
vm.widgetsBundle;
@@ -258,7 +259,11 @@ export default function DashboardController(types, utils, dashboardUtils, widget
dashboardId: vm.currentDashboardId
});
} else {
- $state.go('home.dashboards.dashboard', {dashboardId: vm.currentDashboardId});
+ if ($state.current.name === 'dashboard') {
+ $state.go('dashboard', {dashboardId: vm.currentDashboardId});
+ } else {
+ $state.go('home.dashboards.dashboard', {dashboardId: vm.currentDashboardId});
+ }
}
}
});
@@ -805,6 +810,10 @@ export default function DashboardController(types, utils, dashboardUtils, widget
}
}
+ function hideFullscreenButton() {
+ return vm.widgetEditMode || vm.iframeMode || $rootScope.forceFullscreen || $state.current.name === 'dashboard';
+ }
+
function onRevertWidgetEdit(widgetForm) {
if (widgetForm.$dirty) {
widgetForm.$setPristine();
ui/src/app/dashboard/dashboard.routes.js 18(+18 -0)
diff --git a/ui/src/app/dashboard/dashboard.routes.js b/ui/src/app/dashboard/dashboard.routes.js
index ccb43c7..4572ac3 100644
--- a/ui/src/app/dashboard/dashboard.routes.js
+++ b/ui/src/app/dashboard/dashboard.routes.js
@@ -86,6 +86,24 @@ export default function DashboardRoutes($stateProvider) {
label: '{"icon": "dashboard", "label": "{{ vm.dashboard.title }}", "translate": "false"}'
}
})
+ .state('dashboard', {
+ url: '/dashboard/:dashboardId?state',
+ reloadOnSearch: false,
+ module: 'private',
+ auth: ['TENANT_ADMIN', 'CUSTOMER_USER'],
+ views: {
+ "@": {
+ templateUrl: dashboardTemplate,
+ controller: 'DashboardController',
+ controllerAs: 'vm'
+ }
+ },
+ data: {
+ widgetEditMode: false,
+ searchEnabled: false,
+ pageTitle: 'dashboard.dashboard'
+ }
+ })
.state('home.customers.dashboards.dashboard', {
url: '/:dashboardId?state',
reloadOnSearch: false,
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 9626509..829f174 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -16,7 +16,7 @@
-->
<md-content style="padding-top: 150px;" flex tb-expand-fullscreen="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-button-id="dashboard-expand-button"
- hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom" ng-if="vm.dashboard">
+ hide-expand-button="vm.hideFullscreenButton()" expand-tooltip-direction="bottom" ng-if="vm.dashboard">
<section class="tb-dashboard-toolbar" ng-show="vm.showDashboardToolbar()"
ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
<tb-dashboard-toolbar ng-show="!vm.widgetEditMode" force-fullscreen="forceFullscreen"
diff --git a/ui/src/app/dashboard/states/select-target-state.tpl.html b/ui/src/app/dashboard/states/select-target-state.tpl.html
index 5fd3122..a9a4348 100644
--- a/ui/src/app/dashboard/states/select-target-state.tpl.html
+++ b/ui/src/app/dashboard/states/select-target-state.tpl.html
@@ -41,8 +41,8 @@
</md-dialog-content>
<md-dialog-actions layout="row">
<span flex></span>
- <md-button ng-disabled="$root.loading || !theForm.$dirty || !theForm.$valid" type="submit" class="md-raised md-primary">
- {{ 'action.save' | translate }}
+ <md-button ng-disabled="$root.loading || !theForm.$valid" type="submit" class="md-raised md-primary">
+ {{ 'action.select' | translate }}
</md-button>
<md-button ng-disabled="$root.loading" ng-click="vm.cancel()" style="margin-right:20px;">{{ 'action.cancel' | translate }}</md-button>
</md-dialog-actions>
diff --git a/ui/src/app/device/device-card.tpl.html b/ui/src/app/device/device-card.tpl.html
index 522f1ab..fbda549 100644
--- a/ui/src/app/device/device-card.tpl.html
+++ b/ui/src/app/device/device-card.tpl.html
@@ -16,7 +16,8 @@
-->
<div flex layout="column" style="margin-top: -10px;">
- <div style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
- <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
- <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
+ <div style="text-transform: uppercase; padding-bottom: 5px;">{{vm.item.type}}</div>
+ <div class="tb-card-description">{{vm.item.additionalInfo.description}}</div>
+ <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+ <div style="padding-top: 5px;" class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
</div>
diff --git a/ui/src/app/entity/alias/entity-aliases.tpl.html b/ui/src/app/entity/alias/entity-aliases.tpl.html
index a760eab..8eb7ff5 100644
--- a/ui/src/app/entity/alias/entity-aliases.tpl.html
+++ b/ui/src/app/entity/alias/entity-aliases.tpl.html
@@ -43,7 +43,7 @@
<fieldset ng-disabled="$root.loading">
<div ng-form name="aliasForm" flex layout="row" layout-align="start center" ng-repeat="entityAlias in vm.entityAliases track by $index">
<span flex="5">{{$index + 1}}.</span>
- <di class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
+ <div class="md-whiteframe-4dp tb-alias" flex layout="row" layout-align="start center">
<md-input-container flex="20" style="min-width: 150px;" md-no-float class="md-block">
<input required name="alias" placeholder="{{ 'entity.alias' | translate }}" ng-model="entityAlias.alias">
<div ng-messages="aliasForm.alias.$error">
@@ -81,7 +81,7 @@
close
</md-icon>
</md-button>
- </di>
+ </div>
</div>
</fieldset>
</div>
diff --git a/ui/src/app/entity/attribute/attribute-table.directive.js b/ui/src/app/entity/attribute/attribute-table.directive.js
index b551ae6..0061854 100644
--- a/ui/src/app/entity/attribute/attribute-table.directive.js
+++ b/ui/src/app/entity/attribute/attribute-table.directive.js
@@ -30,7 +30,7 @@ import AliasController from '../../api/alias-controller';
/*@ngInject*/
export default function AttributeTableDirective($compile, $templateCache, $rootScope, $q, $mdEditDialog, $mdDialog,
- $mdUtil, $document, $translate, $filter, utils, types, dashboardUtils,
+ $mdUtil, $document, $translate, $filter, $timeout, utils, types, dashboardUtils,
entityService, attributeService, widgetService) {
var linker = function (scope, element, attrs) {
@@ -110,8 +110,15 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS
scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope);
}
- scope.enterFilterMode = function() {
+ scope.enterFilterMode = function(event) {
+ let $button = angular.element(event.currentTarget);
+ let $toolbarsContainer = $button.closest('.toolbarsContainer');
+
scope.query.search = '';
+
+ $timeout(()=>{
+ $toolbarsContainer.find('.searchInput').focus();
+ })
}
scope.exitFilterMode = function() {
diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html
index d55a6da..ecbcf00 100644
--- a/ui/src/app/entity/attribute/attribute-table.tpl.html
+++ b/ui/src/app/entity/attribute/attribute-table.tpl.html
@@ -26,7 +26,7 @@
</md-select>
</md-input-container>
</section>
- <div class="md-whiteframe-z1" ng-class="{flex: mode==='widget'}">
+ <div class="md-whiteframe-z1 toolbarsContainer" ng-class="{flex: mode==='widget'}">
<md-toolbar class="md-table-toolbar md-default" ng-show="mode==='default'
&& !selectedAttributes.length
&& query.search === null">
@@ -39,7 +39,7 @@
{{ 'action.add' | translate }}
</md-tooltip>
</md-button>
- <md-button class="md-icon-button" ng-click="enterFilterMode()">
+ <md-button class="md-icon-button" ng-click="enterFilterMode($event)">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
@@ -65,7 +65,7 @@
</md-button>
<md-input-container flex>
<label> </label>
- <input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
+ <input ng-model="query.search" class="searchInput" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="exitFilterMode()">
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
ui/src/app/entity/entity-select.directive.js 21(+18 -3)
diff --git a/ui/src/app/entity/entity-select.directive.js b/ui/src/app/entity/entity-select.directive.js
index 8e4031c..e7ac5fd 100644
--- a/ui/src/app/entity/entity-select.directive.js
+++ b/ui/src/app/entity/entity-select.directive.js
@@ -22,14 +22,28 @@ import entitySelectTemplate from './entity-select.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function EntitySelect($compile, $templateCache) {
+export default function EntitySelect($compile, $templateCache, entityService) {
var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(entitySelectTemplate);
element.html(template);
scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
- scope.model = {};
+
+ var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes, scope.useAliasEntityTypes);
+
+ var entityTypeKeys = Object.keys(entityTypes);
+
+ if (entityTypeKeys.length === 1) {
+ scope.displayEntityTypeSelect = false;
+ scope.defaultEntityType = entityTypes[entityTypeKeys[0]];
+ } else {
+ scope.displayEntityTypeSelect = true;
+ }
+
+ scope.model = {
+ entityType: scope.defaultEntityType
+ };
scope.updateView = function () {
if (!scope.disabled) {
@@ -54,7 +68,7 @@ export default function EntitySelect($compile, $templateCache) {
scope.model.entityType = value.entityType;
scope.model.entityId = value.id;
} else {
- scope.model.entityType = null;
+ scope.model.entityType = scope.defaultEntityType;
scope.model.entityId = null;
}
initWatchers();
@@ -106,6 +120,7 @@ export default function EntitySelect($compile, $templateCache) {
theForm: '=?',
tbRequired: '=?',
disabled:'=ngDisabled',
+ allowedEntityTypes: "=?",
useAliasEntityTypes: "=?"
}
};
diff --git a/ui/src/app/entity/entity-select.tpl.html b/ui/src/app/entity/entity-select.tpl.html
index 9e5e227..d6b6eea 100644
--- a/ui/src/app/entity/entity-select.tpl.html
+++ b/ui/src/app/entity/entity-select.tpl.html
@@ -17,10 +17,12 @@
-->
<div layout='row' class="tb-entity-select">
<tb-entity-type-select style="min-width: 100px;"
+ ng-if="displayEntityTypeSelect"
the-form="theForm"
ng-disabled="disabled"
tb-required="tbRequired"
use-alias-entity-types="useAliasEntityTypes"
+ allowed-entity-types="allowedEntityTypes"
ng-model="model.entityType">
</tb-entity-type-select>
<tb-entity-autocomplete flex ng-if="model.entityType"
diff --git a/ui/src/app/entity/entity-type-list.directive.js b/ui/src/app/entity/entity-type-list.directive.js
index f042e5c..8a680c5 100644
--- a/ui/src/app/entity/entity-type-list.directive.js
+++ b/ui/src/app/entity/entity-type-list.directive.js
@@ -35,7 +35,30 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
: $translate.instant('entity.any-entity');
scope.secondaryPlaceholder = '+' + $translate.instant('entity.entity-type');
- var entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
+ var entityTypes;
+
+ if (scope.ignoreAuthorityFilter && scope.allowedEntityTypes
+ && scope.allowedEntityTypes.length) {
+ entityTypes = {};
+ scope.allowedEntityTypes.forEach((entityTypeValue) => {
+ var entityType = entityTypeFromValue(entityTypeValue);
+ if (entityType) {
+ entityTypes[entityType] = entityTypeValue;
+ }
+ });
+ } else {
+ entityTypes = entityService.prepareAllowedEntityTypesList(scope.allowedEntityTypes);
+ }
+
+ function entityTypeFromValue(entityTypeValue) {
+ for (var entityType in types.entityType) {
+ if (types.entityType[entityType] === entityTypeValue) {
+ return entityType;
+ }
+ }
+ return null;
+ }
+
scope.entityTypesList = [];
for (var type in entityTypes) {
var entityTypeInfo = {};
@@ -62,28 +85,43 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
}
ngModelCtrl.$render = function () {
- scope.entityTypeList = [];
+ if (scope.entityTypeListWatch) {
+ scope.entityTypeListWatch();
+ scope.entityTypeListWatch = null;
+ }
+ var entityTypeList = [];
var value = ngModelCtrl.$viewValue;
if (value && value.length) {
value.forEach(function(type) {
var entityTypeInfo = {};
entityTypeInfo.value = type;
entityTypeInfo.name = $translate.instant(types.entityTypeTranslations[entityTypeInfo.value].type) + '';
- scope.entityTypeList.push(entityTypeInfo);
+ entityTypeList.push(entityTypeInfo);
});
}
+ scope.entityTypeList = entityTypeList;
+ scope.entityTypeListWatch = scope.$watch('entityTypeList', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ updateEntityTypeList();
+ }
+ }, true);
}
- scope.$watch('entityTypeList', function () {
- var values = [];
+ function updateEntityTypeList() {
+ var values = ngModelCtrl.$viewValue;
+ if (!values) {
+ values = [];
+ ngModelCtrl.$setViewValue(values);
+ } else {
+ values.length = 0;
+ }
if (scope.entityTypeList && scope.entityTypeList.length) {
- scope.entityTypeList.forEach(function(entityType) {
+ scope.entityTypeList.forEach(function (entityType) {
values.push(entityType.value);
});
}
- ngModelCtrl.$setViewValue(values);
scope.updateValidity();
- }, true);
+ }
$compile(element.contents())(scope);
@@ -103,7 +141,8 @@ export default function EntityTypeListDirective($compile, $templateCache, $q, $m
scope: {
disabled:'=ngDisabled',
tbRequired: '=?',
- allowedEntityTypes: '=?'
+ allowedEntityTypes: '=?',
+ ignoreAuthorityFilter: '=?'
}
};
diff --git a/ui/src/app/entity/relation/relation-filters.directive.js b/ui/src/app/entity/relation/relation-filters.directive.js
index 9ab66ca..e945fb6 100644
--- a/ui/src/app/entity/relation/relation-filters.directive.js
+++ b/ui/src/app/entity/relation/relation-filters.directive.js
@@ -44,6 +44,10 @@ export default function RelationFilters($compile, $templateCache) {
scope.removeFilter = removeFilter;
ngModelCtrl.$render = function () {
+ if (scope.relationFiltersWatch) {
+ scope.relationFiltersWatch();
+ scope.relationFiltersWatch = null;
+ }
if (ngModelCtrl.$viewValue) {
var value = ngModelCtrl.$viewValue;
scope.relationFilters.length = 0;
@@ -51,7 +55,7 @@ export default function RelationFilters($compile, $templateCache) {
scope.relationFilters.push(filter);
});
}
- scope.$watch('relationFilters', function (newVal, prevVal) {
+ scope.relationFiltersWatch = scope.$watch('relationFilters', function (newVal, prevVal) {
if (!angular.equals(newVal, prevVal)) {
updateValue();
}
@@ -74,11 +78,16 @@ export default function RelationFilters($compile, $templateCache) {
}
function updateValue() {
- var value = [];
+ var value = ngModelCtrl.$viewValue;
+ if (!value) {
+ value = [];
+ ngModelCtrl.$setViewValue(value);
+ } else {
+ value.length = 0;
+ }
scope.relationFilters.forEach(function (filter) {
value.push(filter);
});
- ngModelCtrl.$setViewValue(value);
}
$compile(element.contents())(scope);
}
diff --git a/ui/src/app/entity/relation/relation-table.directive.js b/ui/src/app/entity/relation/relation-table.directive.js
index 3247811..872042c 100644
--- a/ui/src/app/entity/relation/relation-table.directive.js
+++ b/ui/src/app/entity/relation/relation-table.directive.js
@@ -41,7 +41,7 @@ export default function RelationTable() {
}
/*@ngInject*/
-function RelationTableController($scope, $q, $mdDialog, $document, $translate, $filter, utils, types, entityRelationService) {
+function RelationTableController($scope, $q, $mdDialog, $document, $translate, $filter, $timeout, utils, types, entityRelationService) {
let vm = this;
@@ -90,8 +90,15 @@ function RelationTableController($scope, $q, $mdDialog, $document, $translate, $
}
});
- function enterFilterMode () {
+ function enterFilterMode (event) {
+ let $button = angular.element(event.currentTarget);
+ let $toolbarsContainer = $button.closest('.toolbarsContainer');
+
vm.query.search = '';
+
+ $timeout(()=>{
+ $toolbarsContainer.find('.searchInput').focus();
+ })
}
function exitFilterMode () {
diff --git a/ui/src/app/entity/relation/relation-table.tpl.html b/ui/src/app/entity/relation/relation-table.tpl.html
index 16d422c..a2b0920 100644
--- a/ui/src/app/entity/relation/relation-table.tpl.html
+++ b/ui/src/app/entity/relation/relation-table.tpl.html
@@ -26,7 +26,7 @@
</md-select>
</md-input-container>
</section>
- <div layout="column" class="md-whiteframe-z1">
+ <div layout="column" class="md-whiteframe-z1 toolbarsContainer">
<md-toolbar class="md-table-toolbar md-default" ng-show="!vm.selectedRelations.length
&& vm.query.search === null">
<div class="md-toolbar-tools">
@@ -39,7 +39,7 @@
{{ 'action.add' | translate }}
</md-tooltip>
</md-button>
- <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+ <md-button class="md-icon-button" ng-click="vm.enterFilterMode($event)">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
@@ -64,7 +64,7 @@
</md-button>
<md-input-container flex>
<label> </label>
- <input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
+ <input ng-model="vm.query.search" class="searchInput" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
diff --git a/ui/src/app/extension/extension-table.directive.js b/ui/src/app/extension/extension-table.directive.js
index ecd3865..18d281c 100644
--- a/ui/src/app/extension/extension-table.directive.js
+++ b/ui/src/app/extension/extension-table.directive.js
@@ -45,7 +45,7 @@ export default function ExtensionTableDirective() {
}
/*@ngInject*/
-function ExtensionTableController($scope, $filter, $document, $translate, types, $mdDialog, attributeService, telemetryWebsocketService, importExport) {
+function ExtensionTableController($scope, $filter, $document, $translate, $timeout, $mdDialog, types, attributeService, telemetryWebsocketService, importExport) {
let vm = this;
@@ -141,11 +141,17 @@ function ExtensionTableController($scope, $filter, $document, $translate, types,
}
});
- function enterFilterMode() {
+ function enterFilterMode(event) {
+ let $button = angular.element(event.currentTarget);
+ let $toolbarsContainer = $button.closest('.toolbarsContainer');
+
vm.query.search = '';
if(vm.inWidget) {
vm.ctx.hideTitlePanel = true;
}
+ $timeout(()=>{
+ $toolbarsContainer.find('.searchInput').focus();
+ })
}
function exitFilterMode() {
diff --git a/ui/src/app/extension/extension-table.tpl.html b/ui/src/app/extension/extension-table.tpl.html
index 0ad2f4d..6a482b9 100644
--- a/ui/src/app/extension/extension-table.tpl.html
+++ b/ui/src/app/extension/extension-table.tpl.html
@@ -16,7 +16,7 @@
-->
<md-content flex class="md-padding tb-absolute-fill tb-data-table extension-table" layout="column">
- <div layout="column" class="md-whiteframe-z1" ng-class="{'tb-absolute-fill' : vm.inWidget}">
+ <div layout="column" class="md-whiteframe-z1 toolbarsContainer" ng-class="{'tb-absolute-fill' : vm.inWidget}">
<md-toolbar ng-if="!vm.inWidget" class="md-table-toolbar md-default" ng-show="!vm.selectedExtensions.length
&& vm.query.search === null">
<div class="md-toolbar-tools">
@@ -41,7 +41,7 @@
{{ 'action.add' | translate }}
</md-tooltip>
</md-button>
- <md-button class="md-icon-button" ng-click="vm.enterFilterMode()">
+ <md-button class="md-icon-button" ng-click="vm.enterFilterMode($event)">
<md-icon>search</md-icon>
<md-tooltip md-direction="top">
{{ 'action.search' | translate }}
@@ -66,7 +66,7 @@
</md-button>
<md-input-container flex>
<label> </label>
- <input ng-model="vm.query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
+ <input ng-model="vm.query.search" class="searchInput" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="{{ 'action.back' | translate }}" ng-click="vm.exitFilterMode()">
<md-icon aria-label="{{ 'action.close' | translate }}" class="material-icons">close</md-icon>
diff --git a/ui/src/app/help/help-links.constant.js b/ui/src/app/help/help-links.constant.js
index 1b708a9..458c118 100644
--- a/ui/src/app/help/help-links.constant.js
+++ b/ui/src/app/help/help-links.constant.js
@@ -20,6 +20,7 @@ var ruleNodeClazzHelpLinkMap = {
'org.thingsboard.rule.engine.filter.TbJsSwitchNode': 'ruleNodeJsSwitch',
'org.thingsboard.rule.engine.filter.TbMsgTypeFilterNode': 'ruleNodeMessageTypeFilter',
'org.thingsboard.rule.engine.filter.TbMsgTypeSwitchNode': 'ruleNodeMessageTypeSwitch',
+ 'org.thingsboard.rule.engine.filter.TbOriginatorTypeFilterNode': 'ruleNodeOriginatorTypeFilter',
'org.thingsboard.rule.engine.filter.TbOriginatorTypeSwitchNode': 'ruleNodeOriginatorTypeSwitch',
'org.thingsboard.rule.engine.metadata.TbGetAttributesNode': 'ruleNodeOriginatorAttributes',
'org.thingsboard.rule.engine.metadata.TbGetOriginatorFieldsNode': 'ruleNodeOriginatorFields',
@@ -31,7 +32,8 @@ var ruleNodeClazzHelpLinkMap = {
'org.thingsboard.rule.engine.transform.TbTransformMsgNode': 'ruleNodeTransformMsg',
'org.thingsboard.rule.engine.mail.TbMsgToEmailNode': 'ruleNodeMsgToEmail',
'org.thingsboard.rule.engine.action.TbClearAlarmNode': 'ruleNodeClearAlarm',
- 'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCrateAlarm',
+ 'org.thingsboard.rule.engine.action.TbCreateAlarmNode': 'ruleNodeCreateAlarm',
+ 'org.thingsboard.rule.engine.delay.TbMsgDelayNode': 'ruleNodeMsgDelay',
'org.thingsboard.rule.engine.debug.TbMsgGeneratorNode': 'ruleNodeMsgGenerator',
'org.thingsboard.rule.engine.action.TbLogNode': 'ruleNodeLog',
'org.thingsboard.rule.engine.rpc.TbSendRPCReplyNode': 'ruleNodeRpcCallReply',
@@ -61,6 +63,7 @@ export default angular.module('thingsboard.help', [])
ruleNodeJsSwitch: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#switch-node",
ruleNodeMessageTypeFilter: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-filter-node",
ruleNodeMessageTypeSwitch: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#message-type-switch-node",
+ ruleNodeOriginatorTypeFilter: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#originator-type-filter-node",
ruleNodeOriginatorTypeSwitch: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/filter-nodes/#originator-type-switch-node",
ruleNodeOriginatorAttributes: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/enrichment-nodes/#originator-attributes",
ruleNodeOriginatorFields: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/enrichment-nodes/#originator-fields",
@@ -72,7 +75,8 @@ export default angular.module('thingsboard.help', [])
ruleNodeTransformMsg: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/transformation-nodes/#script-transformation-node",
ruleNodeMsgToEmail: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/transformation-nodes/#to-email-node",
ruleNodeClearAlarm: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#clear-alarm-node",
- ruleNodeCrateAlarm: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#create-alarm-node",
+ ruleNodeCreateAlarm: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#create-alarm-node",
+ ruleNodeMsgDelay: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#delay-node",
ruleNodeMsgGenerator: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#generator-node",
ruleNodeLog: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#log-node",
ruleNodeRpcCallReply: helpBaseUrl + "/docs/user-guide/rule-engine-2-0/action-nodes/#rpc-call-reply-node",
ui/src/app/locale/locale.constant-it_IT.json 1445(+1445 -0)
diff --git a/ui/src/app/locale/locale.constant-it_IT.json b/ui/src/app/locale/locale.constant-it_IT.json
new file mode 100644
index 0000000..1454d36
--- /dev/null
+++ b/ui/src/app/locale/locale.constant-it_IT.json
@@ -0,0 +1,1445 @@
+{
+ "access": {
+ "unauthorized": "Non autorizzato",
+ "unauthorized-access": "Accesso non autorizzato",
+ "unauthorized-access-text": "Devi effettuare il login per accedere a questa risorsa!",
+ "access-forbidden": "Accesso Vietato",
+ "access-forbidden-text": "Non hai i diritti di accesso a questa posizione!<br/>Prova ad effettuare il login con un diverso account.",
+ "refresh-token-expired": "Sessione scaduta",
+ "refresh-token-failed": "Impossibile aggiornare la sessione"
+ },
+ "action": {
+ "activate": "Attiva",
+ "suspend": "Sospendi",
+ "save": "Salva",
+ "saveAs": "Salva come",
+ "cancel": "Cancella",
+ "ok": "OK",
+ "delete": "Elimina",
+ "add": "Aggiungi",
+ "yes": "Sì",
+ "no": "No",
+ "update": "Aggiorna",
+ "remove": "Rimuovi",
+ "search": "Cerca",
+ "clear-search": "Cancella ricerca",
+ "assign": "Assegna",
+ "unassign": "Annulla assegnazione",
+ "share": "Condividi",
+ "make-private": "Rendi privato",
+ "apply": "Applica",
+ "apply-changes": "Applica modifiche",
+ "edit-mode": "Modalità modifica",
+ "enter-edit-mode": "Attiva la modalità di modifica",
+ "decline-changes": "Annulla le modifiche",
+ "close": "Chiudi",
+ "back": "Indietro",
+ "run": "Esegui",
+ "sign-in": "Registrati!",
+ "edit": "Modifica",
+ "view": "Visualizza",
+ "create": "Crea",
+ "drag": "Trascina",
+ "refresh": "Aggiorna",
+ "undo": "Annulla",
+ "copy": "Copia",
+ "paste": "Incolla",
+ "copy-reference": "Copia riferimento",
+ "paste-reference": "Incolla riferimento",
+ "import": "Importa",
+ "export": "Esporta",
+ "share-via": "Condividi con {{provider}}"
+ },
+ "aggregation": {
+ "aggregation": "Aggregazione",
+ "function": "Funzione di aggregazione dei dati",
+ "limit": "Valori max",
+ "group-interval": "Intervallo di raggruppamento",
+ "min": "Min",
+ "max": "Max",
+ "avg": "Media",
+ "sum": "Somma",
+ "count": "Conteggio",
+ "none": "Nessuna"
+ },
+ "admin": {
+ "general": "Generale",
+ "general-settings": "Impostazioni Generali",
+ "outgoing-mail": "Posta in uscita",
+ "outgoing-mail-settings": "Impostazioni Posta in uscita",
+ "system-settings": "Impostazioni di sistema",
+ "test-mail-sent": "Mail di test inviata con successo!",
+ "base-url": "URL di base",
+ "base-url-required": "URL di base obbligatoria.",
+ "mail-from": "Mittente",
+ "mail-from-required": "Mittente obbligatorio.",
+ "smtp-protocol": "Protocollo SMTP",
+ "smtp-host": "Host SMTP",
+ "smtp-host-required": "Host SMTP obbligatorio.",
+ "smtp-port": "Porta SMTP",
+ "smtp-port-required": "Porta SMTP obbligatoria.",
+ "smtp-port-invalid": "Numero di porta SMTP non valido.",
+ "timeout-msec": "Timeout (msec)",
+ "timeout-required": "Timeout obbligatorio.",
+ "timeout-invalid": "Timeout non valido.",
+ "enable-tls": "Abilita TLS",
+ "send-test-mail": "Invia mail di test"
+ },
+ "alarm": {
+ "alarm": "Allarme",
+ "alarms": "Allarmi",
+ "select-alarm": "Seleziona un allarme",
+ "no-alarms-matching": "Nessun allarme corrispondente a '{{entity}}' è stato trovato.",
+ "alarm-required": "Allarme richiesto",
+ "alarm-status": "Stato Allarme",
+ "search-status": {
+ "ANY": "Qualsiasi",
+ "ACTIVE": "Attivo",
+ "CLEARED": "Cancellato",
+ "ACK": "Riconosciuto",
+ "UNACK": "Non riconosciuto"
+ },
+ "display-status": {
+ "ACTIVE_UNACK": "Active Unacknowledged",
+ "ACTIVE_ACK": "Active Acknowledged",
+ "CLEARED_UNACK": "Cleared Unacknowledged",
+ "CLEARED_ACK": "Cleared Acknowledged"
+ },
+ "no-alarms-prompt": "Nessun allarme trovato",
+ "created-time": "Orario di creazione",
+ "type": "Tipo",
+ "severity": "Gravità",
+ "originator": "Origine",
+ "originator-type": "Tipo origine",
+ "details": "Dettagli",
+ "status": "Stato",
+ "alarm-details": "Dettagli allarme",
+ "start-time": "Orario inizio",
+ "end-time": "Orario fine",
+ "ack-time": "Orario conferma",
+ "clear-time": "Orario cancellazione",
+ "severity-critical": "Critico",
+ "severity-major": "Maggiore",
+ "severity-minor": "Minore",
+ "severity-warning": "Avviso",
+ "severity-indeterminate": "Indeterminato",
+ "acknowledge": "Conferma",
+ "clear": "Cancella",
+ "search": "Ricerca allarmi",
+ "selected-alarms": "{ count, plural, 1 {1 allarme selezionato} other {# allarmi selezionati} }",
+ "no-data": "Nessun dato da visualizzare",
+ "polling-interval": "Intervallo di polling (sec) Allarmi",
+ "polling-interval-required": "Intervallo di polling Allarmi richiesto.",
+ "min-polling-interval-message": "L'intervallo di polling deve essere di almeno 1 sec.",
+ "aknowledge-alarms-title": "Conferma { count, plural, 1 {1 allarme} other {# allarmi} }",
+ "aknowledge-alarms-text": "Sei sicuro di voler confermare { count, plural, 1 {1 allarme} other {# allarmi} }?",
+ "clear-alarms-title": "Elimina { count, plural, 1 {1 allarme} other {# allarmi} }",
+ "clear-alarms-text": "Sei sicuro di voler eliminare { count, plural, 1 {1 allarme} other {# allarmi} }?"
+ },
+ "alias": {
+ "add": "Aggiungi alias",
+ "edit": "Modifica alias",
+ "name": "Nome Alias",
+ "name-required": "Nome Alias obbligatorio",
+ "duplicate-alias": "Un Alias con lo stesso nome è già presente.",
+ "filter-type-single-entity": "Singola entità",
+ "filter-type-entity-list": "Lista Entità",
+ "filter-type-entity-name": "Nome Entità",
+ "filter-type-state-entity": "Entity from dashboard state",
+ "filter-type-state-entity-description": "Entità prelevata dai parametri di stato della dashboard",
+ "filter-type-asset-type": "Tipo di Asset",
+ "filter-type-asset-type-description": "Asset di tipo '{{assetType}}'",
+ "filter-type-asset-type-and-name-description": "Asset di tipo '{{assetType}}' e con un nome che inizia per '{{prefix}}'",
+ "filter-type-device-type": "Tipo di dispositivo",
+ "filter-type-device-type-description": "Dispositivi di tipo '{{deviceType}}'",
+ "filter-type-device-type-and-name-description": "Dispositivi di tipo '{{deviceType}}' e con un nome che inizia per '{{prefix}}'",
+ "filter-type-relations-query": "Relations query",
+ "filter-type-relations-query-description": "{{entities}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
+ "filter-type-asset-search-query": "Asset search query",
+ "filter-type-asset-search-query-description": "Assets with types {{assetTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
+ "filter-type-device-search-query": "Device search query",
+ "filter-type-device-search-query-description": "Devices with types {{deviceTypes}} that have {{relationType}} relation {{direction}} {{rootEntity}}",
+ "entity-filter": "Filtro entità",
+ "resolve-multiple": "Resolve as multiple entities",
+ "filter-type": "Tipo di filtro",
+ "filter-type-required": "Tipo di filtro richiesto.",
+ "entity-filter-no-entity-matched": "Nessuna entità corrispondente al filtro specificato è stata trovata.",
+ "no-entity-filter-specified": "Nessun filtro di entità specificato",
+ "root-state-entity": "Use dashboard state entity as root",
+ "root-entity": "Entità radice",
+ "state-entity-parameter-name": "State entity parameter name",
+ "default-state-entity": "Default state entity",
+ "default-entity-parameter-name": "By default",
+ "max-relation-level": "Max relation level",
+ "unlimited-level": "Unlimited level",
+ "state-entity": "Dashboard state entity",
+ "all-entities": "Tutte le entità",
+ "any-relation": "qualsiasi"
+ },
+ "asset": {
+ "asset": "Asset",
+ "assets": "Asset",
+ "management": "Gestione Asset",
+ "view-assets": "Visualizza Asset",
+ "add": "Aggiungi Asset",
+ "assign-to-customer": "Assegna a cliente",
+ "assign-asset-to-customer": "Assegna Asset al Cliente",
+ "assign-asset-to-customer-text": "Seleziona gli asset da assegnare al cliente",
+ "no-assets-text": "Nessun asset trovato",
+ "assign-to-customer-text": "Seleziona il cliente a cui assegnare l'asset / gli asset",
+ "public": "Pubblico",
+ "assignedToCustomer": "Assegnato al cliente",
+ "make-public": "Rendi pubblico l'asset",
+ "make-private": "Rendi privato l'asset",
+ "unassign-from-customer": "Assegnazione annullata dal cliente",
+ "delete": "Cancella asset",
+ "asset-public": "L'Asset è pubblico",
+ "asset-type": "Tipo di Asset",
+ "asset-type-required": "Tipo di Asset richiesto.",
+ "select-asset-type": "Seleziona tipo di asset",
+ "enter-asset-type": "Inserisci tipo di asset",
+ "any-asset": "Qualsiasi asset",
+ "no-asset-types-matching": "Nessun asset corrispondente al tipo '{{entitySubtype}}' è stato trovato.",
+ "asset-type-list-empty": "Nessun tipo di asset selezionato.",
+ "asset-types": "Tipi di Asset",
+ "name": "Nome",
+ "name-required": "Nome obbligatorio.",
+ "description": "Descrizione",
+ "type": "Tipo",
+ "type-required": "Tipo obbligatorio.",
+ "details": "Dettagli",
+ "events": "Eventi",
+ "add-asset-text": "Aggiungi un nuovo asset",
+ "asset-details": "Dettagli Asset",
+ "assign-assets": "Assegna asset",
+ "assign-assets-text": "Assegna { count, plural, 1 {1 asset} other {# assets} } al cliente",
+ "delete-assets": "Cancella asset",
+ "unassign-assets": "Annulla assegnazione asset",
+ "unassign-assets-action-title": "Unassign { count, plural, 1 {1 asset} other {# assets} } from customer",
+ "assign-new-asset": "Assegna un nuovo asset",
+ "delete-asset-title": "Sei sicuro di voler cancellare l'asset '{{assetName}}'?",
+ "delete-asset-text": "Attenzione, dopo la conferma l'asset e tutti i relativi dati non saranno più recuperabili.",
+ "delete-assets-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 asset} other {# asset} }?",
+ "delete-assets-action-title": "Elimina { count, plural, 1 {1 asset} other {# asset} }",
+ "delete-assets-text": "Attenzione, dopo la modifica tutti gli asset selezionati saranno rimossi e tutti i relativi dati non saranno più recuperabili.",
+ "make-public-asset-title": "Sei sicuro di voler rendere pubblico l'asset '{{assetName}}'?",
+ "make-public-asset-text": "Dopo la conferma l'asset e tutti i suoi dati saranno resi pubblici e accessibili dagli altri.",
+ "make-private-asset-title": "Sei sicuro di voler rendere privato l'asset '{{assetName}}'?",
+ "make-private-asset-text": "Dopo la conferma l'asset e tutti i suoi dati saranno resi privati e non accessibili dagli altri.",
+ "unassign-asset-title": "Sei sicuro di voler annullare l'assegnazione dell'asset '{{assetName}}'?",
+ "unassign-asset-text": "Dopo la conferma l'assegnazione dell'asset sarà annullata e l'asset non sarà più accessibile dal cliente.",
+ "unassign-asset": "Annulla assegnazione asset",
+ "unassign-assets-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 asset} other {# asset} }?",
+ "unassign-assets-text": "Dopo la conferma sarà annullata l'assegnazione di tutti gli asset selezionati e questi non saranno più accessibili dal cliente.",
+ "copyId": "Copia Id asset",
+ "idCopiedMessage": "Id Asset copiato negli Appunti",
+ "select-asset": "Seleziona asset",
+ "no-assets-matching": "Nessun asset corrispondente a '{{entity}}' é stato trovato.",
+ "asset-required": "Asset obbligatorio",
+ "name-starts-with": "Asset con nome che inizia per"
+ },
+ "attribute": {
+ "attributes": "Attributi",
+ "latest-telemetry": "Ultima telemetria",
+ "attributes-scope": "Entity attributes scope",
+ "scope-latest-telemetry": "Ultima telemetria",
+ "scope-client": "Attributi client",
+ "scope-server": "Attributi server",
+ "scope-shared": "Attributi condivisi",
+ "add": "Aggiungi attributo",
+ "key": "Chiave",
+ "last-update-time": "Ultimo aggiornamento",
+ "key-required": "Attributo chiave richiesto.",
+ "value": "Valore",
+ "value-required": "Attributo valore richiesto.",
+ "delete-attributes-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 attributo} other {# attributi} }?",
+ "delete-attributes-text": "Attenzione, dopo la conferma tutti gli attributi selezionati saranno rimossi.",
+ "delete-attributes": "Elimina attributi",
+ "enter-attribute-value": "Inserisci il valore dell'attributo",
+ "show-on-widget": "Mostra sul widget",
+ "widget-mode": "Modalità Widget",
+ "next-widget": "Widget successivo",
+ "prev-widget": "Widget precedente",
+ "add-to-dashboard": "Aggiungi alla dashboard",
+ "add-widget-to-dashboard": "Aggiungi widget alla dashboard",
+ "selected-attributes": "{ count, plural, 1 {1 attributo selezionato} other {# attributi selezionati} }",
+ "selected-telemetry": "{ count, plural, 1 {1 unità di telemetria selezionata} other {# unità di telemetria selezionate} }"
+ },
+ "audit-log": {
+ "audit": "Audit",
+ "audit-logs": "Audit Logs",
+ "timestamp": "Timestamp",
+ "entity-type": "Tipo Entità",
+ "entity-name": "Nome Entità",
+ "user": "Utente",
+ "type": "Tipo",
+ "status": "Stato",
+ "details": "Dettagli",
+ "type-added": "Aggiunto",
+ "type-deleted": "Eliminato",
+ "type-updated": "Aggiornato",
+ "type-attributes-updated": "Attributi aggiornati",
+ "type-attributes-deleted": "Attributi eliminati",
+ "type-rpc-call": "Chiamata RPC",
+ "type-credentials-updated": "Credenziali aggiornate",
+ "type-assigned-to-customer": "Assegnato al Cliente",
+ "type-unassigned-from-customer": "Assegnazione annullata dal Cliente",
+ "type-activated": "Attivato",
+ "type-suspended": "Sospeso",
+ "type-credentials-read": "Credenziali lette",
+ "type-attributes-read": "Attributi letti",
+ "status-success": "Success",
+ "status-failure": "Failure",
+ "audit-log-details": "Dettaglio log audit",
+ "no-audit-logs-prompt": "Log non trovati",
+ "action-data": "Action data",
+ "failure-details": "Failure details",
+ "search": "Riceraca log audit",
+ "clear-search": "Cancella ricerca"
+ },
+ "confirm-on-exit": {
+ "message": "Alcune modifiche non sono state salvate. Sei sicuro di voler abbandonare questa pagina?",
+ "html-message": "Alcune modifiche non sono state salvate.<br/>Sei sicuro di voler abbandonare questa pagina?",
+ "title": "Modifiche non salvate"
+ },
+ "contact": {
+ "country": "Nazione",
+ "city": "Città",
+ "state": "Stato / Provincia",
+ "postal-code": "CAP",
+ "postal-code-invalid": "Formato CAP non valido.",
+ "address": "Indirizzo",
+ "address2": "Indirizzo 2",
+ "phone": "Telefono",
+ "email": "Email",
+ "no-address": "Nessun indirizzo"
+ },
+ "common": {
+ "username": "Nome utente",
+ "password": "Password",
+ "enter-username": "Inserisci nome utente",
+ "enter-password": "Inserisci password",
+ "enter-search": "Enter search"
+ },
+ "content-type": {
+ "json": "Json",
+ "text": "Testo",
+ "binary": "Binario (Base64)"
+ },
+ "customer": {
+ "customer": "Cliente",
+ "customers": "Clienti",
+ "management": "Gestione cliente",
+ "dashboard": "Dashboard cliente",
+ "dashboards": "Dashboard cliente",
+ "devices": "Dispositivi cliente",
+ "assets": "Asset cliente",
+ "public-dashboards": "Dashboard pubbliche",
+ "public-devices": "Dispositivi pubblici",
+ "public-assets": "Asset pubblici",
+ "add": "Aggiungi cliente",
+ "delete": "Elimina cliente",
+ "manage-customer-users": "Gestisci utenti cliente",
+ "manage-customer-devices": "Gestisci dispositivi cliente",
+ "manage-customer-dashboards": "Gestisci dashboard cliente",
+ "manage-public-devices": "Gestisci dispositivi pubblici",
+ "manage-public-dashboards": "Gestisci dashboard pubbliche",
+ "manage-customer-assets": "Gestisci asset cliente",
+ "manage-public-assets": "Gestisci asset pubblici",
+ "add-customer-text": "Aggiungi nuovo cliente",
+ "no-customers-text": "Nessun cliente trovato",
+ "customer-details": "Dettagli cliente",
+ "delete-customer-title": "Sei sicuro di voler eliminare il cliente '{{customerTitle}}'?",
+ "delete-customer-text": "Attenzione, dopo la conferma il cliente e tutti i suoi dati non saranno più recuperabili.",
+ "delete-customers-title": "Sei sicuro di voler cancellare { count, plural, 1 {1 cliente} other {# clienti} }?",
+ "delete-customers-action-title": "Elimina { count, plural, 1 {1 cliente} other {# clienti} }",
+ "delete-customers-text": "Attenzione, dopo la conferma tutti i clienti selezionati saranno rimossi e i loro dati non saranno più recuperabili.",
+ "manage-users": "Gestisci utenti",
+ "manage-assets": "Gestisci asset",
+ "manage-devices": "Gestisci dispositivi",
+ "manage-dashboards": "Gestisci dashboard",
+ "title": "Titolo",
+ "title-required": "Titolo obbligatorio.",
+ "description": "Descrizione",
+ "details": "Dettagli",
+ "events": "Eventi",
+ "copyId": "Copia Id cliente",
+ "idCopiedMessage": "Id cliente copiato negli appunti",
+ "select-customer": "Seleziona cliente",
+ "no-customers-matching": "Nessun cliente corrispondente a '{{entity}}' è stato trovato.",
+ "customer-required": "Cliente obbligatorio",
+ "select-default-customer": "Seleziona cliente di default",
+ "default-customer": "Cliente di default",
+ "default-customer-required": "Default customer is required in order to debug dashboard on Tenant level"
+ },
+ "datetime": {
+ "date-from": "Data da",
+ "time-from": "Orario da",
+ "date-to": "Data a",
+ "time-to": "Orario a"
+ },
+ "dashboard": {
+ "dashboard": "Dashboard",
+ "dashboards": "Dashboard",
+ "management": "Gestione Dashboard",
+ "view-dashboards": "Mostra Dashboard",
+ "add": "Aggiungi Dashboard",
+ "assign-dashboard-to-customer": "Assegna Dashboard al cliente",
+ "assign-dashboard-to-customer-text": "Seleziona le dashboard da assegnare al client",
+ "assign-to-customer-text": "Seleziona il cliente a cui assegnare la/le dashboard",
+ "assign-to-customer": "Assegna al cliente",
+ "unassign-from-customer": "Unassign from customer",
+ "make-public": "Rendi pubblica la dashboard",
+ "make-private": "Rendi privata la dashboard",
+ "manage-assigned-customers": "Gestisci i clienti assegnati",
+ "assigned-customers": "Clienti assegnati",
+ "assign-to-customers": "Assegna Dashboard ai Clienti",
+ "assign-to-customers-text": "Seleziona i clienti da assegnare alla/alle dashboard",
+ "unassign-from-customers": "Unassign Dashboard(s) From Customers",
+ "unassign-from-customers-text": "Seleziona i clienti di cui annullare l'assegnazione alla/alle dashboard",
+ "no-dashboards-text": "Nessuna dashboard trovata",
+ "no-widgets": "Nessun widget configurato",
+ "add-widget": "Aggiungi nuovo widget",
+ "title": "Titolo",
+ "select-widget-title": "Seleziona widget",
+ "select-widget-subtitle": "Elenco tipi di widget disponibili",
+ "delete": "Elimina dashboard",
+ "title-required": "Titolo obbligatorio.",
+ "description": "Descrizione",
+ "details": "Dettagli",
+ "dashboard-details": "Dettagli Dashboard",
+ "add-dashboard-text": "Aggiungi nuova dashboard",
+ "assign-dashboards": "Assegna dashboard",
+ "assign-new-dashboard": "Assegna nuova dashboard",
+ "assign-dashboards-text": "Assegna { count, plural, 1 {1 dashboard} other {# dashboard} } ai clienti",
+ "unassign-dashboards-action-text": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboards} } ai clienti",
+ "delete-dashboards": "Elimina dashboard",
+ "unassign-dashboards": "Annulla assegnazione dashboard",
+ "unassign-dashboards-action-title": "Annulla assegnazione { count, plural, 1 {1 dashboard} other {# dashboards} } al cliente",
+ "delete-dashboard-title": "Sei sicuro di voler cancellare la dashboard '{{dashboardTitle}}'?",
+ "delete-dashboard-text": "Attenzione, dopo la conferma la dashboard e tutti i suoi dati non saranno più recuperabili.",
+ "delete-dashboards-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 dashboard} other {# dashboard} }?",
+ "delete-dashboards-action-title": "Cancella { count, plural, 1 {1 dashboard} other {# dashboard} }",
+ "delete-dashboards-text": "Attenzione, dopo la conferma tutte le dashboard selezionate saranno eliminate e tutti i loro dati non saranno più recuperabili.",
+ "unassign-dashboard-title": "Sei sicuro di voler annullare l'assegnazione della dashboard '{{dashboardTitle}}'?",
+ "unassign-dashboard-text": "Dopo la conferma sarà annullata l'assegnazione della dashboard e questa non sarà più accessibile dal cliente.",
+ "unassign-dashboard": "Annulla assegnazione dashboard",
+ "unassign-dashboards-title": "Sei sicuro di voler annullare l'assegnazione di { count, plural, 1 {1 dashboard} other {# dashboard} }?",
+ "unassign-dashboards-text": "Dopo la conferma sarà annullata l'assegnazione di tutte le dashboards selezionate e queste non saranno più accessibili dal cliente.",
+ "public-dashboard-title": "La Dashboard è ora pubblica",
+ "public-dashboard-text": "La dashboard <b>{{dashboardTitle}}</b> è ora pubblica e accessibile al <a href='{{publicLink}}' target='_blank'>link</a>:",
+ "public-dashboard-notice": "<b>Nota:</b> Ricorda di rendere pubblici i relativi dispositivi per accedere ai loro dati.",
+ "make-private-dashboard-title": "Sei sicuro di voler rendere privata la dashboard '{{dashboardTitle}}'?",
+ "make-private-dashboard-text": "Dopo la conferma la dashboard sarà resa privata e non più accessibile dagli altri.",
+ "make-private-dashboard": "Rendi privata la dashboard",
+ "socialshare-text": "'{{dashboardTitle}}' powered by ThingsBoard",
+ "socialshare-title": "'{{dashboardTitle}}' powered by ThingsBoard",
+ "select-dashboard": "Seleziona dashboard",
+ "no-dashboards-matching": "Nessuna dashboard corrispondente a '{{entity}}' è stata trovata.",
+ "dashboard-required": "Dashboard obbligatoria.",
+ "select-existing": "Seleziona una dashboard esistente",
+ "create-new": "Crea nuova dashboard",
+ "new-dashboard-title": "Titolo nuova dashboard",
+ "open-dashboard": "Apri dashboard",
+ "set-background": "Imposta sfondo",
+ "background-color": "Colore sfondo",
+ "background-image": "Immagine sfondo",
+ "background-size-mode": "Background size mode",
+ "no-image": "Nessuna immagine selezionata",
+ "drop-image": "Trascina un'immagine o fai clic per selezionare un file da caricare.",
+ "settings": "Impostazioni",
+ "columns-count": "Numero colonne",
+ "columns-count-required": "Numero colonne obbligatorio.",
+ "min-columns-count-message": "Ammesso un numero minimo di colonne pari a 10.",
+ "max-columns-count-message": "Ammesso un numero massimo di colonne pari a 1000.",
+ "widgets-margins": "Margine tra i widget",
+ "horizontal-margin": "Margine orizzontale",
+ "horizontal-margin-required": "Margine orizzontale obbligatorio.",
+ "min-horizontal-margin-message": "Ammesso un margine orizzontale minimo pari a 0.",
+ "max-horizontal-margin-message": "Ammesso un margine orizzontale massimo pari a 50.",
+ "vertical-margin": "Margine verticale",
+ "vertical-margin-required": "Margine verticale obbligatorio.",
+ "min-vertical-margin-message": "Ammesso un margine verticale minimo pari a 0.",
+ "max-vertical-margin-message": "Ammesso un margine verticale massimo pari a 50.",
+ "autofill-height": "Auto fill layout height",
+ "mobile-layout": "Impostazioni layout mobile",
+ "mobile-row-height": "Mobile row height, px",
+ "mobile-row-height-required": "Mobile row height value is required.",
+ "min-mobile-row-height-message": "Only 5 pixels is allowed as minimum mobile row height value.",
+ "max-mobile-row-height-message": "Only 200 pixels is allowed as maximum mobile row height value.",
+ "display-title": "Mostra titolo dashboard",
+ "toolbar-always-open": "Mantieni aperta la barra degli strumenti",
+ "title-color": "Colore titolo",
+ "display-dashboards-selection": "Mostra selezione dashboard",
+ "display-entities-selection": "Mostra selezione entità",
+ "display-dashboard-timewindow": "Display timewindow",
+ "display-dashboard-export": "Mostra esportazione",
+ "import": "Importa dashboard",
+ "export": "Esporta dashboard",
+ "export-failed-error": "Impossibile esportare la dashboard: {{error}}",
+ "create-new-dashboard": "Crea nuova dashboard",
+ "dashboard-file": "File dashboard",
+ "invalid-dashboard-file-error": "Impossibile importare la dashboard: struttura dati della dashboard non valida.",
+ "dashboard-import-missing-aliases-title": "Configura alias utilizzati dalla dashboard importata",
+ "create-new-widget": "Crea nuovo widget",
+ "import-widget": "Importa widget",
+ "widget-file": "Widget file",
+ "invalid-widget-file-error": "Impossibile importare il widget: struttura dati del widget non valida.",
+ "widget-import-missing-aliases-title": "Configura gli alias utilizzati dai widget importati",
+ "open-toolbar": "Apri barra degli strumenti",
+ "close-toolbar": "Chiudi barra degli strumenti",
+ "configuration-error": "Errore di configurazione",
+ "alias-resolution-error-title": "Errore di configurazione degli alias della dashboard",
+ "invalid-aliases-config": "Impossibile trovare un dispositivo corrispondente ad un qualche filtro degli alias.<br/>Contatta l'amministratore per risolvere il problema.",
+ "select-devices": "Seleziona dispositivi",
+ "assignedToCustomer": "Assegnato al cliente",
+ "assignedToCustomers": "Assegnato ai clienti",
+ "public": "Pubblico",
+ "public-link": "Link pubblico",
+ "copy-public-link": "Copia link pubblico",
+ "public-link-copied-message": "Link pubblico della dashboard copiato negli appunti",
+ "manage-states": "Manage dashboard states",
+ "states": "Dashboard states",
+ "search-states": "Search dashboard states",
+ "selected-states": "{ count, plural, 1 {1 dashboard state} other {# dashboard states} } selected",
+ "edit-state": "Edit dashboard state",
+ "delete-state": "Delete dashboard state",
+ "add-state": "Add dashboard state",
+ "state": "Dashboard state",
+ "state-name": "Nome",
+ "state-name-required": "Dashboard state name is required.",
+ "state-id": "State Id",
+ "state-id-required": "Dashboard state id is required.",
+ "state-id-exists": "Dashboard state with the same id is already exists.",
+ "is-root-state": "Root state",
+ "delete-state-title": "Delete dashboard state",
+ "delete-state-text": "Are you sure you want delete dashboard state with name '{{stateName}}'?",
+ "show-details": "Mostra dettagli",
+ "hide-details": "Nascondi dettagli",
+ "select-state": "Select target state",
+ "state-controller": "Stato controller"
+ },
+ "datakey": {
+ "settings": "Impostazioni",
+ "advanced": "Avanzate",
+ "label": "Etichetta",
+ "color": "Colore",
+ "units": "Simbolo speciale da mostrare accanto al valore",
+ "decimals": "Numero cifre decimali",
+ "data-generation-func": "Funzione generazione dati",
+ "use-data-post-processing-func": "Use data post-processing function",
+ "configuration": "Data key configuration",
+ "timeseries": "Serie temporali",
+ "attributes": "Attributi",
+ "alarm": "Campi allarme",
+ "timeseries-required": "Entity timeseries are required.",
+ "timeseries-or-attributes-required": "Entity timeseries/attributes are required.",
+ "maximum-timeseries-or-attributes": "Maximum { count, plural, 1 {1 timeseries/attribute is allowed.} other {# timeseries/attributes are allowed} }",
+ "alarm-fields-required": "Campi allarme obbligatori.",
+ "function-types": "Tipi funzione",
+ "function-types-required": "Tipi funzione obbligatorio.",
+ "maximum-function-types": "Massimo { count, plural, 1 {1 tipo di funzione consentito.} other {# tipi di funzione consentiti} }"
+ },
+ "datasource": {
+ "type": "Tipo sorgente dati",
+ "name": "Nome",
+ "add-datasource-prompt": "Aggiungi una sorgente dati"
+ },
+ "details": {
+ "edit-mode": "Modalità modifica",
+ "toggle-edit-mode": "Toggle edit mode"
+ },
+ "device": {
+ "device": "Dispositivo",
+ "device-required": "Dispositivo richiesto.",
+ "devices": "Dispositivi",
+ "management": "Gestione dispositivo",
+ "view-devices": "Visualizza Dispositivi",
+ "device-alias": "Alias dispositivo",
+ "aliases": "Alias dispositivo",
+ "no-alias-matching": "'{{alias}}' non trovato.",
+ "no-aliases-found": "Nessun alias trovato.",
+ "no-key-matching": "'{{key}}' non trovata.",
+ "no-keys-found": "Nessuna chiave trovata.",
+ "create-new-alias": "Create a new one!",
+ "create-new-key": "Create a new one!",
+ "duplicate-alias-error": "Duplicate alias found '{{alias}}'.<br>Device aliases must be unique whithin the dashboard.",
+ "configure-alias": "Configura alias '{{alias}}'",
+ "no-devices-matching": "Nessun dispositivo corrispondente a '{{entity}}' é stato trovato.",
+ "alias": "Alias",
+ "alias-required": "Alias dispositivo richesto.",
+ "remove-alias": "Rimuovi alias dispositivo",
+ "add-alias": "Aggiungi alias dispositivo",
+ "name-starts-with": "Device name starts with",
+ "device-list": "Lista dispositivi",
+ "use-device-name-filter": "Usa filtro",
+ "device-list-empty": "Nessun dispositivo selezionato.",
+ "device-name-filter-required": "Device name filter is required.",
+ "device-name-filter-no-device-matched": "No devices starting with '{{device}}' were found.",
+ "add": "Aggiungi Dispositivo",
+ "assign-to-customer": "Assigna al cliente",
+ "assign-device-to-customer": "Assegna dispositivo/dispositivi al Cliente",
+ "assign-device-to-customer-text": "Seleziona i dispositivi da assegnare al cliente",
+ "make-public": "Rendi pubblico il dispositivo",
+ "make-private": "rendi privato il dispositivo",
+ "no-devices-text": "Nessun dispositivo trovato",
+ "assign-to-customer-text": "Seleziona il cliente a cui assegnare il dispositivo/i dispositivi",
+ "device-details": "Dettagli dispositivo",
+ "add-device-text": "Aggiungi nuovo dispositivo",
+ "credentials": "Credenziali",
+ "manage-credentials": "Gestisci credenziali",
+ "delete": "Elimina dispositivo",
+ "assign-devices": "Assegna dispositivi",
+ "assign-devices-text": "Assegna { count, plural, 1 {1 dispositivo} other {# dispositivi} } al cliente",
+ "delete-devices": "Elimina dispositivi",
+ "unassign-from-customer": "Annulla assegnazione al cliente",
+ "unassign-devices": "Annulla assegnazione dispositivi",
+ "unassign-devices-action-title": "Annulla assegnazione { count, plural, 1 {1 dispositivo} other {# dispositivi} } al cliente",
+ "assign-new-device": "Assegna nuovo dispositivo",
+ "make-public-device-title": "Sei sicuro di voler rendere pubblico il dispositivo '{{deviceName}}'?",
+ "make-public-device-text": "Dopo la conferma il dispositivo e tutti i suoi dati saranno resi pubblici e accessibili dagli altri.",
+ "make-private-device-title": "Sei sicuro di voler rendere privato il dispositivo '{{deviceName}}'?",
+ "make-private-device-text": "Dopo la conferma il dispositivo e tutti i suoi dati saranno resi privati e non più accessibili da altri utenti.",
+ "view-credentials": "Visualizza credenziali",
+ "delete-device-title": "Sei sicuro di voler eliminare il dispositivo '{{deviceName}}'?",
+ "delete-device-text": "Attenzione, dopo la conferma il dispositivo e tutti i suoi dati non saranno più recuperabili.",
+ "delete-devices-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 dispositivo} other {# dispositivi} }?",
+ "delete-devices-action-title": "Elimina { count, plural, 1 {1 dispositivo} other {# dispositivi} }",
+ "delete-devices-text": "Attenzione, dopo la conferma tutti i dispositivi selezionati saranno elimininati e i relativi dati non saranno più recuperabili.",
+ "unassign-device-title": "Sei sicuro di voler annullare l'assegnazione del dispositivo '{{deviceName}}'?",
+ "unassign-device-text": "Dopo la conferma sarà annullata l'assegnazione del dispositivo e questo non sarà più accessibile dal cliente.",
+ "unassign-device": "Annulla assegnazione dispositivo",
+ "unassign-devices-title": "Sei sicuro di voler annullare la'ssegnazione di { count, plural, 1 {1 dispositivo} other {# dispositivi} }?",
+ "unassign-devices-text": "Dopo la conferma sarà annullata l'assegnazione di tutti i dispositivi selezionati e questi non saranno più accessibili dal cliente.",
+ "device-credentials": "Credenziali Dispositivo",
+ "credentials-type": "Tipo credenziali",
+ "access-token": "Token di accesso",
+ "access-token-required": "Token di accesso obbligatorio.",
+ "access-token-invalid": "Il token di accesso deve avere una lunghezza compresa tra 1 e 20 caratteri.",
+ "rsa-key": "Chiave pubblica RSA",
+ "rsa-key-required": "Chiave pubblica RSA obbligatoria.",
+ "secret": "Secret",
+ "secret-required": "Secret obbligatorio.",
+ "device-type": "Tipo dispositivo",
+ "device-type-required": "Tipo dispositivo obbligatorio.",
+ "select-device-type": "Seleziona tipo dispositivo",
+ "enter-device-type": "Inserisci typo dispositivo",
+ "any-device": "Qualsiasi dispositivo",
+ "no-device-types-matching": "Nessun dispositivo corrispondente a '{{entitySubtype}}' è stato trovato.",
+ "device-type-list-empty": "Nessun tipo di dispositivo selezionato.",
+ "device-types": "Tipi dispositivo",
+ "name": "Nome",
+ "name-required": "Nome obbligatorio.",
+ "description": "Descrizione",
+ "events": "Eventi",
+ "details": "Dettagli",
+ "copyId": "Copia Id dispositivo",
+ "copyAccessToken": "Copia token di accesso",
+ "idCopiedMessage": "Id dispositivo copiato negli Appunti",
+ "accessTokenCopiedMessage": "Token di accesso del dispositivo copiato negli Appunti",
+ "assignedToCustomer": "Assegnato al cliente",
+ "unable-delete-device-alias-title": "Impossibile rimuovere l'alias del dispositivo",
+ "unable-delete-device-alias-text": "L'alias del dispositivo '{{deviceAlias}}' non può essere eliminato perchè utilizzato dai seguenti widget:<br/>{{widgetsList}}",
+ "is-gateway": "E' un gateway",
+ "public": "Pubblico",
+ "device-public": "Il dispositivo è pubblico",
+ "select-device": "Seleziona dispositivo"
+ },
+ "dialog": {
+ "close": "Close dialog"
+ },
+ "error": {
+ "unable-to-connect": "Impossibile connettersi al server! Controlla la connessione ad Internet.",
+ "unhandled-error-code": "Codice errore non gestito: {{errorCode}}",
+ "unknown-error": "Errore sconosciuto"
+ },
+ "entity": {
+ "entity": "Entità",
+ "entities": "Entità",
+ "aliases": "Alias entità",
+ "entity-alias": "Alias entità",
+ "unable-delete-entity-alias-title": "Impossibile eliminare alias entità",
+ "unable-delete-entity-alias-text": "L'alias dell'entità '{{entityAlias}}' non può essere eliminato perchè utilizzato dai seguenti widget:<br/>{{widgetsList}}",
+ "duplicate-alias-error": "Trovato un duplicato dell'alias '{{alias}}'.<br>Gli alias dell'entità devono essere univoci all'interno della dashboard.",
+ "missing-entity-filter-error": "Filter is missing for alias '{{alias}}'.",
+ "configure-alias": "Configura '{{alias}}' alias",
+ "alias": "Alias",
+ "alias-required": "Alias entità obbligatorio.",
+ "remove-alias": "Rimuovi alias entità",
+ "add-alias": "Aggiungi alias entità",
+ "entity-list": "Lista entità",
+ "entity-type": "Tipo entità",
+ "entity-types": "Tipi entità",
+ "entity-type-list": "Lista tipo entità",
+ "any-entity": "Qualsiasi entità",
+ "enter-entity-type": "Inserisci tipo entità",
+ "no-entities-matching": "Nessuna entità corrispondente a '{{entity}}' è stata trovata.",
+ "no-entity-types-matching": "Nessun tipo di entità corrispondente a '{{entityType}}' è stato trovato.",
+ "name-starts-with": "Nome inizia per",
+ "use-entity-name-filter": "Usa filtro",
+ "entity-list-empty": "Nessuna entità selezionata.",
+ "entity-type-list-empty": "Nessun tipo di entità selezionato.",
+ "entity-name-filter-required": "Filtro nome entità obbligatorio.",
+ "entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
+ "all-subtypes": "Tutte",
+ "select-entities": "Seleziona entità",
+ "no-aliases-found": "Nessun alias trovato.",
+ "no-alias-matching": "'{{alias}}' non trovato.",
+ "create-new-alias": "Create a new one!",
+ "key": "Chiave",
+ "key-name": "Nome chiave",
+ "no-keys-found": "Nessuna chiave trovata.",
+ "no-key-matching": "'{{key}}' non trovata.",
+ "create-new-key": "Create a new one!",
+ "type": "Tipo",
+ "type-required": "Tipo entità obbligatorio.",
+ "type-device": "Dispositivo",
+ "type-devices": "Dispositivi",
+ "list-of-devices": "{ count, plural, 1 {Un dispositivo} other {Lista di # dispositivi} }",
+ "device-name-starts-with": "Dispositivi i cui nomi iniziano per '{{prefix}}'",
+ "type-asset": "Asset",
+ "type-assets": "Asset",
+ "list-of-assets": "{ count, plural, 1 {Un asset} other {Lista di # asset} }",
+ "asset-name-starts-with": "Asset i cui nomi iniziano per '{{prefix}}'",
+ "type-rule": "Regola",
+ "type-rules": "Regole",
+ "list-of-rules": "{ count, plural, 1 {Una regola} other {Lista di # regole} }",
+ "rule-name-starts-with": "Regole i cui nomi iniziano per '{{prefix}}'",
+ "type-plugin": "Plugin",
+ "type-plugins": "Plugin",
+ "list-of-plugins": "{ count, plural, 1 {Un plugin} other {Lista di # plugin} }",
+ "plugin-name-starts-with": "Plugin i cui nomi iniziano per '{{prefix}}'",
+ "type-tenant": "Tenant",
+ "type-tenants": "Tenants",
+ "list-of-tenants": "{ count, plural, 1 {One tenant} other {List of # tenants} }",
+ "tenant-name-starts-with": "Tenants whose names start with '{{prefix}}'",
+ "type-customer": "Cliente",
+ "type-customers": "Clienti",
+ "list-of-customers": "{ count, plural, 1 {Un cliente} other {Lista di # clienti} }",
+ "customer-name-starts-with": "Clienti i cui nomi iniziano per '{{prefix}}'",
+ "type-user": "Utente",
+ "type-users": "Utenti",
+ "list-of-users": "{ count, plural, 1 {Un utente} other {Lista of # utenti} }",
+ "user-name-starts-with": "Utenti i cui nomi iniziano per '{{prefix}}'",
+ "type-dashboard": "Dashboard",
+ "type-dashboards": "Dashboard",
+ "list-of-dashboards": "{ count, plural, 1 {Una dashboard} other {Lista di # dashboard} }",
+ "dashboard-name-starts-with": "Dashboard i cui nomi iniziano per '{{prefix}}'",
+ "type-alarm": "Allarme",
+ "type-alarms": "Allarmi",
+ "list-of-alarms": "{ count, plural, 1 {Un allarme} other {Lista di # allarmi} }",
+ "alarm-name-starts-with": "Allarmi i cui nomi iniziano per '{{prefix}}'",
+ "type-rulechain": "Rule chain",
+ "type-rulechains": "Rule chains",
+ "list-of-rulechains": "{ count, plural, 1 {One rule chain} other {List of # rule chains} }",
+ "rulechain-name-starts-with": "Rule chains whose names start with '{{prefix}}'",
+ "type-current-customer": "Current Customer",
+ "search": "Ricerca entità",
+ "selected-entities": "{ count, plural, 1 {1 entità selezionata} other {# entità selezionate} }",
+ "entity-name": "Nome entità",
+ "details": "Dettagli entità",
+ "no-entities-prompt": "Nessuna entità trovata",
+ "no-data": "Nessun dato da mostrare"
+ },
+ "event": {
+ "event-type": "Tipo evento",
+ "type-error": "Errore",
+ "type-lc-event": "Ciclo di vita evento",
+ "type-stats": "Statistiche",
+ "type-debug-rule-node": "Debug",
+ "type-debug-rule-chain": "Debug",
+ "no-events-prompt": "Nessun evento trovato",
+ "error": "Errore",
+ "alarm": "Allarme",
+ "event-time": "Orario evento",
+ "server": "Server",
+ "body": "Body",
+ "method": "Metodo",
+ "type": "Tipo",
+ "entity": "Entità",
+ "message-id": "Id Messaggio",
+ "message-type": "Tipo Messaggio",
+ "data-type": "Data Type",
+ "relation-type": "Tipo di relazione",
+ "metadata": "Metadati",
+ "data": "Dati",
+ "event": "Evento",
+ "status": "Stato",
+ "success": "Success",
+ "failed": "Failed",
+ "messages-processed": "Messaggi elaborati",
+ "errors-occurred": "Si sono verificati degli errori"
+ },
+ "extension": {
+ "extensions": "Estensioni",
+ "selected-extensions": "{ count, plural, 1 {1 estensione selezionata} other {# estensioni selezionate} }",
+ "type": "Tipo",
+ "key": "Chiave",
+ "value": "Valore",
+ "id": "Id",
+ "extension-id": "Id Estensione",
+ "extension-type": "Tipo Estensione",
+ "transformer-json": "JSON *",
+ "unique-id-required": "Id estensione corrente già esistente.",
+ "delete": "Elimina estensione",
+ "add": "Aggiungi estensione",
+ "edit": "Modifica estensione",
+ "delete-extension-title": "Sei sicuro di voler eliminare l'estensione '{{extensionId}}'?",
+ "delete-extension-text": "Attenzione, dopo la conferma l'estensione e tutti i suoi data non saranno più recuperabili.",
+ "delete-extensions-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 estensione} other {# estensioni} }?",
+ "delete-extensions-text": "Attenzione, dopo la conferma tutte le estensioni selezionate saranno eliminate.",
+ "converters": "Converters",
+ "converter-id": "Converter id",
+ "configuration": "Configurazione",
+ "converter-configurations": "Converter configurations",
+ "token": "Token di sicurezza",
+ "add-converter": "Add converter",
+ "add-config": "Add converter configuration",
+ "device-name-expression": "Device name expression",
+ "device-type-expression": "Device type expression",
+ "custom": "Custom",
+ "to-double": "To Double",
+ "transformer": "Transformer",
+ "json-required": "Transformer json is required.",
+ "json-parse": "Unable to parse transformer json.",
+ "attributes": "Attributi",
+ "add-attribute": "Aggiungi attributo",
+ "add-map": "Add mapping element",
+ "timeseries": "Serie temporali",
+ "add-timeseries": "Add timeseries",
+ "field-required": "Field is required",
+ "brokers": "Broker",
+ "add-broker": "Aggiungi broker",
+ "host": "Host",
+ "port": "Porta",
+ "port-range": "Port should be in a range from 1 to 65535.",
+ "ssl": "Ssl",
+ "credentials": "Credenziali",
+ "username": "Nome utente",
+ "password": "Password",
+ "retry-interval": "Retry interval in milliseconds",
+ "anonymous": "Anonimo",
+ "basic": "Basic",
+ "pem": "PEM",
+ "ca-cert": "CA certificate file *",
+ "private-key": "File chiave privata *",
+ "cert": "File certificato *",
+ "no-file": "Nessun file selezionato.",
+ "drop-file": "Trascina un file o fai clic per selezionare un file da caricare.",
+ "mapping": "Mapping",
+ "topic-filter": "Filtro topic",
+ "converter-type": "Converter type",
+ "converter-json": "Json",
+ "json-name-expression": "Device name json expression",
+ "topic-name-expression": "Device name topic expression",
+ "json-type-expression": "Device type json expression",
+ "topic-type-expression": "Device type topic expression",
+ "attribute-key-expression": "Attribute key expression",
+ "attr-json-key-expression": "Attribute key json expression",
+ "attr-topic-key-expression": "Attribute key topic expression",
+ "request-id-expression": "Request id expression",
+ "request-id-json-expression": "Request id json expression",
+ "request-id-topic-expression": "Request id topic expression",
+ "response-topic-expression": "Response topic expression",
+ "value-expression": "Value expression",
+ "topic": "Topic",
+ "timeout": "Timeout in millisecondi",
+ "converter-json-required": "Convertitore json obbligatorio.",
+ "converter-json-parse": "Unable to parse converter json.",
+ "filter-expression": "Filter expression",
+ "connect-requests": "Connect requests",
+ "add-connect-request": "Add connect request",
+ "disconnect-requests": "Disconnect requests",
+ "add-disconnect-request": "Add disconnect request",
+ "attribute-requests": "Attribute requests",
+ "add-attribute-request": "Add attribute request",
+ "attribute-updates": "Attribute updates",
+ "add-attribute-update": "Add attribute update",
+ "server-side-rpc": "Server side RPC",
+ "add-server-side-rpc-request": "Add server-side RPC request",
+ "device-name-filter": "Device name filter",
+ "attribute-filter": "Attribute filter",
+ "method-filter": "Method filter",
+ "request-topic-expression": "Request topic expression",
+ "response-timeout": "Response timeout in milliseconds",
+ "topic-expression": "Topic expression",
+ "client-scope": "Client scope",
+ "add-device": "Aggiungi dispositivo",
+ "opc-server": "Server",
+ "opc-add-server": "Aggiungi server",
+ "opc-add-server-prompt": "Aggiungi server",
+ "opc-application-name": "Nome applicazione",
+ "opc-application-uri": "Uri applicazione",
+ "opc-scan-period-in-seconds": "Intervallo di scansione in secondi",
+ "opc-security": "Sicurezza",
+ "opc-identity": "Identità",
+ "opc-keystore": "Keystore",
+ "opc-type": "Tipo",
+ "opc-keystore-type": "Tipo",
+ "opc-keystore-location": "Location *",
+ "opc-keystore-password": "Password",
+ "opc-keystore-alias": "Alias",
+ "opc-keystore-key-password": "Chiave password",
+ "opc-device-node-pattern": "Device node pattern",
+ "opc-device-name-pattern": "Device name pattern",
+ "modbus-server": "Servers/slaves",
+ "modbus-add-server": "Aggiungi server/slave",
+ "modbus-add-server-prompt": "Aggiungi server/slave",
+ "modbus-transport": "Transport",
+ "modbus-port-name": "Nome porta seriale",
+ "modbus-encoding": "Codifica",
+ "modbus-parity": "Parità",
+ "modbus-baudrate": "Baud rate",
+ "modbus-databits": "Data bits",
+ "modbus-stopbits": "Stop bits",
+ "modbus-databits-range": "Data bits should be in a range from 7 to 8.",
+ "modbus-stopbits-range": "Stop bits should be in a range from 1 to 2.",
+ "modbus-unit-id": "Unit ID",
+ "modbus-unit-id-range": "Unit ID should be in a range from 1 to 247.",
+ "modbus-device-name": "Nome dispositivo",
+ "modbus-poll-period": "Intervallo di polling (ms)",
+ "modbus-attributes-poll-period": "Attributes poll period (ms)",
+ "modbus-timeseries-poll-period": "Timeseries poll period (ms)",
+ "modbus-poll-period-range": "L'intervallo di polling deve essere un valore positivo.",
+ "modbus-tag": "Tag",
+ "modbus-function": "Funzione",
+ "modbus-register-address": "Indirizzo registro",
+ "modbus-register-address-range": "L'indirizzo del registro deve essere compreso tra 0 e 65535.",
+ "modbus-register-bit-index": "Bit index",
+ "modbus-register-bit-index-range": "Bit index should be in a range from 0 to 15.",
+ "modbus-register-count": "Register count",
+ "modbus-register-count-range": "Register count should be a positive value.",
+ "modbus-byte-order": "Byte order",
+
+ "sync": {
+ "status": "Stato",
+ "sync": "Sincronizzato",
+ "not-sync": "Non sincronizzato",
+ "last-sync-time": "Ultima sincronizzazione",
+ "not-available": "Non disponibile"
+ },
+
+ "export-extensions-configuration": "Export extensions configuration",
+ "import-extensions-configuration": "Import extensions configuration",
+ "import-extensions": "Importa estensione",
+ "import-extension": "Importa estensione",
+ "export-extension": "Esporta estensione",
+ "file": "File estensione",
+ "invalid-file-error": "File estensione non valido"
+ },
+ "fullscreen": {
+ "expand": "Expand to fullscreen",
+ "exit": "Exit fullscreen",
+ "toggle": "Toggle fullscreen mode",
+ "fullscreen": "Fullscreen"
+ },
+ "function": {
+ "function": "Function"
+ },
+ "grid": {
+ "delete-item-title": "Are you sure you want to delete this item?",
+ "delete-item-text": "Be careful, after the confirmation this item and all related data will become unrecoverable.",
+ "delete-items-title": "Are you sure you want to delete { count, plural, 1 {1 item} other {# items} }?",
+ "delete-items-action-title": "Delete { count, plural, 1 {1 item} other {# items} }",
+ "delete-items-text": "Be careful, after the confirmation all selected items will be removed and all related data will become unrecoverable.",
+ "add-item-text": "Add new item",
+ "no-items-text": "No items found",
+ "item-details": "Item details",
+ "delete-item": "Delete Item",
+ "delete-items": "Delete Items",
+ "scroll-to-top": "Scroll to top"
+ },
+ "help": {
+ "goto-help-page": "Go to help page"
+ },
+ "home": {
+ "home": "Home",
+ "profile": "Profilo",
+ "logout": "Logout",
+ "menu": "Menu",
+ "avatar": "Avatar",
+ "open-user-menu": "Apri menu utente"
+ },
+ "import": {
+ "no-file": "Nessun file selezionato",
+ "drop-file": "Trascina un file JSON o fai clic per selezionare un file da caricare."
+ },
+ "item": {
+ "selected": "Selezionata"
+ },
+ "js-func": {
+ "no-return-error": "La funzione deve restituire un valore!",
+ "return-type-mismatch": "La funzione deve restituire un valore di tipo '{{type}}'!",
+ "tidy": "Tidy"
+ },
+ "key-val": {
+ "key": "Chiave",
+ "value": "Valore",
+ "remove-entry": "Remove entry",
+ "add-entry": "Add entry",
+ "no-data": "No entries"
+ },
+ "layout": {
+ "layout": "Layout",
+ "manage": "Gestisci layout",
+ "settings": "Impostazioni layout",
+ "color": "Colore",
+ "main": "Main",
+ "right": "Right",
+ "select": "Select target layout"
+ },
+ "legend": {
+ "position": "Posizione Legenda",
+ "show-max": "Mostra valore max",
+ "show-min": "Mostra valore min",
+ "show-avg": "Mostra valore medio",
+ "show-total": "Mostra valore totale",
+ "settings": "Impostazioni legenda",
+ "min": "min",
+ "max": "max",
+ "avg": "avg",
+ "total": "totale"
+ },
+ "login": {
+ "login": "Login",
+ "request-password-reset": "Request Password Reset",
+ "reset-password": "Azzera Password",
+ "create-password": "Crea Password",
+ "passwords-mismatch-error": "Le password inserite devono corrispondere!",
+ "password-again": "Ripeti Password",
+ "sign-in": "Please sign in",
+ "username": "Nome utente (email)",
+ "remember-me": "Ricordami",
+ "forgot-password": "Password dimenticata?",
+ "password-reset": "Password reset",
+ "new-password": "Nuova password",
+ "new-password-again": "Ripeti nuova password",
+ "password-link-sent-message": "Link azzeramento password inviato con successo!",
+ "email": "Email"
+ },
+ "position": {
+ "top": "Alto",
+ "bottom": "Basso",
+ "left": "Sinistra",
+ "right": "Destra"
+ },
+ "profile": {
+ "profile": "Profilo",
+ "change-password": "Modifica Password",
+ "current-password": "Password attuale"
+ },
+ "relation": {
+ "relations": "Relations",
+ "direction": "Direction",
+ "search-direction": {
+ "FROM": "Da",
+ "TO": "A"
+ },
+ "direction-type": {
+ "FROM": "da",
+ "TO": "a"
+ },
+ "from-relations": "Outbound relations",
+ "to-relations": "Inbound relations",
+ "selected-relations": "{ count, plural, 1 {1 relation} other {# relations} } selected",
+ "type": "Tipo",
+ "to-entity-type": "A tipo entità",
+ "to-entity-name": "A nome entità",
+ "from-entity-type": "Da tipo entità",
+ "from-entity-name": "Da nome entità",
+ "to-entity": "A entità",
+ "from-entity": "Da entità",
+ "delete": "Delete relation",
+ "relation-type": "Relation type",
+ "relation-type-required": "Relation type is required.",
+ "any-relation-type": "Ogni tipo",
+ "add": "Add relation",
+ "edit": "Edit relation",
+ "delete-to-relation-title": "Are you sure you want to delete relation to the entity '{{entityName}}'?",
+ "delete-to-relation-text": "Attenzione, dopo la conferma l'entità '{{entityName}}' sarà scollegata dall'entità corrente.",
+ "delete-to-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?",
+ "delete-to-relations-text": "Be careful, after the confirmation all selected relations will be removed and corresponding entities will be unrelated from the current entity.",
+ "delete-from-relation-title": "Are you sure you want to delete relation from the entity '{{entityName}}'?",
+ "delete-from-relation-text": "Be careful, after the confirmation current entity will be unrelated from the entity '{{entityName}}'.",
+ "delete-from-relations-title": "Are you sure you want to delete { count, plural, 1 {1 relation} other {# relations} }?",
+ "delete-from-relations-text": "Be careful, after the confirmation all selected relations will be removed and current entity will be unrelated from the corresponding entities.",
+ "remove-relation-filter": "Remove relation filter",
+ "add-relation-filter": "Add relation filter",
+ "any-relation": "Any relation",
+ "relation-filters": "Relation filters",
+ "additional-info": "Additional info (JSON)",
+ "invalid-additional-info": "Unable to parse additional info json."
+ },
+ "rulechain": {
+ "rulechain": "Rule chain",
+ "rulechains": "Rule chains",
+ "root": "Root",
+ "delete": "Delete rule chain",
+ "name": "Nome",
+ "name-required": "Nome obbligatorio.",
+ "description": "Descrizione",
+ "add": "Add Rule Chain",
+ "set-root": "Make rule chain root",
+ "set-root-rulechain-title": "Are you sure you want to make the rule chain '{{ruleChainName}}' root?",
+ "set-root-rulechain-text": "After the confirmation the rule chain will become root and will handle all incoming transport messages.",
+ "delete-rulechain-title": "Are you sure you want to delete the rule chain '{{ruleChainName}}'?",
+ "delete-rulechain-text": "Be careful, after the confirmation the rule chain and all related data will become unrecoverable.",
+ "delete-rulechains-title": "Are you sure you want to delete { count, plural, 1 {1 rule chain} other {# rule chains} }?",
+ "delete-rulechains-action-title": "Delete { count, plural, 1 {1 rule chain} other {# rule chains} }",
+ "delete-rulechains-text": "Be careful, after the confirmation all selected rule chains will be removed and all related data will become unrecoverable.",
+ "add-rulechain-text": "Add new rule chain",
+ "no-rulechains-text": "No rule chains found",
+ "rulechain-details": "Rule chain details",
+ "details": "Dettagli",
+ "events": "Eventi",
+ "system": "Sistema",
+ "import": "Import rule chain",
+ "export": "Export rule chain",
+ "export-failed-error": "Unable to export rule chain: {{error}}",
+ "create-new-rulechain": "Create new rule chain",
+ "rulechain-file": "Rule chain file",
+ "invalid-rulechain-file-error": "Unable to import rule chain: Invalid rule chain data structure.",
+ "copyId": "Copy rule chain Id",
+ "idCopiedMessage": "Rule chain Id has been copied to clipboard",
+ "select-rulechain": "Select rule chain",
+ "no-rulechains-matching": "No rule chains matching '{{entity}}' were found.",
+ "rulechain-required": "Rule chain is required",
+ "management": "Rules management",
+ "debug-mode": "Modalità debug"
+ },
+ "rulenode": {
+ "details": "Dettagli",
+ "events": "Eventi",
+ "search": "Ricerca nodi",
+ "open-node-library": "Apri libreria nodi",
+ "add": "Add rule node",
+ "name": "Nome",
+ "name-required": "Nome obbligatorio.",
+ "type": "Tipo",
+ "description": "Descrizione",
+ "delete": "Delete rule node",
+ "select-all-objects": "Seleziona tutti i nodi e le connessioni",
+ "deselect-all-objects": "Deselect all nodes and connections",
+ "delete-selected-objects": "Cancella nodi e connessioni selezionate",
+ "delete-selected": "Delete selected",
+ "select-all": "Seleziona tutto",
+ "copy-selected": "Copy selected",
+ "deselect-all": "Deselect all",
+ "rulenode-details": "Rule node details",
+ "debug-mode": "Modalità debug",
+ "configuration": "Configurazione",
+ "link": "Link",
+ "link-details": "Rule node link details",
+ "add-link": "Aggiungi link",
+ "link-label": "Etichetta link",
+ "link-label-required": "Etichetta link obbligatoria.",
+ "custom-link-label": "Custom link label",
+ "custom-link-label-required": "Custom link label is required.",
+ "type-filter": "Filtro",
+ "type-filter-details": "Filter incoming messages with configured conditions",
+ "type-enrichment": "Enrichment",
+ "type-enrichment-details": "Add additional information into Message Metadata",
+ "type-transformation": "Transformation",
+ "type-transformation-details": "Change Message payload and Metadata",
+ "type-action": "Azioni",
+ "type-action-details": "Perform special action",
+ "type-external": "External",
+ "type-external-details": "Interacts with external system",
+ "type-rule-chain": "Rule Chain",
+ "type-rule-chain-details": "Forwards incoming messages to specified Rule Chain",
+ "type-input": "Input",
+ "type-input-details": "Logical input of Rule Chain, forwards incoming messages to next related Rule Node",
+ "directive-is-not-loaded": "Defined configuration directive '{{directiveName}}' is not available.",
+ "ui-resources-load-error": "Failed to load configuration ui resources.",
+ "invalid-target-rulechain": "Unable to resolve target rule chain!",
+ "test-script-function": "Test script function",
+ "message": "Messaggio",
+ "message-type": "Tipo messaggio",
+ "message-type-required": "Tipo messaggio obbligatorio",
+ "metadata": "Metadata",
+ "metadata-required": "Metadata entries can't be empty.",
+ "output": "Output",
+ "test": "Test",
+ "help": "Aiuto"
+ },
+ "tenant": {
+ "tenant": "Tenant",
+ "tenants": "Tenant",
+ "management": "Gestione Tenant",
+ "add": "Aggiungi Tenant",
+ "admins": "Amministratori",
+ "manage-tenant-admins": "Gestisci amministratori tenant",
+ "delete": "Cancella tenant",
+ "add-tenant-text": "Aggiungi nuovo tenant",
+ "no-tenants-text": "Nessun tenant trovato",
+ "tenant-details": "Dettagli tenant",
+ "delete-tenant-title": "Sei sicuro di voler eliminare il tenant '{{tenantTitle}}'?",
+ "delete-tenant-text": "Attenzione, dopo la conferma il tenant e tutti i suoi dati non saranno più recuperabili.",
+ "delete-tenants-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 tenant} other {# tenant} }?",
+ "delete-tenants-action-title": "Elimina { count, plural, 1 {1 tenant} other {# tenant} }",
+ "delete-tenants-text": "Attenzione, dopo la conferma tutti i tenant selezionati saranno eliminati e tutti i loro dati non saranno più recuperabili.",
+ "title": "Titolo",
+ "title-required": "Titolo obbligatorio.",
+ "description": "Descrizione",
+ "details": "Dettagli",
+ "events": "Eventi",
+ "copyId": "Copia Id tenant",
+ "idCopiedMessage": "Id tenant copiato negli appunti",
+ "select-tenant": "Seleziona tenant",
+ "no-tenants-matching": "Nessun tenant corrispondente a '{{entity}}' è stato trovato.",
+ "tenant-required": "Tenant obbligatorio"
+ },
+ "timeinterval": {
+ "seconds-interval": "{ seconds, plural, 1 {1 secondo} other {# secondi} }",
+ "minutes-interval": "{ minutes, plural, 1 {1 minuto} other {# minuti} }",
+ "hours-interval": "{ hours, plural, 1 {1 ora} other {# ore} }",
+ "days-interval": "{ days, plural, 1 {1 giorno} other {# giorni} }",
+ "days": "Giorni",
+ "hours": "Ore",
+ "minutes": "Minuti",
+ "seconds": "Secondi",
+ "advanced": "Avanzate"
+ },
+ "timewindow": {
+ "days": "{ days, plural, 1 { giorno } other {# giorni } }",
+ "hours": "{ hours, plural, 0 { hour } 1 {1 ora } other {# ore } }",
+ "minutes": "{ minutes, plural, 0 { minute } 1 {1 minuto } other {# minuti } }",
+ "seconds": "{ seconds, plural, 0 { second } 1 {1 secondo } other {# secondi } }",
+ "realtime": "Realtime",
+ "history": "Cronologia",
+ "last-prefix": "last",
+ "period": "from {{ startTime }} to {{ endTime }}",
+ "edit": "Edit timewindow",
+ "date-range": "Date range",
+ "last": "Last",
+ "time-period": "Time period"
+ },
+ "user": {
+ "user": "Utente",
+ "users": "Utenti",
+ "customer-users": "Customer Users",
+ "tenant-admins": "Amministratori Tenant",
+ "sys-admin": "Amministratore di sistema",
+ "tenant-admin": "Amministratore tenant",
+ "customer": "Cliente",
+ "anonymous": "Anonimo",
+ "add": "Aggiungi Utente",
+ "delete": "Elimina utente",
+ "add-user-text": "Aggiungi nuovo utente",
+ "no-users-text": "Nessun utente trovato",
+ "user-details": "Dettagli utente",
+ "delete-user-title": "Sei sicuro di voler eliminare l'utente '{{userEmail}}'?",
+ "delete-user-text": "Attenzione, dopo la conferma l'utente e tutti i suoi dati non saranno più recuperabili.",
+ "delete-users-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 utente} other {# utenti} }?",
+ "delete-users-action-title": "Elimina { count, plural, 1 {1 utente} other {# utenti} }",
+ "delete-users-text": "Attenzione, dopo la conferma tutti gli utenti selezionati saranno eliminati e tutti i relativi dati non saranno più recuperabili.",
+ "activation-email-sent-message": "Email di attivazione inviata con successo!",
+ "resend-activation": "Resend activation",
+ "email": "Email",
+ "email-required": "Email obbligatoria.",
+ "invalid-email-format": "Formato email non valido.",
+ "first-name": "Nome",
+ "last-name": "Cognome",
+ "description": "Descrizione",
+ "default-dashboard": "Dashboard di default",
+ "always-fullscreen": "Always fullscreen",
+ "select-user": "Seleziona utente",
+ "no-users-matching": "Nessun utente corrispondente a '{{entity}}' è stato trovato.",
+ "user-required": "Utente obbligatorio",
+ "activation-method": "Metodo di attivazione",
+ "display-activation-link": "Mostra link di attivazione",
+ "send-activation-mail": "Invia email di attivazione",
+ "activation-link": "Link attivazione utente",
+ "activation-link-text": "Per attivare l'utente utilizza il seguente <a href='{{activationLink}}' target='_blank'>link di attivazione</a> :",
+ "copy-activation-link": "Copia link di attivazione",
+ "activation-link-copied-message": "Link di attivazione utente copiato negli appunti",
+ "details": "Dettagli"
+ },
+ "value": {
+ "type": "Tipo valore",
+ "string": "String",
+ "string-value": "Valore string",
+ "integer": "Integer",
+ "integer-value": "Valore integer",
+ "invalid-integer-value": "Valore integer non valido",
+ "double": "Double",
+ "double-value": "Valore double",
+ "boolean": "Boolean",
+ "boolean-value": "Valore boolean",
+ "false": "Falso",
+ "true": "Vero",
+ "long": "Long"
+ },
+ "widget": {
+ "widget-library": "Libreria Widget",
+ "widget-bundle": "Bundle widget",
+ "select-widgets-bundle": "Seleziona bundle widget",
+ "management": "Gestione widget",
+ "editor": "Editor Widget",
+ "widget-type-not-found": "Problem loading widget configuration.<br>Probably associated\n widget type was removed.",
+ "widget-type-load-error": "Widget non caricato a causa dei seguenti errori:",
+ "remove": "Elimina widget",
+ "edit": "Modifica widget",
+ "remove-widget-title": "sei sicuro di voler eliminare il widget '{{widgetTitle}}'?",
+ "remove-widget-text": "Dopo la conferma il widget e tutti i suoi dati non saranno più recuperabili.",
+ "timeseries": "Time series",
+ "search-data": "Cerca dati",
+ "no-data-found": "Nessun dato trovato",
+ "latest-values": "Ultimi valori",
+ "rpc": "Control widget",
+ "alarm": "Alarm widget",
+ "static": "Static widget",
+ "select-widget-type": "Seleziona tipo widget",
+ "missing-widget-title-error": "Il tiolo del widget deve essere specificato!",
+ "widget-saved": "Widget salvato",
+ "unable-to-save-widget-error": "Impossibile salvare il widget! Sono presenti degli errori!",
+ "save": "Salva widget",
+ "saveAs": "Salva widget come",
+ "save-widget-type-as": "Salva tipo widget come",
+ "save-widget-type-as-text": "Please enter new widget title and/or select target widgets bundle",
+ "toggle-fullscreen": "Toggle fullscreen",
+ "run": "Esegui widget",
+ "title": "Titolo widget",
+ "title-required": "Titolo widget obbligatorio.",
+ "type": "Tipo widget",
+ "resources": "Risorse",
+ "resource-url": "JavaScript/CSS URL",
+ "remove-resource": "Rimuovi risorsa",
+ "add-resource": "Aggiungi risorsa",
+ "html": "HTML",
+ "tidy": "Tidy",
+ "css": "CSS",
+ "settings-schema": "Impostazioni schema",
+ "datakey-settings-schema": "Data key settings schema",
+ "javascript": "Javascript",
+ "remove-widget-type-title": "Sei sicuro di voler rimuovere il tipo di widget '{{widgetName}}'?",
+ "remove-widget-type-text": "Dopo la conferma il tipo di widget e tutti i suoi dati non saranno più recuperabili.",
+ "remove-widget-type": "Rimuovi tipo widget",
+ "add-widget-type": "Aggiungi nuovo tipo widget",
+ "widget-type-load-failed-error": "Caricamento tipo widget fallito!",
+ "widget-template-load-failed-error": "Caricamento template widget fallito!",
+ "add": "Aggiungi Widget",
+ "undo": "Annulla modifiche widget",
+ "export": "Esporta widget"
+ },
+ "widget-action": {
+ "header-button": "Widget header button",
+ "open-dashboard-state": "Navigate to new dashboard state",
+ "update-dashboard-state": "Update current dashboard state",
+ "open-dashboard": "Navigate to other dashboard",
+ "custom": "Custom action",
+ "target-dashboard-state": "Target dashboard state",
+ "target-dashboard-state-required": "Target dashboard state is required",
+ "set-entity-from-widget": "Set entity from widget",
+ "target-dashboard": "Target dashboard",
+ "open-right-layout": "Open right dashboard layout (mobile view)"
+ },
+ "widgets-bundle": {
+ "current": "Bundle corrente",
+ "widgets-bundles": "Bundle Widget",
+ "add": "Aggiungi Bundle Widget",
+ "delete": "Cancella bundle widget",
+ "title": "Titolo",
+ "title-required": "Titolo obbligatorio.",
+ "add-widgets-bundle-text": "Aggiungi nuovo bundle widget",
+ "no-widgets-bundles-text": "Nessun bundle widget trovato",
+ "empty": "Bundle widget vuoto",
+ "details": "Dettagli",
+ "widgets-bundle-details": "Dettagli bundle widget",
+ "delete-widgets-bundle-title": "Sei sicuro di voler eliminare il bundle widget '{{widgetsBundleTitle}}'?",
+ "delete-widgets-bundle-text": "Attenzione, dopo la conferma il bundle widget e tutti i suoi dati non saranno più recuperabili.",
+ "delete-widgets-bundles-title": "Sei sicuro di voler eliminare { count, plural, 1 {1 bundle widget} other {# bundle widget} }?",
+ "delete-widgets-bundles-action-title": "Elimina { count, plural, 1 {1 bundle widget} other {# bundle widget} }",
+ "delete-widgets-bundles-text": "Attenzione, dopo la conferma tutti i bundle widget selezionati saranno rimossi e tutti i loro dati non saranno più recuperabili.",
+ "no-widgets-bundles-matching": "Nessun bundle widget corrispondente a '{{widgetsBundle}}' è stato trovato.",
+ "widgets-bundle-required": "Bundle widget obbligatorio.",
+ "system": "Sistema",
+ "import": "Importa bundle widget",
+ "export": "Esporta bundle widget",
+ "export-failed-error": "Impossibile esportare bundle widget: {{error}}",
+ "create-new-widgets-bundle": "Crea nuovo bundle widget",
+ "widgets-bundle-file": "File bundle widget",
+ "invalid-widgets-bundle-file-error": "Impossibile importare bundle widget: struttura dati non valida."
+ },
+ "widget-config": {
+ "data": "Dati",
+ "settings": "Impostazioni",
+ "advanced": "Avanzate",
+ "title": "Titolo",
+ "general-settings": "Impostazioni generali",
+ "display-title": "Mostra titolo",
+ "drop-shadow": "Drop shadow",
+ "enable-fullscreen": "Abilita fullscreen",
+ "background-color": "Colore sfondo",
+ "text-color": "Colore testo",
+ "padding": "Padding",
+ "margin": "Margin",
+ "widget-style": "Stile Widget",
+ "title-style": "Stile titolo",
+ "mobile-mode-settings": "Impostazioni modalità mobile",
+ "order": "Ordinamento",
+ "height": "Altezza",
+ "units": "Simbolo speciale da mostrare vicino al valore",
+ "decimals": "Numero di cifre decimali",
+ "timewindow": "Timewindow",
+ "use-dashboard-timewindow": "Use dashboard timewindow",
+ "display-legend": "Mostra legenda",
+ "datasources": "Sorgenti dei dati",
+ "maximum-datasources": "Massimo { count, plural, 1 {1 sorgente dati consentita.} other {# sorgenti dati consentite} }",
+ "datasource-type": "Tipo",
+ "datasource-parameters": "Parametri",
+ "remove-datasource": "Rimuovi sorgente dati",
+ "add-datasource": "Aggiungi sorgente dati",
+ "target-device": "Dispositivo Target",
+ "alarm-source": "Sorgente Allarme",
+ "actions": "Azioni",
+ "action": "Azione",
+ "add-action": "Aggiungi azione",
+ "search-actions": "Ricerca azioni",
+ "action-source": "Sorgente azione",
+ "action-source-required": "Sorgente azione obbligatoria.",
+ "action-name": "Nome",
+ "action-name-required": "Nome azione obbligatorio.",
+ "action-name-not-unique": "Un'altra azione con lo stesso nome è già presente.<br/>Il nome di una azione dovrebbe essere univoco all'interno della stessa sorgente.",
+ "action-icon": "Icona",
+ "action-type": "Tipo",
+ "action-type-required": "Tipo azione obbligatorio.",
+ "edit-action": "Modifica azione",
+ "delete-action": "Cancella azione",
+ "delete-action-title": "Cancella azione del widget",
+ "delete-action-text": "Sei sicuro di voler cancellare l'azione del widget '{{actionName}}'?"
+ },
+ "widget-type": {
+ "import": "Importa un tipo di widget",
+ "export": "Esporta un tipo di widget",
+ "export-failed-error": "Impossibile esportare il tipo di widget: {{error}}",
+ "create-new-widget-type": "Crea un nuovo tipo di widget",
+ "widget-type-file": "Widget type file",
+ "invalid-widget-type-file-error": "Impossibile importare un tipo di widget: struttura dati del widget non valida."
+ },
+ "icon": {
+ "icon": "Icona",
+ "select-icon": "Seleziona icona",
+ "material-icons": "Icone Material",
+ "show-all": "Mostra tutte le icone"
+ },
+ "custom": {
+ "widget-action": {
+ "action-cell-button": "Action cell button",
+ "row-click": "On row click",
+ "marker-click": "On marker click",
+ "tooltip-tag-action": "Tooltip tag action"
+ }
+ },
+ "language": {
+ "language": "Lingua",
+ "locales": {
+ "zh_CN": "Cinese",
+ "ko_KR": "Coreano",
+ "en_US": "Inglese",
+ "it_IT": "Italiano",
+ "ru_RU": "Russo",
+ "es_ES": "Spagnolo"
+ }
+ }
+}
diff --git a/ui/src/app/locale/translate-handler.js b/ui/src/app/locale/translate-handler.js
index 11feb00..9fb28f5 100644
--- a/ui/src/app/locale/translate-handler.js
+++ b/ui/src/app/locale/translate-handler.js
@@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
+ export default angular.module('thingsboard.locale', [])
+ .factory('tbMissingTranslationHandler', ThingsboardMissingTranslateHandler)
+ .name;
+
/*@ngInject*/
-export default function ThingsboardMissingTranslateHandler($log, types) {
+function ThingsboardMissingTranslateHandler($log, types) {
return function (translationId) {
if (translationId && !translationId.startsWith(types.translate.customTranslationsPrefix)) {
diff --git a/ui/src/app/profile/profile.controller.js b/ui/src/app/profile/profile.controller.js
index d17a65a..b947bde 100644
--- a/ui/src/app/profile/profile.controller.js
+++ b/ui/src/app/profile/profile.controller.js
@@ -24,16 +24,9 @@ export default function ProfileController(userService, $scope, $document, $mdDia
var vm = this;
vm.profileUser = {};
-
vm.save = save;
vm.changePassword = changePassword;
- vm.languageList = {
- en_US: {value : "en_US", name: "language.en_US"},
- ko_KR: {value : "ko_KR", name: "language.ko_KR"},
- zh_CN: {value : "zh_CN", name: "language.zh_CN"},
- ru_RU: {value : "ru_RU", name: "language.ru_RU"},
- es_ES: {value : "es_ES", name: "language.es_ES"},
- };
+ vm.languageList = SUPPORTED_LANGS; //eslint-disable-line
loadProfile();
ui/src/app/profile/profile.tpl.html 4(+2 -2)
diff --git a/ui/src/app/profile/profile.tpl.html b/ui/src/app/profile/profile.tpl.html
index a0358c1..26d9925 100644
--- a/ui/src/app/profile/profile.tpl.html
+++ b/ui/src/app/profile/profile.tpl.html
@@ -43,8 +43,8 @@
<md-input-container class="md-block">
<label translate>language.language</label>
<md-select name="language" ng-model="vm.profileUser.additionalInfo.lang">
- <md-option ng-repeat="lang in vm.languageList" ng-value="lang.value">
- {{lang.name | translate}}
+ <md-option ng-repeat="lang in vm.languageList" ng-value="lang">
+ {{ lang ? ('language.locales.' + lang | translate) : ''}}
</md-option>
</md-select>
</md-input-container>
diff --git a/ui/src/app/rulechain/add-link.tpl.html b/ui/src/app/rulechain/add-link.tpl.html
index 0a21104..c5855b7 100644
--- a/ui/src/app/rulechain/add-link.tpl.html
+++ b/ui/src/app/rulechain/add-link.tpl.html
@@ -31,7 +31,7 @@
<span style="min-height: 5px;" flex="" ng-show="!$root.loading"></span>
<md-dialog-content>
<div class="md-dialog-content">
- <tb-rule-node-link link="vm.link" labels="vm.labels" is-edit="true" the-form="theForm"></tb-rule-node-link>
+ <tb-rule-node-link ng-model="vm.link" allowed-labels="vm.labels" is-edit="true" allow-custom="vm.allowCustomLabels"></tb-rule-node-link>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
ui/src/app/rulechain/index.js 2(+2 -0)
diff --git a/ui/src/app/rulechain/index.js b/ui/src/app/rulechain/index.js
index 7740dd0..1ed3680 100644
--- a/ui/src/app/rulechain/index.js
+++ b/ui/src/app/rulechain/index.js
@@ -23,6 +23,7 @@ import RuleNodeDefinedConfigDirective from './rulenode-defined-config.directive'
import RuleNodeConfigDirective from './rulenode-config.directive';
import RuleNodeDirective from './rulenode.directive';
import LinkDirective from './link.directive';
+import MessageTypeAutocompleteDirective from './message-type-autocomplete.directive';
import NodeScriptTest from './script/node-script-test.service';
export default angular.module('thingsboard.ruleChain', [])
@@ -37,5 +38,6 @@ export default angular.module('thingsboard.ruleChain', [])
.directive('tbRuleNodeConfig', RuleNodeConfigDirective)
.directive('tbRuleNode', RuleNodeDirective)
.directive('tbRuleNodeLink', LinkDirective)
+ .directive('tbMessageTypeAutocomplete', MessageTypeAutocompleteDirective)
.factory('ruleNodeScriptTest', NodeScriptTest)
.name;
ui/src/app/rulechain/link.directive.js 117(+89 -28)
diff --git a/ui/src/app/rulechain/link.directive.js b/ui/src/app/rulechain/link.directive.js
index b3565a3..40c7a61 100644
--- a/ui/src/app/rulechain/link.directive.js
+++ b/ui/src/app/rulechain/link.directive.js
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+import './link.scss';
+
/* eslint-disable import/no-unresolved, import/default */
import linkFieldsetTemplate from './link-fieldset.tpl.html';
@@ -22,50 +24,109 @@ import linkFieldsetTemplate from './link-fieldset.tpl.html';
/*@ngInject*/
export default function LinkDirective($compile, $templateCache, $filter) {
- var linker = function (scope, element) {
+ var linker = function (scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get(linkFieldsetTemplate);
element.html(template);
scope.selectedLabel = null;
+ scope.labelSearchText = null;
+
+ scope.ngModelCtrl = ngModelCtrl;
+
+ var labelsList = [];
+
+ scope.transformLinkLabelChip = function (chip) {
+ var res = $filter('filter')(labelsList, {name: chip}, true);
+ var result;
+ if (res && res.length) {
+ result = angular.copy(res[0]);
+ } else {
+ result = {
+ name: chip,
+ value: chip
+ };
+ }
+ return result;
+ };
+
+ scope.labelsSearch = function (searchText) {
+ var labels = searchText ? $filter('filter')(labelsList, {name: searchText}) : labelsList;
+ return labels.map((label) => label.name);
+ };
+
+ scope.createLinkLabel = function (event, chipsId) {
+ var chipsChild = angular.element(chipsId, element)[0].firstElementChild;
+ var el = angular.element(chipsChild);
+ var chipBuffer = el.scope().$mdChipsCtrl.getChipBuffer();
+ event.preventDefault();
+ event.stopPropagation();
+ el.scope().$mdChipsCtrl.appendChip(chipBuffer.trim());
+ el.scope().$mdChipsCtrl.resetChipBuffer();
+ };
- scope.$watch('link', function() {
- scope.selectedLabel = null;
- if (scope.link && scope.labels) {
- if (scope.link.label) {
- var result = $filter('filter')(scope.labels, {name: scope.link.label});
- if (result && result.length) {
- scope.selectedLabel = result[0];
- } else {
- result = $filter('filter')(scope.labels, {custom: true});
- if (result && result.length && result[0].custom) {
- scope.selectedLabel = result[0];
- }
- }
- }
- }
- });
-
- scope.selectedLabelChanged = function() {
- if (scope.link && scope.selectedLabel) {
- if (!scope.selectedLabel.custom) {
- scope.link.label = scope.selectedLabel.name;
- } else {
- scope.link.label = "";
+
+ ngModelCtrl.$render = function () {
+ labelsList.length = 0;
+ for (var label in scope.allowedLabels) {
+ var linkLabel = {
+ name: scope.allowedLabels[label].name,
+ value: scope.allowedLabels[label].value
+ };
+ labelsList.push(linkLabel);
+ }
+
+ var link = ngModelCtrl.$viewValue;
+ var labels = [];
+ if (link && link.labels) {
+ for (var i = 0; i < link.labels.length; i++) {
+ label = link.labels[i];
+ if (scope.allowedLabels[label]) {
+ labels.push(angular.copy(scope.allowedLabels[label]));
+ } else {
+ labels.push({
+ name: label,
+ value: label
+ });
+ }
}
}
+ scope.labels = labels;
+ scope.$watch('labels', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ updateLabels();
+ }
+ }, true);
};
+ function updateLabels() {
+ if (ngModelCtrl.$viewValue) {
+ var labels = [];
+ for (var i = 0; i < scope.labels.length; i++) {
+ labels.push(scope.labels[i].value);
+ }
+ ngModelCtrl.$viewValue.labels = labels;
+ ngModelCtrl.$viewValue.label = labels.join(' / ');
+ updateValidity();
+ }
+ }
+
+ function updateValidity() {
+ var valid = ngModelCtrl.$viewValue.labels &&
+ ngModelCtrl.$viewValue.labels.length ? true : false;
+ ngModelCtrl.$setValidity('linkLabels', valid);
+ }
+
$compile(element.contents())(scope);
}
return {
restrict: "E",
+ require: "^ngModel",
link: linker,
scope: {
- link: '=',
- labels: '=',
+ allowedLabels: '=',
+ allowCustom: '=',
isEdit: '=',
- isReadOnly: '=',
- theForm: '='
+ isReadOnly: '='
}
};
}
ui/src/app/rulechain/link.scss 30(+30 -0)
diff --git a/ui/src/app/rulechain/link.scss b/ui/src/app/rulechain/link.scss
new file mode 100644
index 0000000..3e86619
--- /dev/null
+++ b/ui/src/app/rulechain/link.scss
@@ -0,0 +1,30 @@
+/**
+ * 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-link-label-autocomplete {
+ .tb-not-found {
+ display: block;
+ line-height: 1.5;
+ height: 48px;
+ .tb-no-entries {
+ line-height: 48px;
+ }
+ }
+ li {
+ height: auto !important;
+ white-space: normal !important;
+ }
+}
ui/src/app/rulechain/link-fieldset.tpl.html 57(+39 -18)
diff --git a/ui/src/app/rulechain/link-fieldset.tpl.html b/ui/src/app/rulechain/link-fieldset.tpl.html
index 13ec6c3..e74542f 100644
--- a/ui/src/app/rulechain/link-fieldset.tpl.html
+++ b/ui/src/app/rulechain/link-fieldset.tpl.html
@@ -17,23 +17,44 @@
-->
<md-content class="md-padding tb-link" layout="column">
<fieldset ng-disabled="$root.loading || !isEdit || isReadOnly">
- <md-input-container class="md-block">
- <label translate>rulenode.link-label</label>
- <md-select ng-model="selectedLabel" ng-change="selectedLabelChanged()">
- <md-option ng-repeat="label in labels" ng-value="label">
- {{label.name}}
- </md-option>
- </md-select>
- <div ng-messages="theForm.linkLabel.$error">
- <div translate ng-message="required">rulenode.link-label-required</div>
- </div>
- </md-input-container>
- <md-input-container ng-if="selectedLabel.custom" class="md-block">
- <label translate>rulenode.link-label</label>
- <input required name="customLinkLabel" ng-model="link.label">
- <div ng-messages="theForm.customLinkLabel.$error">
- <div translate ng-message="required">rulenode.custom-link-label-required</div>
- </div>
- </md-input-container>
+ <label translate class="tb-title no-padding" ng-class="{'tb-required': required}">rulenode.link-labels</label>
+ <md-chips id="link_label_chips"
+ ng-required="true"
+ readonly="$root.loading || !isEdit || isReadOnly"
+ ng-model="labels" md-autocomplete-snap
+ md-transform-chip="transformLinkLabelChip($chip)"
+ md-require-match="!allowCustom">
+ <md-autocomplete
+ id="link_label"
+ md-no-cache="true"
+ md-selected-item="selectedLabel"
+ md-search-text="labelSearchText"
+ md-items="item in labelsSearch(labelSearchText)"
+ md-item-text="item.name"
+ md-min-length="0"
+ placeholder="{{'rulenode.link-label' | translate }}"
+ md-menu-class="tb-link-label-autocomplete">
+ <span md-highlight-text="labelSearchText" md-highlight-flags="^i">{{item}}</span>
+ <md-not-found>
+ <div class="tb-not-found">
+ <div class="tb-no-entries" ng-if="!labelSearchText || !labelSearchText.length">
+ <span translate>rulenode.no-link-labels-found</span>
+ </div>
+ <div ng-if="labelSearchText && labelSearchText.length">
+ <span translate translate-values='{ label: "{{labelSearchText | truncate:true:6:'...'}}" }'>rulenode.no-link-label-matching</span>
+ <span ng-if="allowCustom">
+ <a translate ng-click="createLinkLabel($event, '#link_label_chips')">rulenode.create-new-link-label</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="linkLabels" class="tb-error-message">rulenode.link-labels-required</div>
+ </div>
</fieldset>
</md-content>
diff --git a/ui/src/app/rulechain/message-type-autocomplete.directive.js b/ui/src/app/rulechain/message-type-autocomplete.directive.js
new file mode 100644
index 0000000..afa5e35
--- /dev/null
+++ b/ui/src/app/rulechain/message-type-autocomplete.directive.js
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+import './message-type-autocomplete.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import messageTypeAutocompleteTemplate from './message-type-autocomplete.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function MessageTypeAutocomplete($compile, $templateCache, $q, $filter, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(messageTypeAutocompleteTemplate);
+ element.html(template);
+
+ var messageTypeList = [];
+ for (var t in types.messageType) {
+ var type = types.messageType[t];
+ messageTypeList.push(type);
+ }
+
+ scope.messageType = null;
+ scope.messageTypeSearchText = '';
+
+ scope.fetchMessageTypes = function(searchText) {
+ var deferred = $q.defer();
+ var result = $filter('filter')(messageTypeList, {'name': searchText});
+ if (result && result.length) {
+ deferred.resolve(result);
+ } else {
+ deferred.resolve([{name: searchText, value: searchText}]);
+ }
+ return deferred.promise;
+ };
+
+ scope.messageTypeSearchTextChanged = function() {
+ };
+
+ scope.updateView = function () {
+ if (!scope.disabled) {
+ var value = null;
+ if (scope.messageType) {
+ value = scope.messageType.value;
+ }
+ ngModelCtrl.$setViewValue(value);
+ }
+ };
+
+ ngModelCtrl.$render = function () {
+ var value = ngModelCtrl.$viewValue;
+ if (value) {
+ var result = $filter('filter')(messageTypeList, {'value': value}, true);
+ if (result && result.length) {
+ scope.messageType = result[0];
+ } else {
+ scope.messageType = {
+ name: value,
+ value: value
+ };
+ }
+ } else {
+ scope.messageType = null;
+ }
+ };
+
+ scope.$watch('messageType', function (newValue, prevValue) {
+ if (!angular.equals(newValue, prevValue)) {
+ scope.updateView();
+ }
+ });
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ disabled:'=ngDisabled',
+ required:'=ngRequired'
+ }
+ };
+}
diff --git a/ui/src/app/rulechain/message-type-autocomplete.scss b/ui/src/app/rulechain/message-type-autocomplete.scss
new file mode 100644
index 0000000..4cad9cb
--- /dev/null
+++ b/ui/src/app/rulechain/message-type-autocomplete.scss
@@ -0,0 +1,25 @@
+/**
+ * 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-message-type-autocomplete {
+ .tb-message-type-item {
+ display: block;
+ height: 48px;
+ }
+ li {
+ height: auto !important;
+ white-space: normal !important;
+ }
+}
diff --git a/ui/src/app/rulechain/message-type-autocomplete.tpl.html b/ui/src/app/rulechain/message-type-autocomplete.tpl.html
new file mode 100644
index 0000000..bb3e71f
--- /dev/null
+++ b/ui/src/app/rulechain/message-type-autocomplete.tpl.html
@@ -0,0 +1,41 @@
+<!--
+
+ 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-autocomplete ng-required="required"
+ ng-disabled="disabled"
+ md-no-cache="true"
+ md-input-name="messageType"
+ ng-model="messageType"
+ md-selected-item="messageType"
+ md-search-text="messageTypeSearchText"
+ md-search-text-change="messageTypeSearchTextChanged()"
+ md-items="item in fetchMessageTypes(messageTypeSearchText)"
+ md-item-text="item.name"
+ md-min-length="0"
+ placeholder="{{ 'rulenode.select-message-type' | translate }}"
+ md-floating-label="{{ 'rulenode.message-type' | translate }}"
+ md-select-on-match="false"
+ md-menu-class="tb-message-type-autocomplete">
+ <md-item-template>
+ <div class="tb-message-type-item">
+ <span md-highlight-text="messageTypeSearchText" md-highlight-flags="^i">{{item.name}}</span>
+ </div>
+ </md-item-template>
+ <div ng-messages="theForm.messageType.$error">
+ <div translate ng-message="required">rulenode.message-type-required</div>
+ </div>
+</md-autocomplete>
ui/src/app/rulechain/rulechain.controller.js 86(+62 -24)
diff --git a/ui/src/app/rulechain/rulechain.controller.js b/ui/src/app/rulechain/rulechain.controller.js
index dfd1a97..a38f8a8 100644
--- a/ui/src/app/rulechain/rulechain.controller.js
+++ b/ui/src/app/rulechain/rulechain.controller.js
@@ -669,11 +669,15 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}
} else {
if (edge.label) {
+ if (!edge.labels) {
+ edge.labels = edge.label.split(' / ');
+ }
deferred.resolve(edge);
} else {
var labels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
+ var allowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
vm.enableHotKeys = false;
- addRuleNodeLink(event, edge, labels).then(
+ addRuleNodeLink(event, edge, labels, allowCustomLabels).then(
(link) => {
deferred.resolve(link);
vm.enableHotKeys = true;
@@ -713,6 +717,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
vm.isEditingRuleNode = false;
vm.editingRuleNode = null;
vm.editingRuleNodeLinkLabels = ruleChainService.getRuleNodeSupportedLinks(sourceNode.component);
+ vm.editingRuleNodeAllowCustomLabels = ruleChainService.ruleNodeAllowCustomLinks(sourceNode.component);
vm.isEditingRuleNodeLink = true;
vm.editingRuleNodeLinkIndex = vm.ruleChainModel.edges.indexOf(edge);
vm.editingRuleNodeLink = angular.copy(edge);
@@ -744,7 +749,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
isInputSource: isInputSource,
fromIndex: fromIndex,
toIndex: toIndex,
- label: edge.label
+ label: edge.label,
+ labels: edge.labels
};
connections.push(connection);
}
@@ -816,7 +822,8 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var edge = {
source: source,
destination: destination,
- label: connection.label
+ label: connection.label,
+ labels: connection.labels
};
vm.ruleChainModel.edges.push(edge);
vm.modelservice.edges.select(edge);
@@ -1024,6 +1031,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
}
if (vm.ruleChainMetaData.connections) {
+ var edgeMap = {};
for (i = 0; i < vm.ruleChainMetaData.connections.length; i++) {
var connection = vm.ruleChainMetaData.connections[i];
var sourceNode = nodes[connection.fromIndex];
@@ -1032,12 +1040,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var sourceConnectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
var destConnectors = vm.modelservice.nodes.getConnectorsByType(destNode, flowchartConstants.leftConnectorType);
if (sourceConnectors && sourceConnectors.length && destConnectors && destConnectors.length) {
- edge = {
- source: sourceConnectors[0].id,
- destination: destConnectors[0].id,
- label: connection.type
- };
- vm.ruleChainModel.edges.push(edge);
+ var sourceId = sourceConnectors[0].id;
+ var destId = destConnectors[0].id;
+ var edgeKey = sourceId + '_' + destId;
+ edge = edgeMap[edgeKey];
+ if (!edge) {
+ edge = {
+ source: sourceId,
+ destination: destId,
+ label: connection.type,
+ labels: [connection.type]
+ };
+ edgeMap[edgeKey] = edge;
+ vm.ruleChainModel.edges.push(edge);
+ } else {
+ edge.label += ' / ' +connection.type;
+ edge.labels.push(connection.type);
+ }
}
}
}
@@ -1045,6 +1064,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
if (vm.ruleChainMetaData.ruleChainConnections) {
var ruleChainNodesMap = {};
+ var ruleChainEdgeMap = {};
for (i = 0; i < vm.ruleChainMetaData.ruleChainConnections.length; i++) {
var ruleChainConnection = vm.ruleChainMetaData.ruleChainConnections[i];
var ruleChain = ruleChainsMap[ruleChainConnection.targetRuleChainId.id];
@@ -1081,12 +1101,23 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
if (sourceNode) {
connectors = vm.modelservice.nodes.getConnectorsByType(sourceNode, flowchartConstants.rightConnectorType);
if (connectors && connectors.length) {
- var ruleChainEdge = {
- source: connectors[0].id,
- destination: ruleChainNode.connectors[0].id,
- label: ruleChainConnection.type
- };
- vm.ruleChainModel.edges.push(ruleChainEdge);
+ sourceId = connectors[0].id;
+ destId = ruleChainNode.connectors[0].id;
+ edgeKey = sourceId + '_' + destId;
+ var ruleChainEdge = ruleChainEdgeMap[edgeKey];
+ if (!ruleChainEdge) {
+ ruleChainEdge = {
+ source: sourceId,
+ destination: destId,
+ label: ruleChainConnection.type,
+ labels: [ruleChainConnection.type]
+ };
+ ruleChainEdgeMap[edgeKey] = ruleChainEdge;
+ vm.ruleChainModel.edges.push(ruleChainEdge);
+ } else {
+ ruleChainEdge.label += ' / ' +ruleChainConnection.type;
+ ruleChainEdge.labels.push(ruleChainConnection.type);
+ }
}
}
}
@@ -1199,8 +1230,7 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
var ruleChainConnection = {
fromIndex: fromIndex,
targetRuleChainId: {entityType: vm.types.entityType.rulechain, id: destNode.targetRuleChainId},
- additionalInfo: destNode.additionalInfo,
- type: edge.label
+ additionalInfo: destNode.additionalInfo
};
if (!ruleChainConnection.additionalInfo) {
ruleChainConnection.additionalInfo = {};
@@ -1208,15 +1238,22 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
ruleChainConnection.additionalInfo.layoutX = Math.round(destNode.x);
ruleChainConnection.additionalInfo.layoutY = Math.round(destNode.y);
ruleChainConnection.additionalInfo.ruleChainNodeId = destNode.id;
- ruleChainMetaData.ruleChainConnections.push(ruleChainConnection);
+ for (var rcIndex=0;rcIndex<edge.labels.length;rcIndex++) {
+ var newRuleChainConnection = angular.copy(ruleChainConnection);
+ newRuleChainConnection.type = edge.labels[rcIndex];
+ ruleChainMetaData.ruleChainConnections.push(newRuleChainConnection);
+ }
} else {
var toIndex = nodes.indexOf(destNode);
var nodeConnection = {
fromIndex: fromIndex,
- toIndex: toIndex,
- type: edge.label
+ toIndex: toIndex
};
- ruleChainMetaData.connections.push(nodeConnection);
+ for (var cIndex=0;cIndex<edge.labels.length;cIndex++) {
+ var newNodeConnection = angular.copy(nodeConnection);
+ newNodeConnection.type = edge.labels[cIndex];
+ ruleChainMetaData.connections.push(newNodeConnection);
+ }
}
}
}
@@ -1285,13 +1322,13 @@ export function RuleChainController($state, $scope, $compile, $q, $mdUtil, $time
});
}
- function addRuleNodeLink($event, link, labels) {
+ function addRuleNodeLink($event, link, labels, allowCustomLabels) {
return $mdDialog.show({
controller: 'AddRuleNodeLinkController',
controllerAs: 'vm',
templateUrl: addRuleNodeLinkTemplate,
parent: angular.element($document[0].body),
- locals: {link: link, labels: labels},
+ locals: {link: link, labels: labels, allowCustomLabels: allowCustomLabels},
fullscreen: true,
targetEvent: $event
});
@@ -1335,13 +1372,14 @@ export function AddRuleNodeController($scope, $mdDialog, ruleNode, ruleChainId,
}
/*@ngInject*/
-export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, helpLinks) {
+export function AddRuleNodeLinkController($scope, $mdDialog, link, labels, allowCustomLabels, helpLinks) {
var vm = this;
vm.helpLinks = helpLinks;
vm.link = link;
vm.labels = labels;
+ vm.allowCustomLabels = allowCustomLabels;
vm.add = add;
vm.cancel = cancel;
ui/src/app/rulechain/rulechain.scss 4(+4 -0)
diff --git a/ui/src/app/rulechain/rulechain.scss b/ui/src/app/rulechain/rulechain.scss
index c51a955..5999b7e 100644
--- a/ui/src/app/rulechain/rulechain.scss
+++ b/ui/src/app/rulechain/rulechain.scss
@@ -170,6 +170,9 @@
&.tb-rule-chain-type {
background-color: #d6c4f1;
}
+ &.tb-unknown-type {
+ background-color: #f16c29;
+ }
}
.tb-rule-node {
@@ -202,6 +205,7 @@
background-color: #a3eaa9;
user-select: none;
}
+
md-icon {
font-size: 20px;
width: 20px;
diff --git a/ui/src/app/rulechain/rulechain.tpl.html b/ui/src/app/rulechain/rulechain.tpl.html
index a84df90..9c77ae4 100644
--- a/ui/src/app/rulechain/rulechain.tpl.html
+++ b/ui/src/app/rulechain/rulechain.tpl.html
@@ -207,11 +207,11 @@
</details-buttons>
<form name="vm.ruleNodeLinkForm" ng-if="vm.isEditingRuleNodeLink">
<tb-rule-node-link
- link="vm.editingRuleNodeLink"
- labels="vm.editingRuleNodeLinkLabels"
+ ng-model="vm.editingRuleNodeLink"
+ allowed-labels="vm.editingRuleNodeLinkLabels"
+ allow-custom="vm.editingRuleNodeAllowCustomLabels"
is-edit="true"
- is-read-only="false"
- the-form="vm.ruleNodeLinkForm">
+ is-read-only="false">
</tb-rule-node-link>
</form>
</tb-details-sidenav>
diff --git a/ui/src/app/rulechain/script/node-script-test.tpl.html b/ui/src/app/rulechain/script/node-script-test.tpl.html
index 0ce57e8..e5fb9c3 100644
--- a/ui/src/app/rulechain/script/node-script-test.tpl.html
+++ b/ui/src/app/rulechain/script/node-script-test.tpl.html
@@ -38,13 +38,12 @@
<ng-form name="payloadForm">
<div layout="column" style="height: 100%;">
<div layout="row">
- <md-input-container class="md-block" style="margin-bottom: 0px; min-width: 300px;">
- <label translate>rulenode.message-type</label>
- <input required name="msgType" ng-model="vm.inputParams.msgType">
- <div ng-messages="payloadForm.msgType.$error">
- <div translate ng-message="required">rulenode.message-type-required</div>
- </div>
- </md-input-container>
+ <tb-message-type-autocomplete
+ style="margin-bottom: 0px; min-width: 300px;"
+ ng-required="true"
+ ng-model="vm.inputParams.msgType"
+ the-form="payloadForm">
+ </tb-message-type-autocomplete>
</div>
<tb-json-content flex
ng-model="vm.inputParams.msg"
@@ -99,14 +98,14 @@
validate-content="false"
ng-readonly="true"
fill-height="true">
- </tb-json-content>
+ </tb-json-content>generateReport
</div>
</div>
</div>
</div>
</md-dialog-content>
<md-dialog-actions layout="row">
- <md-button ng-disabled="$root.loading" ng-click="vm.test()" class="md-raised md-primary">
+ <md-button ng-disabled="$root.loading || theForm.$invalid" ng-click="vm.test()" class="md-raised md-primary">
{{ 'rulenode.test' | translate }}
</md-button>
<span flex></span>
ui/src/app/services/item-buffer.service.js 11(+10 -1)
diff --git a/ui/src/app/services/item-buffer.service.js b/ui/src/app/services/item-buffer.service.js
index a9fe348..09ed9c5 100644
--- a/ui/src/app/services/item-buffer.service.js
+++ b/ui/src/app/services/item-buffer.service.js
@@ -214,10 +214,19 @@ function ItemBuffer($q, bufferStore, types, utils, dashboardUtils, ruleChainServ
var node = ruleNodes.nodes[i];
var component = ruleChainService.getRuleNodeComponentByClazz(node.componentClazz);
if (component) {
+ var icon = types.ruleNodeType[component.type].icon;
+ var iconUrl = null;
+ if (component.configurationDescriptor.nodeDefinition.icon) {
+ icon = component.configurationDescriptor.nodeDefinition.icon;
+ }
+ if (component.configurationDescriptor.nodeDefinition.iconUrl) {
+ iconUrl = component.configurationDescriptor.nodeDefinition.iconUrl;
+ }
delete node.componentClazz;
node.component = component;
node.nodeClass = types.ruleNodeType[component.type].nodeClass;
- node.icon = types.ruleNodeType[component.type].icon;
+ node.icon = icon;
+ node.iconUrl = iconUrl;
node.connectors = [];
node.x = Math.round(node.x + deltaX);
node.y = Math.round(node.y + deltaY);
diff --git a/ui/src/app/widget/lib/alarms-table-widget.js b/ui/src/app/widget/lib/alarms-table-widget.js
index 0a5bbce..0696a7b 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.js
+++ b/ui/src/app/widget/lib/alarms-table-widget.js
@@ -45,7 +45,7 @@ function AlarmsTableWidget() {
}
/*@ngInject*/
-function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, alarmService, utils, types) {
+function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDialog, $document, $translate, $q, $timeout, alarmService, utils, types) {
var vm = this;
vm.stylesInfo = {};
@@ -266,6 +266,9 @@ function AlarmsTableWidgetController($element, $scope, $filter, $mdMedia, $mdDia
function enterFilterMode () {
vm.query.search = '';
vm.ctx.hideTitlePanel = true;
+ $timeout(()=>{
+ angular.element(vm.ctx.$container).find('.searchInput').focus();
+ })
}
function exitFilterMode () {
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 1cca18b..8480058 100644
--- a/ui/src/app/widget/lib/alarms-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/alarms-table-widget.tpl.html
@@ -28,7 +28,7 @@
</md-button>
<md-input-container flex>
<label> </label>
- <input ng-model="vm.query.search" placeholder="{{'alarm.search' | translate}}"/>
+ <input ng-model="vm.query.search" class="searchInput" placeholder="{{'alarm.search' | translate}}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
diff --git a/ui/src/app/widget/lib/canvas-digital-gauge.js b/ui/src/app/widget/lib/canvas-digital-gauge.js
index 8e6db04..283a426 100644
--- a/ui/src/app/widget/lib/canvas-digital-gauge.js
+++ b/ui/src/app/widget/lib/canvas-digital-gauge.js
@@ -70,6 +70,10 @@ export default class TbCanvasDigitalGauge {
(settings.title && settings.title.length > 0 ?
settings.title : dataKey.label) : '');
+ if (!this.localSettings.unitTitle && this.localSettings.showTimestamp) {
+ this.localSettings.unitTitle = ' ';
+ }
+
this.localSettings.titleFont = {};
var settingsTitleFont = settings.titleFont;
if (!settingsTitleFont) {
@@ -206,6 +210,7 @@ export default class TbCanvasDigitalGauge {
var value = tvPair[1];
if(value !== this.gauge.value) {
this.gauge.value = value;
+ this.gauge._value = value;
} else if (this.localSettings.showTimestamp && this.gauge.timestamp != timestamp) {
this.gauge.timestamp = timestamp;
}
diff --git a/ui/src/app/widget/lib/CanvasDigitalGauge.js b/ui/src/app/widget/lib/CanvasDigitalGauge.js
index 0166e91..ee8b0ed 100644
--- a/ui/src/app/widget/lib/CanvasDigitalGauge.js
+++ b/ui/src/app/widget/lib/CanvasDigitalGauge.js
@@ -204,8 +204,13 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
}
var valueChanged = false;
-
- if (!this.elementValueClone.initialized || this.elementValueClone.renderedValue !== this.value || (options.showTimestamp && this.elementValueClone.renderedTimestamp !== this.timestamp)) {
+ if (!this.elementValueClone.initialized || angular.isDefined(this._value) && this.elementValueClone.renderedValue !== this._value || (options.showTimestamp && this.elementValueClone.renderedTimestamp !== this.timestamp)) {
+ if (angular.isDefined(this._value)) {
+ this.elementValueClone.renderedValue = this._value;
+ }
+ if (angular.isUndefined(this.elementValueClone.renderedValue)) {
+ this.elementValueClone.renderedValue = options.minValue;
+ }
let context = this.contextValueClone;
// clear the cache
context.clearRect(x, y, w, h);
@@ -214,7 +219,7 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
context.drawImage(canvas.elementClone, x, y, w, h);
context.save();
- drawDigitalValue(context, options, this.value);
+ drawDigitalValue(context, options, this.elementValueClone.renderedValue);
if (options.showTimestamp) {
drawDigitalLabel(context, options);
@@ -222,7 +227,6 @@ export default class CanvasDigitalGauge extends canvasGauges.BaseGauge {
}
this.elementValueClone.initialized = true;
- this.elementValueClone.renderedValue = this.value;
valueChanged = true;
}
diff --git a/ui/src/app/widget/lib/entities-table-widget.js b/ui/src/app/widget/lib/entities-table-widget.js
index 1601e71..d0b629d 100644
--- a/ui/src/app/widget/lib/entities-table-widget.js
+++ b/ui/src/app/widget/lib/entities-table-widget.js
@@ -45,7 +45,7 @@ function EntitiesTableWidget() {
}
/*@ngInject*/
-function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, utils, types) {
+function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $translate, $timeout, utils, types) {
var vm = this;
vm.stylesInfo = {};
@@ -254,6 +254,9 @@ function EntitiesTableWidgetController($element, $scope, $filter, $mdMedia, $tra
function enterFilterMode () {
vm.query.search = '';
vm.ctx.hideTitlePanel = true;
+ $timeout(()=>{
+ angular.element(vm.ctx.$container).find('.searchInput').focus();
+ })
}
function exitFilterMode () {
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 97a8d69..474d536 100644
--- a/ui/src/app/widget/lib/entities-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/entities-table-widget.tpl.html
@@ -27,7 +27,7 @@
</md-button>
<md-input-container flex>
<label> </label>
- <input ng-model="vm.query.search" placeholder="{{'entity.search' | translate}}"/>
+ <input ng-model="vm.query.search" class="searchInput" placeholder="{{'entity.search' | translate}}"/>
</md-input-container>
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
diff --git a/ui/src/app/widget/lib/timeseries-table-widget.js b/ui/src/app/widget/lib/timeseries-table-widget.js
index 684bbde..bca7956 100644
--- a/ui/src/app/widget/lib/timeseries-table-widget.js
+++ b/ui/src/app/widget/lib/timeseries-table-widget.js
@@ -44,7 +44,7 @@ function TimeseriesTableWidget() {
}
/*@ngInject*/
-function TimeseriesTableWidgetController($element, $scope, $filter) {
+function TimeseriesTableWidgetController($element, $scope, $filter, $timeout) {
var vm = this;
let dateFormatFilter = 'yyyy-MM-dd HH:mm:ss';
@@ -62,6 +62,9 @@ function TimeseriesTableWidgetController($element, $scope, $filter) {
function enterFilterMode () {
vm.query.search = '';
vm.ctx.hideTitlePanel = true;
+ $timeout(()=>{
+ angular.element(vm.ctx.$container).find('.searchInput').focus();
+ })
}
function exitFilterMode () {
diff --git a/ui/src/app/widget/lib/timeseries-table-widget.tpl.html b/ui/src/app/widget/lib/timeseries-table-widget.tpl.html
index eb9b8ca..08c4c9e 100644
--- a/ui/src/app/widget/lib/timeseries-table-widget.tpl.html
+++ b/ui/src/app/widget/lib/timeseries-table-widget.tpl.html
@@ -27,7 +27,7 @@
</md-button>
<md-input-container flex>
<label> </label>
- <input ng-model="vm.query.search" placeholder="{{'widget.search-data' | translate}}" md-autofocus/>
+ <input ng-model="vm.query.search" class="searchInput" placeholder="{{'widget.search-data' | translate}}" md-autofocus/>
</md-input-container>
<md-button class="md-icon-button" aria-label="Close" ng-click="vm.exitFilterMode()">
<md-icon aria-label="Close" class="material-icons">close</md-icon>
diff --git a/ui/src/app/widget/widget-editor.controller.js b/ui/src/app/widget/widget-editor.controller.js
index 6f0ad87..089d64c 100644
--- a/ui/src/app/widget/widget-editor.controller.js
+++ b/ui/src/app/widget/widget-editor.controller.js
@@ -20,12 +20,11 @@ import 'brace/mode/javascript';
import 'brace/mode/html';
import 'brace/mode/css';
import 'brace/mode/json';
-import 'ace-builds/src-min-noconflict/ace';
-import 'ace-builds/src-min-noconflict/snippets/javascript';
-import 'ace-builds/src-min-noconflict/snippets/text';
-import 'ace-builds/src-min-noconflict/snippets/html';
-import 'ace-builds/src-min-noconflict/snippets/css';
-import 'ace-builds/src-min-noconflict/snippets/json';
+import 'brace/snippets/javascript';
+import 'brace/snippets/text';
+import 'brace/snippets/html';
+import 'brace/snippets/css';
+import 'brace/snippets/json';
/* eslint-disable import/no-unresolved, import/default */
ui/src/scss/main.scss 7(+7 -0)
diff --git a/ui/src/scss/main.scss b/ui/src/scss/main.scss
index 8fed892..2852a7b 100644
--- a/ui/src/scss/main.scss
+++ b/ui/src/scss/main.scss
@@ -16,6 +16,7 @@
@import "~compass-sass-mixins/lib/compass";
@import "constants";
@import "animations";
+@import "mixins";
@import "fonts";
/***************
@@ -437,6 +438,12 @@ pre.tb-highlight {
}
}
+.tb-card-description {
+ color: rgba(0,0,0,0.54);
+ font-size: 13px;
+ @include line-clamp(2, 1.1);
+}
+
/***********************
* Flow
***********************/
ui/src/scss/mixins.scss 27(+26 -1)
diff --git a/ui/src/scss/mixins.scss b/ui/src/scss/mixins.scss
index 9e8b7df..cb66171 100644
--- a/ui/src/scss/mixins.scss
+++ b/ui/src/scss/mixins.scss
@@ -31,4 +31,29 @@
&:-ms-input-placeholder {
@content;
}
-}
\ No newline at end of file
+}
+
+@mixin line-clamp($numLines: 1, $lineHeight: 1.412) {
+ overflow: hidden;
+ position: relative;
+ line-height: $lineHeight;
+ text-align: justify;
+ margin-right: -1em;
+ padding-right: 2em;
+ max-height: ($numLines*$lineHeight)+em;
+ &:before {
+ content: '...';
+ position: absolute;
+ right: 1em;
+ bottom: 0;
+ }
+ &:after {
+ content: '';
+ position: absolute;
+ right: 1em;
+ width: 1em;
+ height: 1em;
+ margin-top: 0.2em;
+ background: white;
+ }
+}
ui/webpack.config.dev.js 30(+27 -3)
diff --git a/ui/webpack.config.dev.js b/ui/webpack.config.dev.js
index b1a1911..242ad9d 100644
--- a/ui/webpack.config.dev.js
+++ b/ui/webpack.config.dev.js
@@ -20,6 +20,17 @@ const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
+const dirTree = require('directory-tree');
+const jsonminify = require("jsonminify");
+
+const PUBLIC_RESOURCE_PATH = '/';
+
+var langs = [];
+dirTree('./src/app/locale/', {extensions:/\.json$/}, (item) => {
+ /* It is expected what the name of a locale file has the following format: */
+ /* 'locale.constant-LANG_CODE[_REGION_CODE].json', e.g. locale.constant-es.json or locale.constant-zh_CN.json*/
+ langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5));
+});
/* devtool: 'cheap-module-eval-source-map', */
@@ -32,7 +43,7 @@ module.exports = {
],
output: {
path: path.resolve(__dirname, 'target/generated-resources/public/static'),
- publicPath: '/',
+ publicPath: PUBLIC_RESOURCE_PATH,
filename: 'bundle.js',
},
plugins: [
@@ -45,7 +56,18 @@ module.exports = {
moment: "moment"
}),
new CopyWebpackPlugin([
- { from: './src/thingsboard.ico', to: 'thingsboard.ico' }
+ {
+ from: './src/thingsboard.ico',
+ to: 'thingsboard.ico'
+ },
+ {
+ from: './src/app/locale',
+ to: 'locale',
+ ignore: [ '*.js' ],
+ transform: function(content, path) {
+ return Buffer.from(jsonminify(content.toString()));
+ }
+ }
]),
new webpack.HotModuleReplacementPlugin(),
new HtmlWebpackPlugin({
@@ -65,6 +87,8 @@ module.exports = {
'process.env': {
NODE_ENV: JSON.stringify('development'),
},
+ PUBLIC_PATH: JSON.stringify(PUBLIC_RESOURCE_PATH),
+ SUPPORTED_LANGS: JSON.stringify(langs)
}),
],
node: {
@@ -117,7 +141,7 @@ module.exports = {
'url?limit=8192',
'img?minimize'
]
- },
+ }
],
},
'html-minifier-loader': {
ui/webpack.config.prod.js 38(+35 -3)
diff --git a/ui/webpack.config.prod.js b/ui/webpack.config.prod.js
index 150638d..a442590 100644
--- a/ui/webpack.config.prod.js
+++ b/ui/webpack.config.prod.js
@@ -18,8 +18,20 @@
const HtmlWebpackPlugin = require('html-webpack-plugin');
const ExtractTextPlugin = require('extract-text-webpack-plugin');
const CopyWebpackPlugin = require('copy-webpack-plugin');
+const CompressionPlugin = require('compression-webpack-plugin');
const webpack = require('webpack');
const path = require('path');
+const dirTree = require('directory-tree');
+const jsonminify = require("jsonminify");
+
+const PUBLIC_RESOURCE_PATH = '/static/';
+
+var langs = [];
+dirTree('./src/app/locale/', {extensions:/\.json$/}, (item) => {
+ /* It is expected what the name of a locale file has the following format: */
+ /* 'locale.constant-LANG_CODE[_REGION_CODE].json', e.g. locale.constant-es.json or locale.constant-zh_CN.json*/
+ langs.push(item.name.slice(item.name.lastIndexOf('-') + 1, -5));
+});
module.exports = {
devtool: 'source-map',
@@ -29,7 +41,7 @@ module.exports = {
],
output: {
path: path.resolve(__dirname, 'target/generated-resources/public/static'),
- publicPath: '/static/',
+ publicPath: PUBLIC_RESOURCE_PATH,
filename: 'bundle.[hash].js',
},
plugins: [
@@ -42,7 +54,18 @@ module.exports = {
moment: "moment"
}),
new CopyWebpackPlugin([
- {from: './src/thingsboard.ico', to: 'thingsboard.ico'}
+ {
+ from: './src/thingsboard.ico',
+ to: 'thingsboard.ico'
+ },
+ {
+ from: './src/app/locale',
+ to: 'locale',
+ ignore: [ '*.js' ],
+ transform: function(content, path) {
+ return Buffer.from(jsonminify(content.toString()));
+ }
+ }
]),
new HtmlWebpackPlugin({
template: './src/index.html',
@@ -63,7 +86,16 @@ module.exports = {
'process.env': {
NODE_ENV: JSON.stringify('production'),
},
+ PUBLIC_PATH: PUBLIC_RESOURCE_PATH,
+ SUPPORTED_LANGS: JSON.stringify(langs)
}),
+ new CompressionPlugin({
+ asset: "[path].gz[query]",
+ algorithm: "gzip",
+ test: /\.js$|\.css$|\.svg$|\.ttf$|\.woff$|\.woff2|\.eot$\.json$/,
+ threshold: 10240,
+ minRatio: 0.8
+ })
],
node: {
tls: "empty",
@@ -115,7 +147,7 @@ module.exports = {
'url?limit=8192',
'img?minimize'
]
- },
+ }
],
},
'html-minifier-loader': {