Commit b02503a45500f4ab72ccdc4fda68ee9ef2b3457f

Authored by viktorbasanets
2 parents 0501e9d4 d9091920

Conflicts resolved

Showing 81 changed files with 2064 additions and 687 deletions

Too many changes to show.

To preserve performance only 81 of 129 files are displayed.

... ... @@ -116,6 +116,7 @@ public class AppActor extends RuleChainManagerActor {
116 116 break;
117 117 case ACTOR_SYSTEM_TO_DEVICE_SESSION_ACTOR_MSG:
118 118 onToDeviceSessionMsg((BasicActorSystemToDeviceSessionActorMsg) msg);
  119 + break;
119 120 default:
120 121 return false;
121 122 }
... ...
... ... @@ -76,7 +76,7 @@ public class DeviceController extends BaseController {
76 76 device.setTenantId(getCurrentUser().getTenantId());
77 77 if (getCurrentUser().getAuthority() == Authority.CUSTOMER_USER) {
78 78 if (device.getId() == null || device.getId().isNullUid() ||
79   - device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
  79 + device.getCustomerId() == null || device.getCustomerId().isNullUid()) {
80 80 throw new ThingsboardException("You don't have permission to perform this operation!",
81 81 ThingsboardErrorCode.PERMISSION_DENIED);
82 82 } else {
... ...
... ... @@ -49,15 +49,15 @@ import org.thingsboard.server.common.data.kv.Aggregation;
49 49 import org.thingsboard.server.common.data.kv.AttributeKey;
50 50 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
51 51 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
52   -import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  52 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
53 53 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
54 54 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
55 55 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
56 56 import org.thingsboard.server.common.data.kv.KvEntry;
57 57 import org.thingsboard.server.common.data.kv.LongDataEntry;
  58 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
58 59 import org.thingsboard.server.common.data.kv.StringDataEntry;
59 60 import org.thingsboard.server.common.data.kv.TsKvEntry;
60   -import org.thingsboard.server.common.data.kv.TsKvQuery;
61 61 import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
62 62 import org.thingsboard.server.common.msg.core.TelemetryUploadRequest;
63 63 import org.thingsboard.server.common.transport.adaptor.JsonConverter;
... ... @@ -81,7 +81,6 @@ import java.util.LinkedHashMap;
81 81 import java.util.List;
82 82 import java.util.Map;
83 83 import java.util.Set;
84   -import java.util.UUID;
85 84 import java.util.concurrent.ExecutorService;
86 85 import java.util.concurrent.Executors;
87 86 import java.util.stream.Collectors;
... ... @@ -201,7 +200,7 @@ public class TelemetryController extends BaseController {
201 200 (result, entityId) -> {
202 201 // If interval is 0, convert this to a NONE aggregation, which is probably what the user really wanted
203 202 Aggregation agg = interval == 0L ? Aggregation.valueOf(Aggregation.NONE.name()) : Aggregation.valueOf(aggStr);
204   - List<TsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, interval, limit, agg))
  203 + List<ReadTsKvQuery> queries = toKeysList(keys).stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, interval, limit, agg))
205 204 .collect(Collectors.toList());
206 205
207 206 Futures.addCallback(tsService.findAll(entityId, queries), getTsKvListCallback(result));
... ...
... ... @@ -22,6 +22,7 @@ import delight.nashornsandbox.NashornSandbox;
22 22 import delight.nashornsandbox.NashornSandboxes;
23 23 import jdk.nashorn.api.scripting.NashornScriptEngineFactory;
24 24 import lombok.extern.slf4j.Slf4j;
  25 +import org.apache.commons.lang3.tuple.Pair;
25 26
26 27 import javax.annotation.PostConstruct;
27 28 import javax.annotation.PreDestroy;
... ... @@ -42,9 +43,10 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
42 43 private ScriptEngine engine;
43 44 private ExecutorService monitorExecutorService;
44 45
45   - private Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
46   -
47   - private Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
  46 + private final Map<UUID, String> functionsMap = new ConcurrentHashMap<>();
  47 + private final Map<UUID,AtomicInteger> blackListedFunctions = new ConcurrentHashMap<>();
  48 + private final Map<String, Pair<UUID, AtomicInteger>> scriptToId = new ConcurrentHashMap<>();
  49 + private final Map<UUID, AtomicInteger> scriptIdToCount = new ConcurrentHashMap<>();
48 50
49 51 @PostConstruct
50 52 public void init() {
... ... @@ -78,19 +80,27 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
78 80
79 81 @Override
80 82 public ListenableFuture<UUID> eval(JsScriptType scriptType, String scriptBody, String... argNames) {
81   - UUID scriptId = UUID.randomUUID();
82   - String functionName = "invokeInternal_" + scriptId.toString().replace('-','_');
83   - String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
84   - try {
85   - if (useJsSandbox()) {
86   - sandbox.eval(jsScript);
87   - } else {
88   - engine.eval(jsScript);
  83 + Pair<UUID, AtomicInteger> deduplicated = deduplicate(scriptType, scriptBody);
  84 + UUID scriptId = deduplicated.getLeft();
  85 + AtomicInteger duplicateCount = deduplicated.getRight();
  86 +
  87 + if(duplicateCount.compareAndSet(0, 1)) {
  88 + String functionName = "invokeInternal_" + scriptId.toString().replace('-', '_');
  89 + String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames);
  90 + try {
  91 + if (useJsSandbox()) {
  92 + sandbox.eval(jsScript);
  93 + } else {
  94 + engine.eval(jsScript);
  95 + }
  96 + functionsMap.put(scriptId, functionName);
  97 + } catch (Exception e) {
  98 + duplicateCount.decrementAndGet();
  99 + log.warn("Failed to compile JS script: {}", e.getMessage(), e);
  100 + return Futures.immediateFailedFuture(e);
89 101 }
90   - functionsMap.put(scriptId, functionName);
91   - } catch (Exception e) {
92   - log.warn("Failed to compile JS script: {}", e.getMessage(), e);
93   - return Futures.immediateFailedFuture(e);
  102 + } else {
  103 + duplicateCount.incrementAndGet();
94 104 }
95 105 return Futures.immediateFuture(scriptId);
96 106 }
... ... @@ -122,6 +132,13 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
122 132
123 133 @Override
124 134 public ListenableFuture<Void> release(UUID scriptId) {
  135 + AtomicInteger count = scriptIdToCount.get(scriptId);
  136 + if(count != null) {
  137 + if(count.decrementAndGet() > 0) {
  138 + return Futures.immediateFuture(null);
  139 + }
  140 + }
  141 +
125 142 String functionName = functionsMap.get(scriptId);
126 143 if (functionName != null) {
127 144 try {
... ... @@ -156,4 +173,16 @@ public abstract class AbstractNashornJsSandboxService implements JsSandboxServic
156 173 throw new RuntimeException("No script factory implemented for scriptType: " + scriptType);
157 174 }
158 175 }
  176 +
  177 + private Pair<UUID, AtomicInteger> deduplicate(JsScriptType scriptType, String scriptBody) {
  178 + Pair<UUID, AtomicInteger> precomputed = Pair.of(UUID.randomUUID(), new AtomicInteger());
  179 +
  180 + Pair<UUID, AtomicInteger> pair = scriptToId.computeIfAbsent(deduplicateKey(scriptType, scriptBody), i -> precomputed);
  181 + AtomicInteger duplicateCount = scriptIdToCount.computeIfAbsent(pair.getLeft(), i -> pair.getRight());
  182 + return Pair.of(pair.getLeft(), duplicateCount);
  183 + }
  184 +
  185 + private String deduplicateKey(JsScriptType scriptType, String scriptBody) {
  186 + return scriptType + "_" + scriptBody;
  187 + }
159 188 }
... ...
... ... @@ -45,7 +45,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S
45 45 try {
46 46 this.scriptId = this.sandboxService.eval(JsScriptType.RULE_NODE_SCRIPT, script, argNames).get();
47 47 } catch (Exception e) {
48   - throw new IllegalArgumentException("Can't compile script: " + e.getMessage());
  48 + throw new IllegalArgumentException("Can't compile script: " + e.getMessage(), e);
49 49 }
50 50 }
51 51
... ...
... ... @@ -24,26 +24,33 @@ import org.springframework.beans.factory.annotation.Autowired;
24 24 import org.springframework.context.annotation.Lazy;
25 25 import org.springframework.stereotype.Service;
26 26 import org.springframework.util.StringUtils;
  27 +import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg;
27 28 import org.thingsboard.rule.engine.api.util.DonAsynchron;
  29 +import org.thingsboard.server.actors.service.ActorService;
28 30 import org.thingsboard.server.common.data.DataConstants;
29 31 import org.thingsboard.server.common.data.EntityType;
30 32 import org.thingsboard.server.common.data.EntityView;
31 33 import org.thingsboard.server.common.data.id.DeviceId;
32 34 import org.thingsboard.server.common.data.id.EntityId;
33 35 import org.thingsboard.server.common.data.id.EntityIdFactory;
  36 +//<<<<<<< HEAD
34 37 import org.thingsboard.server.common.data.id.EntityViewId;
  38 +//=======
  39 +import org.thingsboard.server.common.data.id.TenantId;
  40 +//>>>>>>> d909192071880b7af2137333142bc62ece369ec1
35 41 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
36 42 import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry;
37   -import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  43 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
38 44 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
39 45 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
40 46 import org.thingsboard.server.common.data.kv.DataType;
41 47 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
42 48 import org.thingsboard.server.common.data.kv.KvEntry;
43 49 import org.thingsboard.server.common.data.kv.LongDataEntry;
  50 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
44 51 import org.thingsboard.server.common.data.kv.StringDataEntry;
45 52 import org.thingsboard.server.common.data.kv.TsKvEntry;
46   -import org.thingsboard.server.common.data.kv.TsKvQuery;
  53 +import org.thingsboard.server.common.msg.cluster.SendToClusterMsg;
47 54 import org.thingsboard.server.common.msg.cluster.ServerAddress;
48 55 import org.thingsboard.server.dao.attributes.AttributesService;
49 56 import org.thingsboard.server.dao.entityview.EntityViewService;
... ... @@ -108,6 +115,10 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
108 115 @Lazy
109 116 private DeviceStateService stateService;
110 117
  118 + @Autowired
  119 + @Lazy
  120 + private ActorService actorService;
  121 +
111 122 private ExecutorService tsCallBackExecutor;
112 123 private ExecutorService wsCallBackExecutor;
113 124
... ... @@ -213,6 +224,13 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
213 224 }
214 225
215 226 @Override
  227 + public void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> attributes) {
  228 + DeviceAttributesEventNotificationMsg notificationMsg = DeviceAttributesEventNotificationMsg.onUpdate(tenantId,
  229 + deviceId, DataConstants.SHARED_SCOPE, new ArrayList<>(attributes));
  230 + actorService.onMsg(new SendToClusterMsg(deviceId, notificationMsg));
  231 + }
  232 +
  233 + @Override
216 234 public void onNewRemoteSubscription(ServerAddress serverAddress, byte[] data) {
217 235 ClusterAPIProtos.SubscriptionProto proto;
218 236 try {
... ... @@ -364,9 +382,9 @@ public class DefaultTelemetrySubscriptionService implements TelemetrySubscriptio
364 382 e -> log.error("Failed to fetch missed updates.", e), tsCallBackExecutor);
365 383 } else if (subscription.getType() == TelemetryFeature.TIMESERIES) {
366 384 long curTs = System.currentTimeMillis();
367   - List<TsKvQuery> queries = new ArrayList<>();
  385 + List<ReadTsKvQuery> queries = new ArrayList<>();
368 386 subscription.getKeyStates().entrySet().forEach(e -> {
369   - queries.add(new BaseTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
  387 + queries.add(new BaseReadTsKvQuery(e.getKey(), e.getValue() + 1L, curTs));
370 388 });
371 389
372 390 DonAsynchron.withCallback(tsService.findAll(entityId, queries),
... ...
... ... @@ -30,10 +30,10 @@ import org.thingsboard.server.common.data.id.EntityId;
30 30 import org.thingsboard.server.common.data.id.EntityIdFactory;
31 31 import org.thingsboard.server.common.data.kv.Aggregation;
32 32 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
33   -import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  33 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
34 34 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  35 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
35 36 import org.thingsboard.server.common.data.kv.TsKvEntry;
36   -import org.thingsboard.server.common.data.kv.TsKvQuery;
37 37 import org.thingsboard.server.dao.attributes.AttributesService;
38 38 import org.thingsboard.server.dao.timeseries.TimeseriesService;
39 39 import org.thingsboard.server.service.security.AccessValidator;
... ... @@ -251,7 +251,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
251 251 }
252 252 EntityId entityId = EntityIdFactory.getByTypeAndId(cmd.getEntityType(), cmd.getEntityId());
253 253 List<String> keys = new ArrayList<>(getKeys(cmd).orElse(Collections.emptySet()));
254   - List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
  254 + List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, cmd.getStartTs(), cmd.getEndTs(), cmd.getInterval(), getLimit(cmd.getLimit()), getAggregation(cmd.getAgg())))
255 255 .collect(Collectors.toList());
256 256
257 257 FutureCallback<List<TsKvEntry>> callback = new FutureCallback<List<TsKvEntry>>() {
... ... @@ -337,7 +337,7 @@ public class DefaultTelemetryWebSocketService implements TelemetryWebSocketServi
337 337 log.debug("[{}] fetching timeseries data for last {} ms for keys: ({}) for device : {}", sessionId, cmd.getTimeWindow(), cmd.getKeys(), entityId);
338 338 startTs = cmd.getStartTs();
339 339 long endTs = cmd.getStartTs() + cmd.getTimeWindow();
340   - List<TsKvQuery> queries = keys.stream().map(key -> new BaseTsKvQuery(key, startTs, endTs, cmd.getInterval(),
  340 + List<ReadTsKvQuery> queries = keys.stream().map(key -> new BaseReadTsKvQuery(key, startTs, endTs, cmd.getInterval(),
341 341 getLimit(cmd.getLimit()), getAggregation(cmd.getAgg()))).collect(Collectors.toList());
342 342
343 343 final FutureCallback<List<TsKvEntry>> callback = getSubscriptionCallback(sessionRef, cmd, sessionId, entityId, startTs, keys);
... ...
1   -/**
  1 + /**
2 2 * Copyright © 2016-2018 The Thingsboard Authors
3 3 *
4 4 * Licensed under the Apache License, Version 2.0 (the "License");
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.kv;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class BaseDeleteTsKvQuery extends BaseTsKvQuery implements DeleteTsKvQuery {
  22 +
  23 + private final Boolean rewriteLatestIfDeleted;
  24 +
  25 + public BaseDeleteTsKvQuery(String key, long startTs, long endTs, boolean rewriteLatestIfDeleted) {
  26 + super(key, startTs, endTs);
  27 + this.rewriteLatestIfDeleted = rewriteLatestIfDeleted;
  28 + }
  29 +
  30 + public BaseDeleteTsKvQuery(String key, long startTs, long endTs) {
  31 + this(key, startTs, endTs, false);
  32 + }
  33 +
  34 +
  35 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.kv;
  17 +
  18 +import lombok.Data;
  19 +
  20 +@Data
  21 +public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery {
  22 +
  23 + private final long interval;
  24 + private final int limit;
  25 + private final Aggregation aggregation;
  26 + private final String orderBy;
  27 +
  28 + public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation) {
  29 + this(key, startTs, endTs, interval, limit, aggregation, "DESC");
  30 + }
  31 +
  32 + public BaseReadTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation,
  33 + String orderBy) {
  34 + super(key, startTs, endTs);
  35 + this.interval = interval;
  36 + this.limit = limit;
  37 + this.aggregation = aggregation;
  38 + this.orderBy = orderBy;
  39 + }
  40 +
  41 + public BaseReadTsKvQuery(String key, long startTs, long endTs) {
  42 + this(key, startTs, endTs, endTs - startTs, 1, Aggregation.AVG, "DESC");
  43 + }
  44 +
  45 +}
... ...
... ... @@ -23,21 +23,11 @@ public class BaseTsKvQuery implements TsKvQuery {
23 23 private final String key;
24 24 private final long startTs;
25 25 private final long endTs;
26   - private final long interval;
27   - private final int limit;
28   - private final Aggregation aggregation;
29 26
30   - public BaseTsKvQuery(String key, long startTs, long endTs, long interval, int limit, Aggregation aggregation) {
  27 + public BaseTsKvQuery(String key, long startTs, long endTs) {
31 28 this.key = key;
32 29 this.startTs = startTs;
33 30 this.endTs = endTs;
34   - this.interval = interval;
35   - this.limit = limit;
36   - this.aggregation = aggregation;
37   - }
38   -
39   - public BaseTsKvQuery(String key, long startTs, long endTs) {
40   - this(key, startTs, endTs, endTs-startTs, 1, Aggregation.AVG);
41 31 }
42 32
43 33 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.kv;
  17 +
  18 +public interface DeleteTsKvQuery extends TsKvQuery {
  19 +
  20 + Boolean getRewriteLatestIfDeleted();
  21 +
  22 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.common.data.kv;
  17 +
  18 +public interface ReadTsKvQuery extends TsKvQuery {
  19 +
  20 + long getInterval();
  21 +
  22 + int getLimit();
  23 +
  24 + Aggregation getAggregation();
  25 +
  26 + String getOrderBy();
  27 +
  28 +}
... ...
... ... @@ -23,10 +23,4 @@ public interface TsKvQuery {
23 23
24 24 long getEndTs();
25 25
26   - long getInterval();
27   -
28   - int getLimit();
29   -
30   - Aggregation getAggregation();
31   -
32 26 }
... ...
... ... @@ -16,9 +16,7 @@
16 16 package org.thingsboard.server.dao.relation;
17 17
18 18 import com.google.common.base.Function;
19   -import com.google.common.util.concurrent.AsyncFunction;
20   -import com.google.common.util.concurrent.Futures;
21   -import com.google.common.util.concurrent.ListenableFuture;
  19 +import com.google.common.util.concurrent.*;
22 20 import lombok.extern.slf4j.Slf4j;
23 21 import org.springframework.beans.factory.annotation.Autowired;
24 22 import org.springframework.cache.Cache;
... ... @@ -41,7 +39,6 @@ import org.thingsboard.server.dao.exception.DataValidationException;
41 39
42 40 import javax.annotation.Nullable;
43 41 import java.util.ArrayList;
44   -import java.util.Arrays;
45 42 import java.util.Collections;
46 43 import java.util.HashSet;
47 44 import java.util.List;
... ... @@ -94,10 +91,10 @@ public class BaseRelationService implements RelationService {
94 91
95 92 @Caching(evict = {
96 93 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
97   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
98   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
99   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
100   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
  94 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
  95 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
  96 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
  97 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
101 98 })
102 99 @Override
103 100 public boolean saveRelation(EntityRelation relation) {
... ... @@ -108,10 +105,10 @@ public class BaseRelationService implements RelationService {
108 105
109 106 @Caching(evict = {
110 107 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
111   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
112   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
113   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
114   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
  108 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
  109 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
  110 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
  111 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
115 112 })
116 113 @Override
117 114 public ListenableFuture<Boolean> saveRelationAsync(EntityRelation relation) {
... ... @@ -122,10 +119,10 @@ public class BaseRelationService implements RelationService {
122 119
123 120 @Caching(evict = {
124 121 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
125   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
126   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
127   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
128   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
  122 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
  123 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
  124 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
  125 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
129 126 })
130 127 @Override
131 128 public boolean deleteRelation(EntityRelation relation) {
... ... @@ -136,10 +133,10 @@ public class BaseRelationService implements RelationService {
136 133
137 134 @Caching(evict = {
138 135 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.to, #relation.type, #relation.typeGroup}"),
139   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup}"),
140   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup}"),
141   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup}"),
142   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup}")
  136 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.type, #relation.typeGroup, 'FROM'}"),
  137 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.from, #relation.typeGroup, 'FROM'}"),
  138 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.typeGroup, 'TO'}"),
  139 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#relation.to, #relation.type, #relation.typeGroup, 'TO'}")
143 140 })
144 141 @Override
145 142 public ListenableFuture<Boolean> deleteRelationAsync(EntityRelation relation) {
... ... @@ -150,10 +147,10 @@ public class BaseRelationService implements RelationService {
150 147
151 148 @Caching(evict = {
152 149 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}"),
153   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}"),
154   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}"),
155   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}"),
156   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
  150 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}"),
  151 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}"),
  152 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}"),
  153 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
157 154 })
158 155 @Override
159 156 public boolean deleteRelation(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
... ... @@ -164,10 +161,10 @@ public class BaseRelationService implements RelationService {
164 161
165 162 @Caching(evict = {
166 163 @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #to, #relationType, #typeGroup}"),
167   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}"),
168   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}"),
169   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}"),
170   - @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
  164 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}"),
  165 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}"),
  166 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}"),
  167 + @CacheEvict(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
171 168 })
172 169 @Override
173 170 public ListenableFuture<Boolean> deleteRelationAsync(EntityId from, EntityId to, String relationType, RelationTypeGroup typeGroup) {
... ... @@ -250,30 +247,36 @@ public class BaseRelationService implements RelationService {
250 247 fromTypeAndTypeGroup.add(relation.getFrom());
251 248 fromTypeAndTypeGroup.add(relation.getType());
252 249 fromTypeAndTypeGroup.add(relation.getTypeGroup());
  250 + fromTypeAndTypeGroup.add(EntitySearchDirection.FROM.name());
253 251 cache.evict(fromTypeAndTypeGroup);
254 252
255 253 List<Object> fromAndTypeGroup = new ArrayList<>();
256 254 fromAndTypeGroup.add(relation.getFrom());
257 255 fromAndTypeGroup.add(relation.getTypeGroup());
  256 + fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
258 257 cache.evict(fromAndTypeGroup);
259 258
260 259 List<Object> toAndTypeGroup = new ArrayList<>();
261 260 toAndTypeGroup.add(relation.getTo());
262 261 toAndTypeGroup.add(relation.getTypeGroup());
  262 + toAndTypeGroup.add(EntitySearchDirection.TO.name());
263 263 cache.evict(toAndTypeGroup);
264 264
265 265 List<Object> toTypeAndTypeGroup = new ArrayList<>();
266   - fromTypeAndTypeGroup.add(relation.getTo());
267   - fromTypeAndTypeGroup.add(relation.getType());
268   - fromTypeAndTypeGroup.add(relation.getTypeGroup());
  266 + toTypeAndTypeGroup.add(relation.getTo());
  267 + toTypeAndTypeGroup.add(relation.getType());
  268 + toTypeAndTypeGroup.add(relation.getTypeGroup());
  269 + toTypeAndTypeGroup.add(EntitySearchDirection.TO.name());
269 270 cache.evict(toTypeAndTypeGroup);
270 271 }
271 272
272   - @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup}")
  273 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #typeGroup, 'FROM'}")
273 274 @Override
274 275 public List<EntityRelation> findByFrom(EntityId from, RelationTypeGroup typeGroup) {
  276 + validate(from);
  277 + validateTypeGroup(typeGroup);
275 278 try {
276   - return findByFromAsync(from, typeGroup).get();
  279 + return relationDao.findAllByFrom(from, typeGroup).get();
277 280 } catch (InterruptedException | ExecutionException e) {
278 281 throw new RuntimeException(e);
279 282 }
... ... @@ -284,7 +287,29 @@ public class BaseRelationService implements RelationService {
284 287 log.trace("Executing findByFrom [{}][{}]", from, typeGroup);
285 288 validate(from);
286 289 validateTypeGroup(typeGroup);
287   - return relationDao.findAllByFrom(from, typeGroup);
  290 +
  291 + List<Object> fromAndTypeGroup = new ArrayList<>();
  292 + fromAndTypeGroup.add(from);
  293 + fromAndTypeGroup.add(typeGroup);
  294 + fromAndTypeGroup.add(EntitySearchDirection.FROM.name());
  295 +
  296 + Cache cache = cacheManager.getCache(RELATIONS_CACHE);
  297 + List<EntityRelation> fromCache = cache.get(fromAndTypeGroup, List.class);
  298 + if (fromCache != null) {
  299 + return Futures.immediateFuture(fromCache);
  300 + } else {
  301 + ListenableFuture<List<EntityRelation>> relationsFuture = relationDao.findAllByFrom(from, typeGroup);
  302 + Futures.addCallback(relationsFuture,
  303 + new FutureCallback<List<EntityRelation>>() {
  304 + @Override
  305 + public void onSuccess(@Nullable List<EntityRelation> result) {
  306 + cache.putIfAbsent(fromAndTypeGroup, result);
  307 + }
  308 + @Override
  309 + public void onFailure(Throwable t) {}
  310 + });
  311 + return relationsFuture;
  312 + }
288 313 }
289 314
290 315 @Override
... ... @@ -305,7 +330,7 @@ public class BaseRelationService implements RelationService {
305 330 });
306 331 }
307 332
308   - @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup}")
  333 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#from, #relationType, #typeGroup, 'FROM'}")
309 334 @Override
310 335 public List<EntityRelation> findByFromAndType(EntityId from, String relationType, RelationTypeGroup typeGroup) {
311 336 try {
... ... @@ -324,11 +349,13 @@ public class BaseRelationService implements RelationService {
324 349 return relationDao.findAllByFromAndType(from, relationType, typeGroup);
325 350 }
326 351
327   - @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup}")
  352 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #typeGroup, 'TO'}")
328 353 @Override
329 354 public List<EntityRelation> findByTo(EntityId to, RelationTypeGroup typeGroup) {
  355 + validate(to);
  356 + validateTypeGroup(typeGroup);
330 357 try {
331   - return findByToAsync(to, typeGroup).get();
  358 + return relationDao.findAllByTo(to, typeGroup).get();
332 359 } catch (InterruptedException | ExecutionException e) {
333 360 throw new RuntimeException(e);
334 361 }
... ... @@ -339,7 +366,29 @@ public class BaseRelationService implements RelationService {
339 366 log.trace("Executing findByTo [{}][{}]", to, typeGroup);
340 367 validate(to);
341 368 validateTypeGroup(typeGroup);
342   - return relationDao.findAllByTo(to, typeGroup);
  369 +
  370 + List<Object> toAndTypeGroup = new ArrayList<>();
  371 + toAndTypeGroup.add(to);
  372 + toAndTypeGroup.add(typeGroup);
  373 + toAndTypeGroup.add(EntitySearchDirection.TO.name());
  374 +
  375 + Cache cache = cacheManager.getCache(RELATIONS_CACHE);
  376 + List<EntityRelation> fromCache = cache.get(toAndTypeGroup, List.class);
  377 + if (fromCache != null) {
  378 + return Futures.immediateFuture(fromCache);
  379 + } else {
  380 + ListenableFuture<List<EntityRelation>> relationsFuture = relationDao.findAllByTo(to, typeGroup);
  381 + Futures.addCallback(relationsFuture,
  382 + new FutureCallback<List<EntityRelation>>() {
  383 + @Override
  384 + public void onSuccess(@Nullable List<EntityRelation> result) {
  385 + cache.putIfAbsent(toAndTypeGroup, result);
  386 + }
  387 + @Override
  388 + public void onFailure(Throwable t) {}
  389 + });
  390 + return relationsFuture;
  391 + }
343 392 }
344 393
345 394 @Override
... ... @@ -371,7 +420,7 @@ public class BaseRelationService implements RelationService {
371 420 });
372 421 }
373 422
374   - @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup}")
  423 + @Cacheable(cacheNames = RELATIONS_CACHE, key = "{#to, #relationType, #typeGroup, 'TO'}")
375 424 @Override
376 425 public List<EntityRelation> findByToAndType(EntityId to, String relationType, RelationTypeGroup typeGroup) {
377 426 try {
... ...
... ... @@ -31,9 +31,10 @@ import org.thingsboard.server.common.data.UUIDConverter;
31 31 import org.thingsboard.server.common.data.id.EntityId;
32 32 import org.thingsboard.server.common.data.kv.Aggregation;
33 33 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
  34 +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
  35 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
34 36 import org.thingsboard.server.common.data.kv.StringDataEntry;
35 37 import org.thingsboard.server.common.data.kv.TsKvEntry;
36   -import org.thingsboard.server.common.data.kv.TsKvQuery;
37 38 import org.thingsboard.server.dao.DaoUtil;
38 39 import org.thingsboard.server.dao.model.sql.TsKvEntity;
39 40 import org.thingsboard.server.dao.model.sql.TsKvLatestCompositeKey;
... ... @@ -102,7 +103,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
102 103 }
103 104
104 105 @Override
105   - public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
  106 + public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
106 107 List<ListenableFuture<List<TsKvEntry>>> futures = queries
107 108 .stream()
108 109 .map(query -> findAllAsync(entityId, query))
... ... @@ -121,7 +122,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
121 122 }, service);
122 123 }
123 124
124   - private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
  125 + private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
125 126 if (query.getAggregation() == Aggregation.NONE) {
126 127 return findAllAsyncWithLimit(entityId, query);
127 128 } else {
... ... @@ -228,7 +229,7 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
228 229 });
229 230 }
230 231
231   - private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
  232 + private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
232 233 return Futures.immediateFuture(
233 234 DaoUtil.convertDataList(
234 235 tsKvRepository.findAllWithLimit(
... ... @@ -306,6 +307,36 @@ public class JpaTimeseriesDao extends JpaAbstractDaoListeningExecutorService imp
306 307 });
307 308 }
308 309
  310 + @Override
  311 + public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
  312 + return service.submit(() -> {
  313 + tsKvRepository.delete(
  314 + fromTimeUUID(entityId.getId()),
  315 + entityId.getEntityType(),
  316 + query.getKey(),
  317 + query.getStartTs(),
  318 + query.getEndTs());
  319 + return null;
  320 + });
  321 + }
  322 +
  323 + @Override
  324 + public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
  325 + TsKvLatestEntity latestEntity = new TsKvLatestEntity();
  326 + latestEntity.setEntityType(entityId.getEntityType());
  327 + latestEntity.setEntityId(fromTimeUUID(entityId.getId()));
  328 + latestEntity.setKey(query.getKey());
  329 + return service.submit(() -> {
  330 + tsKvLatestRepository.delete(latestEntity);
  331 + return null;
  332 + });
  333 + }
  334 +
  335 + @Override
  336 + public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
  337 + return service.submit(() -> null);
  338 + }
  339 +
309 340 @PreDestroy
310 341 void onDestroy() {
311 342 if (insertService != null) {
... ...
... ... @@ -16,10 +16,12 @@
16 16 package org.thingsboard.server.dao.sql.timeseries;
17 17
18 18 import org.springframework.data.domain.Pageable;
  19 +import org.springframework.data.jpa.repository.Modifying;
19 20 import org.springframework.data.jpa.repository.Query;
20 21 import org.springframework.data.repository.CrudRepository;
21 22 import org.springframework.data.repository.query.Param;
22 23 import org.springframework.scheduling.annotation.Async;
  24 +import org.springframework.transaction.annotation.Transactional;
23 25 import org.thingsboard.server.common.data.EntityType;
24 26 import org.thingsboard.server.dao.model.sql.TsKvCompositeKey;
25 27 import org.thingsboard.server.dao.model.sql.TsKvEntity;
... ... @@ -41,6 +43,17 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
41 43 @Param("endTs") long endTs,
42 44 Pageable pageable);
43 45
  46 + @Transactional
  47 + @Modifying
  48 + @Query("DELETE FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " +
  49 + "AND tskv.entityType = :entityType AND tskv.key = :entityKey " +
  50 + "AND tskv.ts > :startTs AND tskv.ts < :endTs")
  51 + void delete(@Param("entityId") String entityId,
  52 + @Param("entityType") EntityType entityType,
  53 + @Param("entityKey") String key,
  54 + @Param("startTs") long startTs,
  55 + @Param("endTs") long endTs);
  56 +
44 57 @Async
45 58 @Query("SELECT new TsKvEntity(MAX(tskv.strValue), MAX(tskv.longValue), MAX(tskv.doubleValue)) FROM TsKvEntity tskv " +
46 59 "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
... ... @@ -56,30 +69,30 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
56 69 "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
57 70 "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
58 71 CompletableFuture<TsKvEntity> findMin(@Param("entityId") String entityId,
59   - @Param("entityType") EntityType entityType,
60   - @Param("entityKey") String entityKey,
61   - @Param("startTs") long startTs,
62   - @Param("endTs") long endTs);
  72 + @Param("entityType") EntityType entityType,
  73 + @Param("entityKey") String entityKey,
  74 + @Param("startTs") long startTs,
  75 + @Param("endTs") long endTs);
63 76
64 77 @Async
65 78 @Query("SELECT new TsKvEntity(COUNT(tskv.booleanValue), COUNT(tskv.strValue), COUNT(tskv.longValue), COUNT(tskv.doubleValue)) FROM TsKvEntity tskv " +
66 79 "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
67 80 "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
68 81 CompletableFuture<TsKvEntity> findCount(@Param("entityId") String entityId,
69   - @Param("entityType") EntityType entityType,
70   - @Param("entityKey") String entityKey,
71   - @Param("startTs") long startTs,
72   - @Param("endTs") long endTs);
  82 + @Param("entityType") EntityType entityType,
  83 + @Param("entityKey") String entityKey,
  84 + @Param("startTs") long startTs,
  85 + @Param("endTs") long endTs);
73 86
74 87 @Async
75 88 @Query("SELECT new TsKvEntity(AVG(tskv.longValue), AVG(tskv.doubleValue)) FROM TsKvEntity tskv " +
76 89 "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
77 90 "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
78 91 CompletableFuture<TsKvEntity> findAvg(@Param("entityId") String entityId,
79   - @Param("entityType") EntityType entityType,
80   - @Param("entityKey") String entityKey,
81   - @Param("startTs") long startTs,
82   - @Param("endTs") long endTs);
  92 + @Param("entityType") EntityType entityType,
  93 + @Param("entityKey") String entityKey,
  94 + @Param("startTs") long startTs,
  95 + @Param("endTs") long endTs);
83 96
84 97
85 98 @Async
... ... @@ -87,8 +100,8 @@ public interface TsKvRepository extends CrudRepository<TsKvEntity, TsKvComposite
87 100 "WHERE tskv.entityId = :entityId AND tskv.entityType = :entityType " +
88 101 "AND tskv.key = :entityKey AND tskv.ts > :startTs AND tskv.ts < :endTs")
89 102 CompletableFuture<TsKvEntity> findSum(@Param("entityId") String entityId,
90   - @Param("entityType") EntityType entityType,
91   - @Param("entityKey") String entityKey,
92   - @Param("startTs") long startTs,
93   - @Param("endTs") long endTs);
  103 + @Param("entityType") EntityType entityType,
  104 + @Param("entityKey") String entityKey,
  105 + @Param("startTs") long startTs,
  106 + @Param("endTs") long endTs);
94 107 }
... ...
... ... @@ -25,9 +25,10 @@ import org.thingsboard.server.common.data.EntityType;
25 25 import org.thingsboard.server.common.data.EntityView;
26 26 import org.thingsboard.server.common.data.id.EntityId;
27 27 import org.thingsboard.server.common.data.id.EntityViewId;
28   -import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  28 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
  29 +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
  30 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
29 31 import org.thingsboard.server.common.data.kv.TsKvEntry;
30   -import org.thingsboard.server.common.data.kv.TsKvQuery;
31 32 import org.thingsboard.server.dao.entityview.EntityViewService;
32 33 import org.thingsboard.server.dao.exception.IncorrectParameterException;
33 34 import org.thingsboard.server.dao.service.Validator;
... ... @@ -46,6 +47,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
46 47 public class BaseTimeseriesService implements TimeseriesService {
47 48
48 49 public static final int INSERTS_PER_ENTRY = 3;
  50 + public static final int DELETES_PER_ENTRY = INSERTS_PER_ENTRY;
49 51
50 52 @Autowired
51 53 private TimeseriesDao timeseriesDao;
... ... @@ -54,9 +56,9 @@ public class BaseTimeseriesService implements TimeseriesService {
54 56 private EntityViewService entityViewService;
55 57
56 58 @Override
57   - public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries) {
  59 + public ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries) {
58 60 validate(entityId);
59   - queries.forEach(query -> validate(query));
  61 + queries.forEach(BaseTimeseriesService::validate);
60 62 if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
61 63 EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
62 64 return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries));
... ... @@ -69,13 +71,14 @@ public class BaseTimeseriesService implements TimeseriesService {
69 71 validate(entityId);
70 72 List<ListenableFuture<TsKvEntry>> futures = Lists.newArrayListWithExpectedSize(keys.size());
71 73 keys.forEach(key -> Validator.validateString(key, "Incorrect key " + key));
72   - if (false/*entityId.getEntityType().equals(EntityType.ENTITY_VIEW)*/) {
  74 + if (entityId.getEntityType().equals(EntityType.ENTITY_VIEW)) {
73 75 EntityView entityView = entityViewService.findEntityViewById((EntityViewId) entityId);
74   - Collection<String> newKeys = chooseKeysForEntityView(entityView, keys);
75   - newKeys.forEach(newKey -> futures.add(timeseriesDao.findLatest(entityView.getEntityId(), newKey)));
76   - } else {
77   - keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
  76 + Collection<String> matchingKeys = chooseKeysForEntityView(entityView, keys);
  77 + List<ReadTsKvQuery> queries = new ArrayList<>();
  78 + matchingKeys.forEach(key -> queries.add(new BaseReadTsKvQuery(key, entityView.getStartTs(), entityView.getEndTs())));
  79 + return timeseriesDao.findAllAsync(entityView.getEntityId(), updateQueriesForEntityView(entityView, queries));
78 80 }
  81 + keys.forEach(key -> futures.add(timeseriesDao.findLatest(entityId, key)));
79 82 return Futures.allAsList(futures);
80 83 }
81 84
... ... @@ -129,8 +132,8 @@ public class BaseTimeseriesService implements TimeseriesService {
129 132 futures.add(timeseriesDao.save(entityId, tsKvEntry, ttl));
130 133 }
131 134
132   - private List<TsKvQuery> updateQueriesForEntityView(EntityView entityView, List<TsKvQuery> queries) {
133   - List<TsKvQuery> newQueries = new ArrayList<>();
  135 + private List<ReadTsKvQuery> updateQueriesForEntityView(EntityView entityView, List<ReadTsKvQuery> queries) {
  136 + List<ReadTsKvQuery> newQueries = new ArrayList<>();
134 137 entityView.getKeys().getTimeseries()
135 138 .forEach(viewKey -> queries
136 139 .forEach(query -> {
... ... @@ -148,7 +151,6 @@ public class BaseTimeseriesService implements TimeseriesService {
148 151 return newQueries;
149 152 }
150 153
151   - @Deprecated /*Will be a modified*/
152 154 private Collection<String> chooseKeysForEntityView(EntityView entityView, Collection<String> keys) {
153 155 Collection<String> newKeys = new ArrayList<>();
154 156 entityView.getKeys().getTimeseries()
... ... @@ -156,27 +158,53 @@ public class BaseTimeseriesService implements TimeseriesService {
156 158 .forEach(key -> {
157 159 if (key.equals(viewKey)) {
158 160 newKeys.add(key);
159   - }}));
  161 + }
  162 + }));
160 163 return newKeys;
161 164 }
162 165
  166 + @Override
  167 + public ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> deleteTsKvQueries) {
  168 + validate(entityId);
  169 + deleteTsKvQueries.forEach(BaseTimeseriesService::validate);
  170 + List<ListenableFuture<Void>> futures = Lists.newArrayListWithExpectedSize(deleteTsKvQueries.size() * DELETES_PER_ENTRY);
  171 + for (DeleteTsKvQuery tsKvQuery : deleteTsKvQueries) {
  172 + deleteAndRegisterFutures(futures, entityId, tsKvQuery);
  173 + }
  174 + return Futures.allAsList(futures);
  175 + }
  176 +
  177 + private void deleteAndRegisterFutures(List<ListenableFuture<Void>> futures, EntityId entityId, DeleteTsKvQuery query) {
  178 + futures.add(timeseriesDao.remove(entityId, query));
  179 + futures.add(timeseriesDao.removeLatest(entityId, query));
  180 + futures.add(timeseriesDao.removePartition(entityId, query));
  181 + }
  182 +
163 183 private static void validate(EntityId entityId) {
164 184 Validator.validateEntityId(entityId, "Incorrect entityId " + entityId);
165 185 }
166 186
167   - private static void validate(TsKvQuery query) {
  187 + private static void validate(ReadTsKvQuery query) {
168 188 if (query == null) {
169   - throw new IncorrectParameterException("TsKvQuery can't be null");
  189 + throw new IncorrectParameterException("ReadTsKvQuery can't be null");
170 190 } else if (isBlank(query.getKey())) {
171   - throw new IncorrectParameterException("Incorrect TsKvQuery. Key can't be empty");
  191 + throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Key can't be empty");
172 192 } else if (query.getAggregation() == null) {
173   - throw new IncorrectParameterException("Incorrect TsKvQuery. Aggregation can't be empty");
  193 + throw new IncorrectParameterException("Incorrect ReadTsKvQuery. Aggregation can't be empty");
  194 + }
  195 + }
  196 +
  197 + private static void validate(DeleteTsKvQuery query) {
  198 + if (query == null) {
  199 + throw new IncorrectParameterException("DeleteTsKvQuery can't be null");
  200 + } else if (isBlank(query.getKey())) {
  201 + throw new IncorrectParameterException("Incorrect DeleteTsKvQuery. Key can't be empty");
174 202 }
175 203 }
176 204
177   - private static TsKvQuery updateQuery(Long startTs, Long endTs, String viewKey, TsKvQuery query) {
  205 + private static ReadTsKvQuery updateQuery(Long startTs, Long endTs, String viewKey, ReadTsKvQuery query) {
178 206 return startTs <= query.getStartTs() && endTs >= query.getEndTs() ? query :
179   - new BaseTsKvQuery(viewKey, startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
  207 + new BaseReadTsKvQuery(viewKey, startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation());
180 208 }
181 209
182 210 private static void checkForNonEntityView(EntityId entityId) throws Exception {
... ...
... ... @@ -20,6 +20,7 @@ import com.datastax.driver.core.PreparedStatement;
20 20 import com.datastax.driver.core.ResultSet;
21 21 import com.datastax.driver.core.ResultSetFuture;
22 22 import com.datastax.driver.core.Row;
  23 +import com.datastax.driver.core.Statement;
23 24 import com.datastax.driver.core.querybuilder.QueryBuilder;
24 25 import com.datastax.driver.core.querybuilder.Select;
25 26 import com.google.common.base.Function;
... ... @@ -34,16 +35,17 @@ import org.springframework.core.env.Environment;
34 35 import org.springframework.stereotype.Component;
35 36 import org.thingsboard.server.common.data.id.EntityId;
36 37 import org.thingsboard.server.common.data.kv.Aggregation;
37   -import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  38 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
38 39 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
39 40 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
40 41 import org.thingsboard.server.common.data.kv.DataType;
  42 +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
41 43 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
42 44 import org.thingsboard.server.common.data.kv.KvEntry;
43 45 import org.thingsboard.server.common.data.kv.LongDataEntry;
  46 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
44 47 import org.thingsboard.server.common.data.kv.StringDataEntry;
45 48 import org.thingsboard.server.common.data.kv.TsKvEntry;
46   -import org.thingsboard.server.common.data.kv.TsKvQuery;
47 49 import org.thingsboard.server.dao.model.ModelConstants;
48 50 import org.thingsboard.server.dao.nosql.CassandraAbstractAsyncDao;
49 51 import org.thingsboard.server.dao.util.NoSqlDao;
... ... @@ -54,11 +56,11 @@ import javax.annotation.PreDestroy;
54 56 import java.time.Instant;
55 57 import java.time.LocalDateTime;
56 58 import java.time.ZoneOffset;
  59 +import java.util.ArrayList;
57 60 import java.util.Arrays;
  61 +import java.util.Collections;
58 62 import java.util.List;
59   -import java.util.ArrayList;
60 63 import java.util.Optional;
61   -import java.util.Collections;
62 64 import java.util.stream.Collectors;
63 65
64 66 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
... ... @@ -76,8 +78,8 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
76 78 public static final String GENERATED_QUERY_FOR_ENTITY_TYPE_AND_ENTITY_ID = "Generated query [{}] for entityType {} and entityId {}";
77 79 public static final String SELECT_PREFIX = "SELECT ";
78 80 public static final String EQUALS_PARAM = " = ? ";
79   -
80   -
  81 + public static final String ASC_ORDER = "ASC";
  82 + public static final String DESC_ORDER = "DESC";
81 83 private static List<Long> FIXED_PARTITION = Arrays.asList(new Long[]{0L});
82 84
83 85 @Autowired
... ... @@ -96,9 +98,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
96 98 private PreparedStatement latestInsertStmt;
97 99 private PreparedStatement[] saveStmts;
98 100 private PreparedStatement[] saveTtlStmts;
99   - private PreparedStatement[] fetchStmts;
  101 + private PreparedStatement[] fetchStmtsAsc;
  102 + private PreparedStatement[] fetchStmtsDesc;
100 103 private PreparedStatement findLatestStmt;
101 104 private PreparedStatement findAllLatestStmt;
  105 + private PreparedStatement deleteStmt;
  106 + private PreparedStatement deletePartitionStmt;
102 107
103 108 private boolean isInstall() {
104 109 return environment.acceptsProfiles("install");
... ... @@ -108,7 +113,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
108 113 public void init() {
109 114 super.startExecutor();
110 115 if (!isInstall()) {
111   - getFetchStmt(Aggregation.NONE);
  116 + getFetchStmt(Aggregation.NONE, DESC_ORDER);
112 117 Optional<TsPartitionDate> partition = TsPartitionDate.parse(partitioning);
113 118 if (partition.isPresent()) {
114 119 tsFormat = partition.get();
... ... @@ -125,7 +130,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
125 130 }
126 131
127 132 @Override
128   - public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries) {
  133 + public ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries) {
129 134 List<ListenableFuture<List<TsKvEntry>>> futures = queries.stream().map(query -> findAllAsync(entityId, query)).collect(Collectors.toList());
130 135 return Futures.transform(Futures.allAsList(futures), new Function<List<List<TsKvEntry>>, List<TsKvEntry>>() {
131 136 @Nullable
... ... @@ -142,7 +147,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
142 147 }
143 148
144 149
145   - private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, TsKvQuery query) {
  150 + private ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, ReadTsKvQuery query) {
146 151 if (query.getAggregation() == Aggregation.NONE) {
147 152 return findAllAsyncWithLimit(entityId, query);
148 153 } else {
... ... @@ -152,7 +157,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
152 157 while (stepTs < query.getEndTs()) {
153 158 long startTs = stepTs;
154 159 long endTs = stepTs + step;
155   - TsKvQuery subQuery = new BaseTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation());
  160 + ReadTsKvQuery subQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, step, 1, query.getAggregation(), query.getOrderBy());
156 161 futures.add(findAndAggregateAsync(entityId, subQuery, toPartitionTs(startTs), toPartitionTs(endTs)));
157 162 stepTs = endTs;
158 163 }
... ... @@ -171,7 +176,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
171 176 return tsFormat.getTruncateUnit().equals(TsPartitionDate.EPOCH_START);
172 177 }
173 178
174   - private ListenableFuture<List<Long>> getPartitionsFuture(TsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
  179 + private ListenableFuture<List<Long>> getPartitionsFuture(ReadTsKvQuery query, EntityId entityId, long minPartition, long maxPartition) {
175 180 if (isFixedPartitioning()) { //no need to fetch partitions from DB
176 181 return Futures.immediateFuture(FIXED_PARTITION);
177 182 }
... ... @@ -179,11 +184,9 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
179 184 return Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
180 185 }
181 186
182   - private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, TsKvQuery query) {
183   -
  187 + private ListenableFuture<List<TsKvEntry>> findAllAsyncWithLimit(EntityId entityId, ReadTsKvQuery query) {
184 188 long minPartition = toPartitionTs(query.getStartTs());
185 189 long maxPartition = toPartitionTs(query.getEndTs());
186   -
187 190 final ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
188 191 final SimpleListenableFuture<List<TsKvEntry>> resultFuture = new SimpleListenableFuture<>();
189 192
... ... @@ -212,7 +215,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
212 215 if (cursor.isFull() || !cursor.hasNextPartition()) {
213 216 resultFuture.set(cursor.getData());
214 217 } else {
215   - PreparedStatement proto = getFetchStmt(Aggregation.NONE);
  218 + PreparedStatement proto = getFetchStmt(Aggregation.NONE, cursor.getOrderBy());
216 219 BoundStatement stmt = proto.bind();
217 220 stmt.setString(0, cursor.getEntityType());
218 221 stmt.setUUID(1, cursor.getEntityId());
... ... @@ -237,14 +240,12 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
237 240 }
238 241 }
239 242
240   - private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, TsKvQuery query, long minPartition, long maxPartition) {
  243 + private ListenableFuture<Optional<TsKvEntry>> findAndAggregateAsync(EntityId entityId, ReadTsKvQuery query, long minPartition, long maxPartition) {
241 244 final Aggregation aggregation = query.getAggregation();
242 245 final String key = query.getKey();
243 246 final long startTs = query.getStartTs();
244 247 final long endTs = query.getEndTs();
245 248 final long ts = startTs + (endTs - startTs) / 2;
246   -
247   -
248 249 ListenableFuture<List<Long>> partitionsListFuture = getPartitionsFuture(query, entityId, minPartition, maxPartition);
249 250 ListenableFuture<List<ResultSet>> aggregationChunks = Futures.transformAsync(partitionsListFuture,
250 251 getFetchChunksAsyncFunction(entityId, key, aggregation, startTs, endTs), readResultsProcessingExecutor);
... ... @@ -260,7 +261,7 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
260 261 private AsyncFunction<List<Long>, List<ResultSet>> getFetchChunksAsyncFunction(EntityId entityId, String key, Aggregation aggregation, long startTs, long endTs) {
261 262 return partitions -> {
262 263 try {
263   - PreparedStatement proto = getFetchStmt(aggregation);
  264 + PreparedStatement proto = getFetchStmt(aggregation, DESC_ORDER);
264 265 List<ResultSetFuture> futures = new ArrayList<>(partitions.size());
265 266 for (Long partition : partitions) {
266 267 log.trace("Fetching data for partition [{}] for entityType {} and entityId {}", partition, entityId.getEntityType(), entityId.getId());
... ... @@ -363,6 +364,204 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
363 364 return getFuture(executeAsyncWrite(stmt), rs -> null);
364 365 }
365 366
  367 + @Override
  368 + public ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query) {
  369 + long minPartition = toPartitionTs(query.getStartTs());
  370 + long maxPartition = toPartitionTs(query.getEndTs());
  371 +
  372 + ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
  373 +
  374 + final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
  375 + final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
  376 +
  377 + Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
  378 + @Override
  379 + public void onSuccess(@Nullable List<Long> partitions) {
  380 + QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitions);
  381 + deleteAsync(cursor, resultFuture);
  382 + }
  383 +
  384 + @Override
  385 + public void onFailure(Throwable t) {
  386 + log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
  387 + }
  388 + }, readResultsProcessingExecutor);
  389 + return resultFuture;
  390 + }
  391 +
  392 + private void deleteAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
  393 + if (!cursor.hasNextPartition()) {
  394 + resultFuture.set(null);
  395 + } else {
  396 + PreparedStatement proto = getDeleteStmt();
  397 + BoundStatement stmt = proto.bind();
  398 + stmt.setString(0, cursor.getEntityType());
  399 + stmt.setUUID(1, cursor.getEntityId());
  400 + stmt.setString(2, cursor.getKey());
  401 + stmt.setLong(3, cursor.getNextPartition());
  402 + stmt.setLong(4, cursor.getStartTs());
  403 + stmt.setLong(5, cursor.getEndTs());
  404 +
  405 + Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
  406 + @Override
  407 + public void onSuccess(@Nullable ResultSet result) {
  408 + deleteAsync(cursor, resultFuture);
  409 + }
  410 +
  411 + @Override
  412 + public void onFailure(Throwable t) {
  413 + log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
  414 + }
  415 + }, readResultsProcessingExecutor);
  416 + }
  417 + }
  418 +
  419 + private PreparedStatement getDeleteStmt() {
  420 + if (deleteStmt == null) {
  421 + deleteStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_CF +
  422 + " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
  423 + + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
  424 + + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
  425 + + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
  426 + + "AND " + ModelConstants.TS_COLUMN + " > ? "
  427 + + "AND " + ModelConstants.TS_COLUMN + " <= ?");
  428 + }
  429 + return deleteStmt;
  430 + }
  431 +
  432 + @Override
  433 + public ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query) {
  434 + ListenableFuture<TsKvEntry> latestEntryFuture = findLatest(entityId, query.getKey());
  435 +
  436 + ListenableFuture<Boolean> booleanFuture = Futures.transformAsync(latestEntryFuture, latestEntry -> {
  437 + long ts = latestEntry.getTs();
  438 + if (ts >= query.getStartTs() && ts <= query.getEndTs()) {
  439 + return Futures.immediateFuture(true);
  440 + } else {
  441 + log.trace("Won't be deleted latest value for [{}], key - {}", entityId, query.getKey());
  442 + }
  443 + return Futures.immediateFuture(false);
  444 + }, readResultsProcessingExecutor);
  445 +
  446 + ListenableFuture<Void> removedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
  447 + if (isRemove) {
  448 + return deleteLatest(entityId, query.getKey());
  449 + }
  450 + return Futures.immediateFuture(null);
  451 + }, readResultsProcessingExecutor);
  452 +
  453 + if (query.getRewriteLatestIfDeleted()) {
  454 + ListenableFuture<Void> savedLatestFuture = Futures.transformAsync(booleanFuture, isRemove -> {
  455 + if (isRemove) {
  456 + return getNewLatestEntryFuture(entityId, query);
  457 + }
  458 + return Futures.immediateFuture(null);
  459 + }, readResultsProcessingExecutor);
  460 +
  461 + return Futures.transformAsync(Futures.allAsList(Arrays.asList(savedLatestFuture, removedLatestFuture)),
  462 + list -> Futures.immediateFuture(null), readResultsProcessingExecutor);
  463 + }
  464 + return removedLatestFuture;
  465 + }
  466 +
  467 + private ListenableFuture<Void> getNewLatestEntryFuture(EntityId entityId, DeleteTsKvQuery query) {
  468 + long startTs = 0;
  469 + long endTs = query.getStartTs() - 1;
  470 + ReadTsKvQuery findNewLatestQuery = new BaseReadTsKvQuery(query.getKey(), startTs, endTs, endTs - startTs, 1,
  471 + Aggregation.NONE, DESC_ORDER);
  472 + ListenableFuture<List<TsKvEntry>> future = findAllAsync(entityId, findNewLatestQuery);
  473 +
  474 + return Futures.transformAsync(future, entryList -> {
  475 + if (entryList.size() == 1) {
  476 + return saveLatest(entityId, entryList.get(0));
  477 + } else {
  478 + log.trace("Could not find new latest value for [{}], key - {}", entityId, query.getKey());
  479 + }
  480 + return Futures.immediateFuture(null);
  481 + }, readResultsProcessingExecutor);
  482 + }
  483 +
  484 + private ListenableFuture<Void> deleteLatest(EntityId entityId, String key) {
  485 + Statement delete = QueryBuilder.delete().all().from(ModelConstants.TS_KV_LATEST_CF)
  486 + .where(eq(ModelConstants.ENTITY_TYPE_COLUMN, entityId.getEntityType()))
  487 + .and(eq(ModelConstants.ENTITY_ID_COLUMN, entityId.getId()))
  488 + .and(eq(ModelConstants.KEY_COLUMN, key));
  489 + log.debug("Remove request: {}", delete.toString());
  490 + return getFuture(executeAsyncWrite(delete), rs -> null);
  491 + }
  492 +
  493 + @Override
  494 + public ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query) {
  495 + long minPartition = toPartitionTs(query.getStartTs());
  496 + long maxPartition = toPartitionTs(query.getEndTs());
  497 + if (minPartition == maxPartition) {
  498 + return Futures.immediateFuture(null);
  499 + } else {
  500 + ResultSetFuture partitionsFuture = fetchPartitions(entityId, query.getKey(), minPartition, maxPartition);
  501 +
  502 + final SimpleListenableFuture<Void> resultFuture = new SimpleListenableFuture<>();
  503 + final ListenableFuture<List<Long>> partitionsListFuture = Futures.transform(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor);
  504 +
  505 + Futures.addCallback(partitionsListFuture, new FutureCallback<List<Long>>() {
  506 + @Override
  507 + public void onSuccess(@Nullable List<Long> partitions) {
  508 + int index = 0;
  509 + if (minPartition != query.getStartTs()) {
  510 + index = 1;
  511 + }
  512 + List<Long> partitionsToDelete = new ArrayList<>();
  513 + for (int i = index; i < partitions.size() - 1; i++) {
  514 + partitionsToDelete.add(partitions.get(i));
  515 + }
  516 + QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitionsToDelete);
  517 + deletePartitionAsync(cursor, resultFuture);
  518 + }
  519 +
  520 + @Override
  521 + public void onFailure(Throwable t) {
  522 + log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t);
  523 + }
  524 + }, readResultsProcessingExecutor);
  525 + return resultFuture;
  526 + }
  527 + }
  528 +
  529 + private void deletePartitionAsync(final QueryCursor cursor, final SimpleListenableFuture<Void> resultFuture) {
  530 + if (!cursor.hasNextPartition()) {
  531 + resultFuture.set(null);
  532 + } else {
  533 + PreparedStatement proto = getDeletePartitionStmt();
  534 + BoundStatement stmt = proto.bind();
  535 + stmt.setString(0, cursor.getEntityType());
  536 + stmt.setUUID(1, cursor.getEntityId());
  537 + stmt.setLong(2, cursor.getNextPartition());
  538 + stmt.setString(3, cursor.getKey());
  539 +
  540 + Futures.addCallback(executeAsyncWrite(stmt), new FutureCallback<ResultSet>() {
  541 + @Override
  542 + public void onSuccess(@Nullable ResultSet result) {
  543 + deletePartitionAsync(cursor, resultFuture);
  544 + }
  545 +
  546 + @Override
  547 + public void onFailure(Throwable t) {
  548 + log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t);
  549 + }
  550 + }, readResultsProcessingExecutor);
  551 + }
  552 + }
  553 +
  554 + private PreparedStatement getDeletePartitionStmt() {
  555 + if (deletePartitionStmt == null) {
  556 + deletePartitionStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_PARTITIONS_CF +
  557 + " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
  558 + + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
  559 + + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
  560 + + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM);
  561 + }
  562 + return deletePartitionStmt;
  563 + }
  564 +
366 565 private List<TsKvEntry> convertResultToTsKvEntryList(List<Row> rows) {
367 566 List<TsKvEntry> entries = new ArrayList<>(rows.size());
368 567 if (!rows.isEmpty()) {
... ... @@ -458,28 +657,43 @@ public class CassandraBaseTimeseriesDao extends CassandraAbstractAsyncDao implem
458 657 return saveTtlStmts[dataType.ordinal()];
459 658 }
460 659
461   - private PreparedStatement getFetchStmt(Aggregation aggType) {
462   - if (fetchStmts == null) {
463   - fetchStmts = new PreparedStatement[Aggregation.values().length];
464   - for (Aggregation type : Aggregation.values()) {
465   - if (type == Aggregation.SUM && fetchStmts[Aggregation.AVG.ordinal()] != null) {
466   - fetchStmts[type.ordinal()] = fetchStmts[Aggregation.AVG.ordinal()];
467   - } else if (type == Aggregation.AVG && fetchStmts[Aggregation.SUM.ordinal()] != null) {
468   - fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()];
469   - } else {
470   - fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX +
471   - String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
472   - + " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
473   - + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
474   - + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
475   - + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
476   - + "AND " + ModelConstants.TS_COLUMN + " > ? "
477   - + "AND " + ModelConstants.TS_COLUMN + " <= ?"
478   - + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " DESC LIMIT ?" : ""));
  660 + private PreparedStatement getFetchStmt(Aggregation aggType, String orderBy) {
  661 + switch (orderBy) {
  662 + case ASC_ORDER:
  663 + if (fetchStmtsAsc == null) {
  664 + fetchStmtsAsc = initFetchStmt(orderBy);
  665 + }
  666 + return fetchStmtsAsc[aggType.ordinal()];
  667 + case DESC_ORDER:
  668 + if (fetchStmtsDesc == null) {
  669 + fetchStmtsDesc = initFetchStmt(orderBy);
479 670 }
  671 + return fetchStmtsDesc[aggType.ordinal()];
  672 + default:
  673 + throw new RuntimeException("Not supported" + orderBy + "order!");
  674 + }
  675 + }
  676 +
  677 + private PreparedStatement[] initFetchStmt(String orderBy) {
  678 + PreparedStatement[] fetchStmts = new PreparedStatement[Aggregation.values().length];
  679 + for (Aggregation type : Aggregation.values()) {
  680 + if (type == Aggregation.SUM && fetchStmts[Aggregation.AVG.ordinal()] != null) {
  681 + fetchStmts[type.ordinal()] = fetchStmts[Aggregation.AVG.ordinal()];
  682 + } else if (type == Aggregation.AVG && fetchStmts[Aggregation.SUM.ordinal()] != null) {
  683 + fetchStmts[type.ordinal()] = fetchStmts[Aggregation.SUM.ordinal()];
  684 + } else {
  685 + fetchStmts[type.ordinal()] = prepare(SELECT_PREFIX +
  686 + String.join(", ", ModelConstants.getFetchColumnNames(type)) + " FROM " + ModelConstants.TS_KV_CF
  687 + + " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM
  688 + + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM
  689 + + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM
  690 + + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM
  691 + + "AND " + ModelConstants.TS_COLUMN + " > ? "
  692 + + "AND " + ModelConstants.TS_COLUMN + " <= ?"
  693 + + (type == Aggregation.NONE ? " ORDER BY " + ModelConstants.TS_COLUMN + " " + orderBy + " LIMIT ?" : ""));
480 694 }
481 695 }
482   - return fetchStmts[aggType.ordinal()];
  696 + return fetchStmts;
483 697 }
484 698
485 699 private PreparedStatement getLatestStmt() {
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.timeseries;
  17 +
  18 +import lombok.Getter;
  19 +import org.thingsboard.server.common.data.kv.TsKvQuery;
  20 +
  21 +import java.util.List;
  22 +import java.util.UUID;
  23 +
  24 +public class QueryCursor {
  25 +
  26 + @Getter
  27 + protected final String entityType;
  28 + @Getter
  29 + protected final UUID entityId;
  30 + @Getter
  31 + protected final String key;
  32 + @Getter
  33 + private final long startTs;
  34 + @Getter
  35 + private final long endTs;
  36 +
  37 + final List<Long> partitions;
  38 + private int partitionIndex;
  39 +
  40 + public QueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
  41 + this.entityType = entityType;
  42 + this.entityId = entityId;
  43 + this.key = baseQuery.getKey();
  44 + this.startTs = baseQuery.getStartTs();
  45 + this.endTs = baseQuery.getEndTs();
  46 + this.partitions = partitions;
  47 + this.partitionIndex = partitions.size() - 1;
  48 + }
  49 +
  50 + public boolean hasNextPartition() {
  51 + return partitionIndex >= 0;
  52 + }
  53 +
  54 + public long getNextPartition() {
  55 + long partition = partitions.get(partitionIndex);
  56 + partitionIndex--;
  57 + return partition;
  58 + }
  59 +
  60 +}
... ...
... ... @@ -17,8 +17,9 @@ package org.thingsboard.server.dao.timeseries;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.id.EntityId;
  20 +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
  21 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
20 22 import org.thingsboard.server.common.data.kv.TsKvEntry;
21   -import org.thingsboard.server.common.data.kv.TsKvQuery;
22 23
23 24 import java.util.List;
24 25
... ... @@ -27,7 +28,7 @@ import java.util.List;
27 28 */
28 29 public interface TimeseriesDao {
29 30
30   - ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<TsKvQuery> queries);
  31 + ListenableFuture<List<TsKvEntry>> findAllAsync(EntityId entityId, List<ReadTsKvQuery> queries);
31 32
32 33 ListenableFuture<TsKvEntry> findLatest(EntityId entityId, String key);
33 34
... ... @@ -38,4 +39,10 @@ public interface TimeseriesDao {
38 39 ListenableFuture<Void> savePartition(EntityId entityId, long tsKvEntryTs, String key, long ttl);
39 40
40 41 ListenableFuture<Void> saveLatest(EntityId entityId, TsKvEntry tsKvEntry);
  42 +
  43 + ListenableFuture<Void> remove(EntityId entityId, DeleteTsKvQuery query);
  44 +
  45 + ListenableFuture<Void> removeLatest(EntityId entityId, DeleteTsKvQuery query);
  46 +
  47 + ListenableFuture<Void> removePartition(EntityId entityId, DeleteTsKvQuery query);
41 48 }
... ...
... ... @@ -17,8 +17,9 @@ package org.thingsboard.server.dao.timeseries;
17 17
18 18 import com.google.common.util.concurrent.ListenableFuture;
19 19 import org.thingsboard.server.common.data.id.EntityId;
  20 +import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
  21 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
20 22 import org.thingsboard.server.common.data.kv.TsKvEntry;
21   -import org.thingsboard.server.common.data.kv.TsKvQuery;
22 23
23 24 import java.util.Collection;
24 25 import java.util.List;
... ... @@ -28,7 +29,7 @@ import java.util.List;
28 29 */
29 30 public interface TimeseriesService {
30 31
31   - ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<TsKvQuery> queries);
  32 + ListenableFuture<List<TsKvEntry>> findAll(EntityId entityId, List<ReadTsKvQuery> queries);
32 33
33 34 ListenableFuture<List<TsKvEntry>> findLatest(EntityId entityId, Collection<String> keys);
34 35
... ... @@ -37,4 +38,6 @@ public interface TimeseriesService {
37 38 ListenableFuture<List<Void>> save(EntityId entityId, TsKvEntry tsKvEntry);
38 39
39 40 ListenableFuture<List<Void>> save(EntityId entityId, List<TsKvEntry> tsKvEntry, long ttl);
  41 +
  42 + ListenableFuture<List<Void>> remove(EntityId entityId, List<DeleteTsKvQuery> queries);
40 43 }
... ...
... ... @@ -16,57 +16,53 @@
16 16 package org.thingsboard.server.dao.timeseries;
17 17
18 18 import lombok.Getter;
  19 +import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
19 20 import org.thingsboard.server.common.data.kv.TsKvEntry;
20   -import org.thingsboard.server.common.data.kv.TsKvQuery;
21 21
22 22 import java.util.ArrayList;
23 23 import java.util.List;
24 24 import java.util.UUID;
25 25
  26 +import static org.thingsboard.server.dao.timeseries.CassandraBaseTimeseriesDao.DESC_ORDER;
  27 +
26 28 /**
27 29 * Created by ashvayka on 21.02.17.
28 30 */
29   -public class TsKvQueryCursor {
30   - @Getter
31   - private final String entityType;
32   - @Getter
33   - private final UUID entityId;
34   - @Getter
35   - private final String key;
36   - @Getter
37   - private final long startTs;
38   - @Getter
39   - private final long endTs;
40   - private final List<Long> partitions;
  31 +public class TsKvQueryCursor extends QueryCursor {
  32 +
41 33 @Getter
42 34 private final List<TsKvEntry> data;
  35 + @Getter
  36 + private String orderBy;
43 37
44 38 private int partitionIndex;
45 39 private int currentLimit;
46 40
47   - public TsKvQueryCursor(String entityType, UUID entityId, TsKvQuery baseQuery, List<Long> partitions) {
48   - this.entityType = entityType;
49   - this.entityId = entityId;
50   - this.key = baseQuery.getKey();
51   - this.startTs = baseQuery.getStartTs();
52   - this.endTs = baseQuery.getEndTs();
53   - this.partitions = partitions;
54   - this.partitionIndex = partitions.size() - 1;
  41 + public TsKvQueryCursor(String entityType, UUID entityId, ReadTsKvQuery baseQuery, List<Long> partitions) {
  42 + super(entityType, entityId, baseQuery, partitions);
  43 + this.orderBy = baseQuery.getOrderBy();
  44 + this.partitionIndex = isDesc() ? partitions.size() - 1 : 0;
55 45 this.data = new ArrayList<>();
56 46 this.currentLimit = baseQuery.getLimit();
57 47 }
58 48
  49 + @Override
59 50 public boolean hasNextPartition() {
60   - return partitionIndex >= 0;
  51 + return isDesc() ? partitionIndex >= 0 : partitionIndex <= partitions.size() - 1;
61 52 }
62 53
63 54 public boolean isFull() {
64 55 return currentLimit <= 0;
65 56 }
66 57
  58 + @Override
67 59 public long getNextPartition() {
68 60 long partition = partitions.get(partitionIndex);
69   - partitionIndex--;
  61 + if (isDesc()) {
  62 + partitionIndex--;
  63 + } else {
  64 + partitionIndex++;
  65 + }
70 66 return partition;
71 67 }
72 68
... ... @@ -78,4 +74,8 @@ public class TsKvQueryCursor {
78 74 currentLimit -= newData.size();
79 75 data.addAll(newData);
80 76 }
  77 +
  78 + private boolean isDesc() {
  79 + return orderBy.equals(DESC_ORDER);
  80 + }
81 81 }
... ...
... ... @@ -227,6 +227,13 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
227 227 Assert.assertTrue(relations.contains(relationA));
228 228 Assert.assertTrue(relations.contains(relationB));
229 229 Assert.assertTrue(relations.contains(relationC));
  230 +
  231 + //Test from cache
  232 + relations = relationService.findByQuery(query).get();
  233 + Assert.assertEquals(3, relations.size());
  234 + Assert.assertTrue(relations.contains(relationA));
  235 + Assert.assertTrue(relations.contains(relationB));
  236 + Assert.assertTrue(relations.contains(relationC));
230 237 }
231 238
232 239 @Test
... ... @@ -253,6 +260,12 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest {
253 260 Assert.assertEquals(2, relations.size());
254 261 Assert.assertTrue(relations.contains(relationAB));
255 262 Assert.assertTrue(relations.contains(relationBC));
  263 +
  264 + //Test from cache
  265 + relations = relationService.findByQuery(query).get();
  266 + Assert.assertEquals(2, relations.size());
  267 + Assert.assertTrue(relations.contains(relationAB));
  268 + Assert.assertTrue(relations.contains(relationBC));
256 269 }
257 270
258 271
... ...
... ... @@ -21,7 +21,8 @@ import org.junit.Assert;
21 21 import org.junit.Test;
22 22 import org.thingsboard.server.common.data.id.DeviceId;
23 23 import org.thingsboard.server.common.data.kv.Aggregation;
24   -import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  24 +import org.thingsboard.server.common.data.kv.BaseDeleteTsKvQuery;
  25 +import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
25 26 import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
26 27 import org.thingsboard.server.common.data.kv.BooleanDataEntry;
27 28 import org.thingsboard.server.common.data.kv.DoubleDataEntry;
... ... @@ -53,6 +54,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
53 54 private static final String BOOLEAN_KEY = "booleanKey";
54 55
55 56 private static final long TS = 42L;
  57 + private static final String DESC_ORDER = "DESC";
56 58
57 59 KvEntry stringKvEntry = new StringDataEntry(STRING_KEY, "value");
58 60 KvEntry longKvEntry = new LongDataEntry(LONG_KEY, Long.MAX_VALUE);
... ... @@ -101,6 +103,26 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
101 103 }
102 104
103 105 @Test
  106 + public void testDeleteDeviceTsData() throws Exception {
  107 + DeviceId deviceId = new DeviceId(UUIDs.timeBased());
  108 +
  109 + saveEntries(deviceId, 10000);
  110 + saveEntries(deviceId, 20000);
  111 + saveEntries(deviceId, 30000);
  112 + saveEntries(deviceId, 40000);
  113 +
  114 + tsService.remove(deviceId, Collections.singletonList(
  115 + new BaseDeleteTsKvQuery(STRING_KEY, 15000, 45000))).get();
  116 +
  117 + List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(
  118 + new BaseReadTsKvQuery(STRING_KEY, 5000, 45000, 10000, 10, Aggregation.NONE))).get();
  119 + Assert.assertEquals(1, list.size());
  120 +
  121 + List<TsKvEntry> latest = tsService.findLatest(deviceId, Collections.singletonList(STRING_KEY)).get();
  122 + Assert.assertEquals(null, latest.get(0).getValueAsString());
  123 + }
  124 +
  125 + @Test
104 126 public void testFindDeviceTsData() throws Exception {
105 127 DeviceId deviceId = new DeviceId(UUIDs.timeBased());
106 128 List<TsKvEntry> entries = new ArrayList<>();
... ... @@ -114,7 +136,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
114 136 entries.add(save(deviceId, 45000, 500));
115 137 entries.add(save(deviceId, 55000, 600));
116 138
117   - List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  139 + List<TsKvEntry> list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
118 140 60000, 20000, 3, Aggregation.NONE))).get();
119 141 assertEquals(3, list.size());
120 142 assertEquals(55000, list.get(0).getTs());
... ... @@ -126,7 +148,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
126 148 assertEquals(35000, list.get(2).getTs());
127 149 assertEquals(java.util.Optional.of(400L), list.get(2).getLongValue());
128 150
129   - list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  151 + list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
130 152 60000, 20000, 3, Aggregation.AVG))).get();
131 153 assertEquals(3, list.size());
132 154 assertEquals(10000, list.get(0).getTs());
... ... @@ -138,7 +160,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
138 160 assertEquals(50000, list.get(2).getTs());
139 161 assertEquals(java.util.Optional.of(550L), list.get(2).getLongValue());
140 162
141   - list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  163 + list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
142 164 60000, 20000, 3, Aggregation.SUM))).get();
143 165
144 166 assertEquals(3, list.size());
... ... @@ -151,7 +173,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
151 173 assertEquals(50000, list.get(2).getTs());
152 174 assertEquals(java.util.Optional.of(1100L), list.get(2).getLongValue());
153 175
154   - list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  176 + list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
155 177 60000, 20000, 3, Aggregation.MIN))).get();
156 178
157 179 assertEquals(3, list.size());
... ... @@ -164,7 +186,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
164 186 assertEquals(50000, list.get(2).getTs());
165 187 assertEquals(java.util.Optional.of(500L), list.get(2).getLongValue());
166 188
167   - list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  189 + list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
168 190 60000, 20000, 3, Aggregation.MAX))).get();
169 191
170 192 assertEquals(3, list.size());
... ... @@ -177,7 +199,7 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest {
177 199 assertEquals(50000, list.get(2).getTs());
178 200 assertEquals(java.util.Optional.of(600L), list.get(2).getLongValue());
179 201
180   - list = tsService.findAll(deviceId, Collections.singletonList(new BaseTsKvQuery(LONG_KEY, 0,
  202 + list = tsService.findAll(deviceId, Collections.singletonList(new BaseReadTsKvQuery(LONG_KEY, 0,
181 203 60000, 20000, 3, Aggregation.COUNT))).get();
182 204
183 205 assertEquals(3, list.size());
... ...
... ... @@ -40,6 +40,8 @@ public final class MqttClientConfig {
40 40 private Class<? extends Channel> channelClass = NioSocketChannel.class;
41 41
42 42 private boolean reconnect = true;
  43 + private long reconnectDelay = 1L;
  44 + private int maxBytesInMessage = 8092;
43 45
44 46 public MqttClientConfig() {
45 47 this(null);
... ... @@ -146,4 +148,38 @@ public final class MqttClientConfig {
146 148 public void setReconnect(boolean reconnect) {
147 149 this.reconnect = reconnect;
148 150 }
  151 +
  152 + public long getReconnectDelay() {
  153 + return reconnectDelay;
  154 + }
  155 +
  156 + /**
  157 + * Sets the reconnect delay in seconds. Defaults to 1 second.
  158 + * @param reconnectDelay
  159 + * @throws IllegalArgumentException if reconnectDelay is smaller than 1.
  160 + */
  161 + public void setReconnectDelay(long reconnectDelay) {
  162 + if (reconnectDelay <= 0) {
  163 + throw new IllegalArgumentException("reconnectDelay must be > 0");
  164 + }
  165 + this.reconnectDelay = reconnectDelay;
  166 + }
  167 +
  168 + public int getMaxBytesInMessage() {
  169 + return maxBytesInMessage;
  170 + }
  171 +
  172 + /**
  173 + * Sets the maximum number of bytes in the message for the {@link io.netty.handler.codec.mqtt.MqttDecoder}.
  174 + * Default value is 8092 as specified by Netty. The absolute maximum size is 256MB as set by the MQTT spec.
  175 + *
  176 + * @param maxBytesInMessage
  177 + * @throws IllegalArgumentException if maxBytesInMessage is smaller than 1 or greater than 256_000_000.
  178 + */
  179 + public void setMaxBytesInMessage(int maxBytesInMessage) {
  180 + if (maxBytesInMessage <= 0 || maxBytesInMessage > 256_000_000) {
  181 + throw new IllegalArgumentException("maxBytesInMessage must be > 0 or < 256_000_000");
  182 + }
  183 + this.maxBytesInMessage = maxBytesInMessage;
  184 + }
149 185 }
... ...
... ... @@ -155,7 +155,7 @@ final class MqttClientImpl implements MqttClient {
155 155 if (reconnect) {
156 156 this.reconnect = true;
157 157 }
158   - eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), 1L, TimeUnit.SECONDS);
  158 + eventLoop.schedule((Runnable) () -> connect(host, port, reconnect), clientConfig.getReconnectDelay(), TimeUnit.SECONDS);
159 159 }
160 160 }
161 161
... ... @@ -512,7 +512,7 @@ final class MqttClientImpl implements MqttClient {
512 512 ch.pipeline().addLast(sslContext.newHandler(ch.alloc(), host, port));
513 513 }
514 514
515   - ch.pipeline().addLast("mqttDecoder", new MqttDecoder());
  515 + ch.pipeline().addLast("mqttDecoder", new MqttDecoder(clientConfig.getMaxBytesInMessage()));
516 516 ch.pipeline().addLast("mqttEncoder", MqttEncoder.INSTANCE);
517 517 ch.pipeline().addLast("idleStateHandler", new IdleStateHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds(), MqttClientImpl.this.clientConfig.getTimeoutSeconds(), 0));
518 518 ch.pipeline().addLast("mqttPingHandler", new MqttPingHandler(MqttClientImpl.this.clientConfig.getTimeoutSeconds()));
... ...
... ... @@ -16,11 +16,14 @@
16 16 package org.thingsboard.rule.engine.api;
17 17
18 18 import com.google.common.util.concurrent.FutureCallback;
  19 +import org.thingsboard.server.common.data.id.DeviceId;
19 20 import org.thingsboard.server.common.data.id.EntityId;
  21 +import org.thingsboard.server.common.data.id.TenantId;
20 22 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
21 23 import org.thingsboard.server.common.data.kv.TsKvEntry;
22 24
23 25 import java.util.List;
  26 +import java.util.Set;
24 27
25 28 /**
26 29 * Created by ashvayka on 02.04.18.
... ... @@ -41,4 +44,6 @@ public interface RuleEngineTelemetryService {
41 44
42 45 void saveAttrAndNotify(EntityId entityId, String scope, String key, boolean value, FutureCallback<Void> callback);
43 46
  47 + void onSharedAttributesUpdate(TenantId tenantId, DeviceId deviceId, Set<AttributeKvEntry> attributes);
  48 +
44 49 }
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.metadata;
  17 +
  18 +import com.fasterxml.jackson.core.JsonGenerator;
  19 +import com.fasterxml.jackson.core.JsonParser;
  20 +import com.fasterxml.jackson.databind.ObjectMapper;
  21 +import com.fasterxml.jackson.databind.node.ArrayNode;
  22 +import com.fasterxml.jackson.databind.node.ObjectNode;
  23 +import com.google.common.util.concurrent.ListenableFuture;
  24 +import lombok.extern.slf4j.Slf4j;
  25 +import org.thingsboard.rule.engine.api.*;
  26 +import org.thingsboard.rule.engine.api.util.DonAsynchron;
  27 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  28 +import org.thingsboard.server.common.data.kv.BaseTsKvQuery;
  29 +import org.thingsboard.server.common.data.kv.TsKvEntry;
  30 +import org.thingsboard.server.common.data.kv.TsKvQuery;
  31 +import org.thingsboard.server.common.data.plugin.ComponentType;
  32 +import org.thingsboard.server.common.msg.TbMsg;
  33 +
  34 +import java.util.List;
  35 +import java.util.concurrent.ExecutionException;
  36 +import java.util.concurrent.TimeUnit;
  37 +import java.util.stream.Collectors;
  38 +
  39 +import static org.thingsboard.rule.engine.api.TbRelationTypes.SUCCESS;
  40 +import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.FETCH_MODE_ALL;
  41 +import static org.thingsboard.rule.engine.metadata.TbGetTelemetryNodeConfiguration.MAX_FETCH_SIZE;
  42 +import static org.thingsboard.server.common.data.kv.Aggregation.NONE;
  43 +
  44 +/**
  45 + * Created by mshvayka on 04.09.18.
  46 + */
  47 +@Slf4j
  48 +@RuleNode(type = ComponentType.ENRICHMENT,
  49 + name = "originator telemetry",
  50 + configClazz = TbGetTelemetryNodeConfiguration.class,
  51 + nodeDescription = "Add Message Originator Telemetry for selected time range into Message Metadata\n",
  52 + nodeDetails = "The node allows you to select fetch mode <b>FIRST/LAST/ALL</b> to fetch telemetry of certain time range that are added into Message metadata without any prefix. " +
  53 + "If selected fetch mode <b>ALL</b> Telemetry will be added like array into Message Metadata where <b>key</b> is Timestamp and <b>value</b> is value of Telemetry. " +
  54 + "If selected fetch mode <b>FIRST</b> or <b>LAST</b> Telemetry will be added like string without Timestamp",
  55 + uiResources = {"static/rulenode/rulenode-core-config.js"},
  56 + configDirective = "tbEnrichmentNodeGetTelemetryFromDatabase")
  57 +public class TbGetTelemetryNode implements TbNode {
  58 +
  59 + private TbGetTelemetryNodeConfiguration config;
  60 + private List<String> tsKeyNames;
  61 + private long startTsOffset;
  62 + private long endTsOffset;
  63 + private int limit;
  64 + private ObjectMapper mapper;
  65 +
  66 + @Override
  67 + public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException {
  68 + this.config = TbNodeUtils.convert(configuration, TbGetTelemetryNodeConfiguration.class);
  69 + tsKeyNames = config.getLatestTsKeyNames();
  70 + startTsOffset = TimeUnit.valueOf(config.getStartIntervalTimeUnit()).toMillis(config.getStartInterval());
  71 + endTsOffset = TimeUnit.valueOf(config.getEndIntervalTimeUnit()).toMillis(config.getEndInterval());
  72 + limit = config.getFetchMode().equals(FETCH_MODE_ALL) ? MAX_FETCH_SIZE : 1;
  73 + mapper = new ObjectMapper();
  74 + mapper.configure(JsonGenerator.Feature.QUOTE_FIELD_NAMES, false);
  75 + mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true);
  76 + }
  77 +
  78 + @Override
  79 + public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException {
  80 + if (tsKeyNames.isEmpty()) {
  81 + ctx.tellFailure(msg, new IllegalStateException("Telemetry is not selected!"));
  82 + } else {
  83 + try {
  84 + List<TsKvQuery> queries = buildQueries();
  85 + ListenableFuture<List<TsKvEntry>> list = ctx.getTimeseriesService().findAll(msg.getOriginator(), queries);
  86 + DonAsynchron.withCallback(list, data -> {
  87 + process(data, msg);
  88 + TbMsg newMsg = ctx.newMsg(msg.getType(), msg.getOriginator(), msg.getMetaData(), msg.getData());
  89 + ctx.tellNext(newMsg, SUCCESS);
  90 + }, error -> ctx.tellFailure(msg, error), ctx.getDbCallbackExecutor());
  91 + } catch (Exception e) {
  92 + ctx.tellFailure(msg, e);
  93 + }
  94 + }
  95 + }
  96 +
  97 + //TODO: handle direction;
  98 + private List<TsKvQuery> buildQueries() {
  99 + long ts = System.currentTimeMillis();
  100 + long startTs = ts - startTsOffset;
  101 + long endTs = ts - endTsOffset;
  102 +
  103 + return tsKeyNames.stream()
  104 + .map(key -> new BaseTsKvQuery(key, startTs, endTs, 1, limit, NONE))
  105 + .collect(Collectors.toList());
  106 + }
  107 +
  108 + private void process(List<TsKvEntry> entries, TbMsg msg) {
  109 + ObjectNode resultNode = mapper.createObjectNode();
  110 + if (limit == MAX_FETCH_SIZE) {
  111 + entries.forEach(entry -> processArray(resultNode, entry));
  112 + } else {
  113 + entries.forEach(entry -> processSingle(resultNode, entry));
  114 + }
  115 +
  116 + for (String key : tsKeyNames) {
  117 + if(resultNode.has(key)){
  118 + msg.getMetaData().putValue(key, resultNode.get(key).toString());
  119 + }
  120 + }
  121 + }
  122 +
  123 + private void processSingle(ObjectNode node, TsKvEntry entry) {
  124 + node.put(entry.getKey(), entry.getValueAsString());
  125 + }
  126 +
  127 + private void processArray(ObjectNode node, TsKvEntry entry) {
  128 + if(node.has(entry.getKey())){
  129 + ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey());
  130 + ObjectNode obj = buildNode(entry);
  131 + arrayNode.add(obj);
  132 + }else {
  133 + ArrayNode arrayNode = mapper.createArrayNode();
  134 + ObjectNode obj = buildNode(entry);
  135 + arrayNode.add(obj);
  136 + node.set(entry.getKey(), arrayNode);
  137 + }
  138 + }
  139 +
  140 + private ObjectNode buildNode(TsKvEntry entry) {
  141 + ObjectNode obj = mapper.createObjectNode()
  142 + .put("ts", entry.getTs());
  143 + switch (entry.getDataType()) {
  144 + case STRING:
  145 + obj.put("value", entry.getValueAsString());
  146 + break;
  147 + case LONG:
  148 + obj.put("value", entry.getLongValue().get());
  149 + break;
  150 + case BOOLEAN:
  151 + obj.put("value", entry.getBooleanValue().get());
  152 + break;
  153 + case DOUBLE:
  154 + obj.put("value", entry.getDoubleValue().get());
  155 + break;
  156 + }
  157 + return obj;
  158 + }
  159 +
  160 + @Override
  161 + public void destroy() {
  162 +
  163 + }
  164 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2018 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.rule.engine.metadata;
  17 +
  18 +import lombok.Data;
  19 +import org.thingsboard.rule.engine.api.NodeConfiguration;
  20 +
  21 +import java.util.Collections;
  22 +import java.util.List;
  23 +import java.util.concurrent.TimeUnit;
  24 +
  25 +/**
  26 + * Created by mshvayka on 04.09.18.
  27 + */
  28 +@Data
  29 +public class TbGetTelemetryNodeConfiguration implements NodeConfiguration<TbGetTelemetryNodeConfiguration> {
  30 +
  31 + public static final String FETCH_MODE_FIRST = "FIRST";
  32 + public static final String FETCH_MODE_LAST = "LAST";
  33 + public static final String FETCH_MODE_ALL = "ALL";
  34 + public static final int MAX_FETCH_SIZE = 1000;
  35 +
  36 + private int startInterval;
  37 + private int endInterval;
  38 + private String startIntervalTimeUnit;
  39 + private String endIntervalTimeUnit;
  40 + private String fetchMode; //FIRST, LAST, LATEST
  41 +
  42 + private List<String> latestTsKeyNames;
  43 +
  44 +
  45 +
  46 + @Override
  47 + public TbGetTelemetryNodeConfiguration defaultConfiguration() {
  48 + TbGetTelemetryNodeConfiguration configuration = new TbGetTelemetryNodeConfiguration();
  49 + configuration.setLatestTsKeyNames(Collections.emptyList());
  50 + configuration.setFetchMode("FIRST");
  51 + configuration.setStartIntervalTimeUnit(TimeUnit.MINUTES.name());
  52 + configuration.setStartInterval(2);
  53 + configuration.setEndIntervalTimeUnit(TimeUnit.MINUTES.name());
  54 + configuration.setEndInterval(1);
  55 + return configuration;
  56 + }
  57 +}
... ...
... ... @@ -17,12 +17,15 @@ package org.thingsboard.rule.engine.telemetry;
17 17
18 18 import com.google.gson.JsonParser;
19 19 import lombok.extern.slf4j.Slf4j;
20   -import org.thingsboard.rule.engine.api.util.TbNodeUtils;
21 20 import org.thingsboard.rule.engine.api.RuleNode;
22 21 import org.thingsboard.rule.engine.api.TbContext;
23 22 import org.thingsboard.rule.engine.api.TbNode;
24 23 import org.thingsboard.rule.engine.api.TbNodeConfiguration;
25 24 import org.thingsboard.rule.engine.api.TbNodeException;
  25 +import org.thingsboard.rule.engine.api.util.TbNodeUtils;
  26 +import org.thingsboard.server.common.data.DataConstants;
  27 +import org.thingsboard.server.common.data.EntityType;
  28 +import org.thingsboard.server.common.data.id.DeviceId;
26 29 import org.thingsboard.server.common.data.kv.AttributeKvEntry;
27 30 import org.thingsboard.server.common.data.plugin.ComponentType;
28 31 import org.thingsboard.server.common.msg.TbMsg;
... ... @@ -62,6 +65,9 @@ public class TbMsgAttributesNode implements TbNode {
62 65 String src = msg.getData();
63 66 Set<AttributeKvEntry> attributes = JsonConverter.convertToAttributes(new JsonParser().parse(src)).getAttributes();
64 67 ctx.getTelemetryService().saveAndNotify(msg.getOriginator(), config.getScope(), new ArrayList<>(attributes), new TelemetryNodeCallback(ctx, msg));
  68 + if (msg.getOriginator().getEntityType() == EntityType.DEVICE && DataConstants.SHARED_SCOPE.equals(config.getScope())) {
  69 + ctx.getTelemetryService().onSharedAttributesUpdate(ctx.getTenantId(), new DeviceId(msg.getOriginator().getId()), attributes);
  70 + }
65 71 }
66 72
67 73 @Override
... ...
1   -!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> ';
2   -},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:&apos;...&apos;}}" }\'>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>&nbsp</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",{
3   -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}]));
  1 +!function(e){function t(a){if(n[a])return n[a].exports;var r=n[a]={exports:{},id:a,loaded:!1};return e[a].call(r.exports,r,r.exports,t),r.loaded=!0,r.exports}var n={};return t.m=e,t.c=n,t.p="/static/",t(0)}(function(e){for(var t in e)if(Object.prototype.hasOwnProperty.call(e,t))switch(typeof e[t]){case"function":break;case"object":e[t]=function(t){var n=t.slice(1),a=e[t[0]];return function(e,t,r){a.apply(this,[e,t,r].concat(n))}}(e[t]);break;default:e[t]=e[e[t]]}return e}([function(e,t,n){e.exports=n(79)},function(e,t){},1,1,1,1,function(e,t){e.exports=' <section ng-form name=attributesConfigForm layout=column> <md-input-container class=md-block> <label translate>attribute.attributes-scope</label> <md-select ng-model=configuration.scope ng-disabled=$root.loading> <md-option ng-repeat="scope in types.attributesScope" ng-value=scope.value> {{scope.name | translate}} </md-option> </md-select> </md-input-container> </section> '},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <md-input-container class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=" <section class=tb-alarm-config ng-form name=alarmConfigForm layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.alarm-details-builder</label> <tb-js-func ng-model=configuration.alarmDetailsBuildJs function-name=Details function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testDetailsBuildJs($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-details-function' | translate }} </md-button> </div> <section layout=column layout-gt-sm=row> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-type</label> <input ng-required=true name=alarmType ng-model=configuration.alarmType> <div ng-messages=alarmConfigForm.alarmType.$error> <div ng-message=required translate>tb.rulenode.alarm-type-required</div> </div> </md-input-container> <md-input-container flex class=md-block> <label translate>tb.rulenode.alarm-severity</label> <md-select required name=severity ng-model=configuration.severity> <md-option ng-repeat=\"(severityKey, severity) in types.alarmSeverity\" ng-value=severityKey> {{ severity.name | translate}} </md-option> </md-select> <div ng-messages=alarmConfigForm.severity.$error> <div ng-message=required translate>tb.rulenode.alarm-severity-required</div> </div> </md-input-container> </section> <md-checkbox aria-label=\"{{ 'tb.rulenode.propagate' | translate }}\" ng-model=configuration.propagate>{{ 'tb.rulenode.propagate' | translate }} </md-checkbox> </section> "},function(e,t){e.exports=" <section class=tb-generator-config ng-form name=generatorConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.message-count</label> <input ng-required=true type=number step=1 name=messageCount ng-model=configuration.msgCount min=0> <div ng-messages=generatorConfigForm.messageCount.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.message-count-required</div> <div ng-message=min translate>tb.rulenode.min-message-count-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=1> <div ng-messages=generatorConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-seconds-message</div> </div> </md-input-container> <div layout=column> <label class=tb-small>{{ 'tb.rulenode.originator' | translate }}</label> <tb-entity-select the-form=generatorConfigForm tb-required=false ng-model=originator> </tb-entity-select> </div> <label translate class=\"tb-title no-padding\">tb.rulenode.generate</label> <tb-js-func ng-model=configuration.jsScript function-name=Generate function-args=\"{{ ['prevMsg', 'prevMetadata', 'prevMsgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-generator-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section ng-form name=kafkaConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=kafkaConfigForm.topicPattern.$error> <div ng-message=required translate>tb.rulenode.topic-pattern-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bootstrap-servers</label> <input ng-required=true name=bootstrapServers ng-model=configuration.bootstrapServers> <div ng-messages=kafkaConfigForm.bootstrapServers.$error> <div ng-message=required translate>tb.rulenode.bootstrap-servers-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.retries</label> <input type=number step=1 name=retries ng-model=configuration.retries min=0> <div ng-messages=kafkaConfigForm.retries.$error> <div ng-message=min translate>tb.rulenode.min-retries-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.batch-size-bytes</label> <input type=number step=1 name=batchSize ng-model=configuration.batchSize min=0> <div ng-messages=kafkaConfigForm.batchSize.$error> <div ng-message=min translate>tb.rulenode.min-batch-size-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.linger-ms</label> <input type=number step=1 name=linger ng-model=configuration.linger min=0> <div ng-messages=kafkaConfigForm.linger.$error> <div ng-message=min translate>tb.rulenode.min-linger-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.buffer-memory-bytes</label> <input type=number step=1 name=bufferMemory ng-model=configuration.bufferMemory min=0> <div ng-messages=kafkaConfigForm.bufferMemory.$error> <div ng-message=min translate>tb.rulenode.min-buffer-memory-bytes-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.acks</label> <md-select ng-model=configuration.acks ng-disabled=$root.loading> <md-option ng-repeat="ackValue in ackValues" ng-value=ackValue> {{ ackValue }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.key-serializer</label> <input ng-required=true name=keySerializer ng-model=configuration.keySerializer> <div ng-messages=kafkaConfigForm.keySerializer.$error> <div ng-message=required translate>tb.rulenode.key-serializer-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.value-serializer</label> <input ng-required=true name=valueSerializer ng-model=configuration.valueSerializer> <div ng-messages=kafkaConfigForm.valueSerializer.$error> <div ng-message=required translate>tb.rulenode.value-serializer-required</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.other-properties</label> <tb-kv-map-config ng-model=configuration.otherProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.to-string</label> <tb-js-func ng-model=configuration.jsScript function-name=ToString function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-to-string-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=' <section class=tb-mqtt-config ng-form name=mqttConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-pattern</label> <input ng-required=true name=topicPattern ng-model=configuration.topicPattern> <div ng-messages=mqttConfigForm.topicPattern.$error> <div translate ng-message=required>tb.rulenode.topic-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.mqtt-topic-pattern-hint</div> </md-input-container> <div flex layout=column layout-gt-sm=row> <md-input-container flex=60 class=md-block> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=mqttConfigForm.host.$error> <div translate ng-message=required>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.port> <div ng-messages=mqttConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.port-required</div> <div translate ng-message=min>tb.rulenode.port-range</div> <div translate ng-message=max>tb.rulenode.port-range</div> </div> </md-input-container> <md-input-container flex=40 class=md-block> <label translate>tb.rulenode.connect-timeout</label> <input type=number step=1 min=1 max=200 ng-required=true name=connectTimeoutSec ng-model=configuration.connectTimeoutSec> <div ng-messages=mqttConfigForm.connectTimeoutSec.$error> <div translate ng-message=required>tb.rulenode.connect-timeout-required</div> <div translate ng-message=min>tb.rulenode.connect-timeout-range</div> <div translate ng-message=max>tb.rulenode.connect-timeout-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.client-id</label> <input name=clientId ng-model=configuration.clientId> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.clean-session\' | translate }}" ng-model=configuration.cleanSession> {{ \'tb.rulenode.clean-session\' | translate }} </md-checkbox> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-ssl\' | translate }}" ng-model=configuration.ssl> {{ \'tb.rulenode.enable-ssl\' | translate }} </md-checkbox> <md-expansion-panel-group class=tb-credentials-panel-group ng-class="{\'disabled\': $root.loading || readonly}" md-component-id=credentialsPanelGroup> <md-expansion-panel md-component-id=credentialsPanel> <md-expansion-panel-collapsed> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-collapsed> <md-expansion-panel-expanded> <md-expansion-panel-header ng-click="$mdExpansionPanel(\'credentialsPanel\').collapse()"> <div class=tb-panel-title>{{ \'tb.rulenode.credentials\' | translate }}</div> <div class=tb-panel-prompt>{{ ruleNodeTypes.mqttCredentialTypes[configuration.credentials.type].name | translate }}</div> <span flex></span> <md-expansion-panel-icon></md-expansion-panel-icon> </md-expansion-panel-header> <md-expansion-panel-content> <div layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.credentials-type</label> <md-select ng-required=true name=credentialsType ng-model=configuration.credentials.type ng-disabled="$root.loading || readonly" ng-change=credentialsTypeChanged()> <md-option ng-repeat="(credentialsType, credentialsValue) in ruleNodeTypes.mqttCredentialTypes" ng-value=credentialsValue.value> {{credentialsValue.name | translate}} </md-option> </md-select> <div ng-messages=mqttConfigForm.credentialsType.$error> <div translate ng-message=required>tb.rulenode.credentials-type-required</div> </div> </md-input-container> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes.basic.value"> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input ng-required=true name=mqttUsername ng-model=configuration.credentials.username> <div ng-messages=mqttConfigForm.mqttUsername.$error> <div translate ng-message=required>tb.rulenode.username-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input type=password ng-required=true name=mqttPassword ng-model=configuration.credentials.password> <div ng-messages=mqttConfigForm.mqttPassword.$error> <div translate ng-message=required>tb.rulenode.password-required</div> </div> </md-input-container> </section> <section flex layout=column ng-if="configuration.credentials.type == ruleNodeTypes.mqttCredentialTypes[\'cert.PEM\'].value" class=dropdown-section> <div class=tb-container ng-class="configuration.credentials.caCertFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.ca-cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'caCert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'caCert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=caCertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=caCertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.caCertFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.caCertFileName>{{configuration.credentials.caCertFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.certFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.cert</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'Cert\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'Cert\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=CertSelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=CertSelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.certFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.certFileName>{{configuration.credentials.certFileName}}</div> </div> <div class=tb-container ng-class="configuration.credentials.privateKeyFileName ? \'ng-valid\' : \'ng-invalid\'"> <label class=tb-label translate>tb.rulenode.private-key</label> <div flow-init={singleFile:true} flow-file-added="certFileAdded($file, \'privateKey\')" class=tb-file-select-container> <div class=tb-file-clear-container> <md-button ng-click="clearCertFile(\'privateKey\')" class="tb-file-clear-btn md-icon-button md-primary" aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'action.remove\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.remove\' | translate }}" class=material-icons>close</md-icon> </md-button> </div> <div class="alert tb-flow-drop" flow-drop> <label for=privateKeySelect translate>tb.rulenode.drop-file</label> <input class=file-input flow-btn id=privateKeySelect> </div> </div> </div> <div class=dropdown-messages> <div ng-if=!configuration.credentials.privateKeyFileName class=tb-error-message translate>tb.rulenode.no-file</div> <div ng-if=configuration.credentials.privateKeyFileName>{{configuration.credentials.privateKeyFileName}}</div> </div> <md-input-container class=md-block> <label translate>tb.rulenode.private-key-password</label> <input type=password name=privateKeyPassword ng-model=configuration.credentials.password> </md-input-container> </section> </div> </md-expansion-panel-content> </md-expansion-panel-expanded> </md-expansion-panel> </md-expansion-panel-group> </section>'},function(e,t){e.exports=" <section ng-form name=msgDelayConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.period-seconds</label> <input ng-required=true type=number step=1 name=periodInSeconds ng-model=configuration.periodInSeconds min=0> <div ng-messages=msgDelayConfigForm.periodInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.period-seconds-required</div> <div ng-message=min translate>tb.rulenode.min-period-0-seconds-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-pending-messages</label> <input ng-required=true type=number step=1 name=maxPendingMsgs ng-model=configuration.maxPendingMsgs min=1 max=100000> <div ng-messages=msgDelayConfigForm.maxPendingMsgs.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.max-pending-messages-required</div> <div ng-message=min translate>tb.rulenode.max-pending-messages-range</div> <div ng-message=max translate>tb.rulenode.max-pending-messages-range</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=rabbitMqConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.exchange-name-pattern</label> <input name=exchangeNamePattern ng-model=configuration.exchangeNamePattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.routing-key-pattern</label> <input name=routingKeyPattern ng-model=configuration.routingKeyPattern> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.message-properties</label> <md-select ng-model=configuration.messageProperties ng-disabled="$root.loading || readonly"> <md-option ng-repeat="property in messageProperties" ng-value=property> {{ property }} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.host</label> <input ng-required=true name=host ng-model=configuration.host> <div ng-messages=rabbitMqConfigForm.host.$error> <div ng-message=required translate>tb.rulenode.host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.port</label> <input ng-required=true type=number step=1 name=port ng-model=configuration.port min=0 max=65535> <div ng-messages=rabbitMqConfigForm.port.$error> <div ng-message=required translate>tb.rulenode.port-required</div> <div ng-message=min translate>tb.rulenode.port-range</div> <div ng-message=max translate>tb.rulenode.port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.virtual-host</label> <input name=virtualHost ng-model=configuration.virtualHost> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=virtualHost ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=virtualHost type=password ng-model=configuration.password> </md-input-container> <md-input-container class=md-block> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.automatic-recovery\' | translate }}" ng-model=ruleNode.automaticRecoveryEnabled>{{ \'tb.rulenode.automatic-recovery\' | translate }} </md-checkbox> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.connection-timeout-ms</label> <input type=number step=1 name=connectionTimeout ng-model=configuration.connectionTimeout min=0> <div ng-messages=rabbitMqConfigForm.connectionTimeout.$error> <div ng-message=min translate>tb.rulenode.min-connection-timeout-ms-message</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.handshake-timeout-ms</label> <input type=number step=1 name=handshakeTimeout ng-model=configuration.handshakeTimeout min=0> <div ng-messages=rabbitMqConfigForm.handshakeTimeout.$error> <div ng-message=min translate>tb.rulenode.min-handshake-timeout-ms-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.client-properties</label> <tb-kv-map-config ng-model=configuration.clientProperties ng-required=false key-text="\'tb.rulenode.key\'" key-required-text="\'tb.rulenode.key-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=' <section ng-form name=restApiCallConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.endpoint-url-pattern</label> <input ng-required=true name=endpointUrlPattern ng-model=configuration.restEndpointUrlPattern> <div ng-messages=restApiCallConfigForm.endpointUrlPattern.$error> <div ng-message=required translate>tb.rulenode.endpoint-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.endpoint-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.request-method</label> <md-select ng-model=configuration.requestMethod ng-disabled=$root.loading> <md-option ng-repeat="type in ruleNodeTypes.httpRequestType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <label translate class=tb-title>tb.rulenode.headers</label> <div class=tb-hint translate>tb.rulenode.headers-hint</div> <tb-kv-map-config ng-model=configuration.headers ng-required=false key-text="\'tb.rulenode.header\'" key-required-text="\'tb.rulenode.header-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section ng-form name=rpcReplyConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.request-id-metadata-attribute</label> <input name=requestIdMetaDataAttribute ng-model=configuration.requestIdMetaDataAttribute> </md-input-container> </section> "},function(e,t){e.exports=" <section ng-form name=rpcRequestConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-sec</label> <input ng-required=true type=number step=1 name=timeoutInSeconds ng-model=configuration.timeoutInSeconds min=0> <div ng-messages=rpcRequestConfigForm.timeoutInSeconds.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.timeout-required</div> <div ng-message=min translate>tb.rulenode.min-timeout-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sendEmailConfigForm layout=column> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.use-system-smtp-settings\' | translate }}" ng-model=configuration.useSystemSmtpSettings> {{ \'tb.rulenode.use-system-smtp-settings\' | translate }} </md-checkbox> <section layout=column ng-if=!configuration.useSystemSmtpSettings> <md-input-container class=md-block> <label translate>tb.rulenode.smtp-protocol</label> <md-select ng-disabled="$root.loading || readonly" ng-model=configuration.smtpProtocol> <md-option ng-repeat="smtpProtocol in smtpProtocols" value={{smtpProtocol}}> {{smtpProtocol.toUpperCase()}} </md-option> </md-select> </md-input-container> <div layout-gt-sm=row> <md-input-container class=md-block flex=100 flex-gt-sm=60> <label translate>tb.rulenode.smtp-host</label> <input ng-required=true name=smtpHost ng-model=configuration.smtpHost> <div ng-messages=sendEmailConfigForm.smtpHost.$error> <div translate ng-message=required>tb.rulenode.smtp-host-required</div> </div> </md-input-container> <md-input-container class=md-block flex=100 flex-gt-sm=40> <label translate>tb.rulenode.smtp-port</label> <input type=number step=1 min=1 max=65535 ng-required=true name=port ng-model=configuration.smtpPort> <div ng-messages=sendEmailConfigForm.port.$error> <div translate ng-message=required>tb.rulenode.smtp-port-required</div> <div translate ng-message=min>tb.rulenode.smtp-port-range</div> <div translate ng-message=max>tb.rulenode.smtp-port-range</div> </div> </md-input-container> </div> <md-input-container class=md-block> <label translate>tb.rulenode.timeout-msec</label> <input type=number step=1 min=0 ng-required=true name=timeout ng-model=configuration.timeout> <div ng-messages=sendEmailConfigForm.timeout.$error> <div translate ng-message=required>tb.rulenode.timeout-required</div> <div translate ng-message=min>tb.rulenode.min-timeout-msec-message</div> </div> </md-input-container> <md-checkbox ng-disabled="$root.loading || readonly" aria-label="{{ \'tb.rulenode.enable-tls\' | translate }}" ng-model=configuration.enableTls>{{ \'tb.rulenode.enable-tls\' | translate }}</md-checkbox> <md-input-container class=md-block> <label translate>tb.rulenode.username</label> <input name=username placeholder="{{ \'tb.rulenode.enter-username\' | translate }}" ng-model=configuration.username> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.password</label> <input name=password placeholder="{{ \'tb.rulenode.enter-password\' | translate }}" type=password ng-model=configuration.password> </md-input-container> </section> </section> '},function(e,t){e.exports=" <section ng-form name=snsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.topic-arn-pattern</label> <input ng-required=true name=topicArnPattern ng-model=configuration.topicArnPattern> <div ng-messages=snsConfigForm.topicArnPattern.$error> <div ng-message=required translate>tb.rulenode.topic-arn-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.topic-arn-pattern-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section ng-form name=sqsConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.queue-type</label> <md-select ng-model=configuration.queueType ng-disabled="$root.loading || readonly"> <md-option ng-repeat="type in ruleNodeTypes.sqsQueueType" ng-value=type.value> {{ type.name | translate }} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.queue-url-pattern</label> <input ng-required=true name=queueUrlPattern ng-model=configuration.queueUrlPattern> <div ng-messages=sqsConfigForm.queueUrlPattern.$error> <div ng-message=required translate>tb.rulenode.queue-url-pattern-required</div> </div> <div class=tb-hint translate>tb.rulenode.queue-url-pattern-hint</div> </md-input-container> <md-input-container class=md-block ng-if="configuration.queueType == ruleNodeTypes.sqsQueueType.STANDARD.value"> <label translate>tb.rulenode.delay-seconds</label> <input type=number step=1 name=delaySeconds ng-model=configuration.delaySeconds min=0 max=900> <div ng-messages=sqsConfigForm.delaySeconds.$error> <div ng-message=min translate>tb.rulenode.min-delay-seconds-message</div> <div ng-message=max translate>tb.rulenode.max-delay-seconds-message</div> </div> </md-input-container> <label translate class=tb-title>tb.rulenode.message-attributes</label> <div class=tb-hint translate>tb.rulenode.message-attributes-hint</div> <tb-kv-map-config ng-model=configuration.messageAttributes ng-required=false key-text="\'tb.rulenode.name\'" key-required-text="\'tb.rulenode.name-required\'" val-text="\'tb.rulenode.value\'" val-required-text="\'tb.rulenode.value-required\'"> </tb-kv-map-config> <md-input-container class=md-block> <label translate>tb.rulenode.aws-access-key-id</label> <input ng-required=true name=accessKeyId ng-model=configuration.accessKeyId> <div ng-messages=snsConfigForm.accessKeyId.$error> <div ng-message=required translate>tb.rulenode.aws-access-key-id-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-secret-access-key</label> <input ng-required=true name=secretAccessKey ng-model=configuration.secretAccessKey> <div ng-messages=snsConfigForm.secretAccessKey.$error> <div ng-message=required translate>tb.rulenode.aws-secret-access-key-required</div> </div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.aws-region</label> <input ng-required=true name=region ng-model=configuration.region> <div ng-messages=snsConfigForm.region.$error> <div ng-message=required translate>tb.rulenode.aws-region-required</div> </div> </md-input-container> </section> '},function(e,t){e.exports=" <section ng-form name=timeseriesConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.default-ttl</label> <input ng-required=true type=number step=1 name=defaultTTL ng-model=configuration.defaultTTL min=0> <div ng-messages=timeseriesConfigForm.defaultTTL.$error multiple=multiple md-auto-hide=false> <div ng-message=required translate>tb.rulenode.default-ttl-required</div> <div ng-message=min translate>tb.rulenode.min-default-ttl-message</div> </div> </md-input-container> </section> "},function(e,t){e.exports=' <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat="direction in types.entitySearchDirection" ng-value=direction> {{ (\'relation.search-direction.\' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder="{{ \'tb.rulenode.unlimited-level\' | translate }}" ng-model=query.maxLevel aria-label="{{ \'tb.rulenode.max-relation-level\' | translate }}"> </md-input-container> </div> <div class=md-caption style=color:rgba(0,0,0,.57) translate>relation.relation-type</div> <tb-relation-type-autocomplete flex hide-label ng-model=query.relationType tb-required=false> </tb-relation-type-autocomplete> <div class="md-caption tb-required" style=color:rgba(0,0,0,.57) translate>device.device-types</div> <tb-entity-subtype-list tb-required=true entity-type=types.entityType.device ng-model=query.deviceTypes> </tb-entity-subtype-list> </section> ';
  2 +},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.device-relations-query</label> <tb-device-relations-query-config style=padding-bottom:15px ng-model=configuration.deviceRelationsQuery> </tb-device-relations-query-config> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=' <section class=tb-telemetry-from-database-config ng-form name=getTelemetryConfigForm layout=column> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <md-input-container style=margin-bottom:38px;margin-top:58px> <label translate class="tb-title no-padding">tb.rulenode.fetch-mode</label> <md-select required ng-model=configuration.fetchMode> <md-option ng-repeat="type in ruleNodeTypes.fetchModeType" ng-value=type> {{ type }} </md-option> </md-select> </md-input-container> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value"> <label translate class="tb-title no-padding">tb.rulenode.start-interval</label> <input required type=number step=1 min=1 max=2147483647 name=startInterval ng-model=configuration.startInterval> <div ng-messages=getTelemetryConfigForm.startInterval.$error> <div translate ng-message=required>tb.rulenode.start-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit"> <label translate class="tb-title no-padding">tb.rulenode.start-interval-time-unit</label> <md-select required name=startIntervalTimeUnit aria-label="{{ \'tb.rulenode.start-interval-time-unit\' | translate }}" ng-model=configuration.startIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> <div layout=column layout-gt-sm=row> <md-input-container flex class="md-block tb-time-value"> <label translate class="tb-title no-padding">tb.rulenode.end-interval</label> <input required type=number step=1 min=1 max=2147483647 name=endInterval ng-model=configuration.endInterval> <div ng-messages=getTelemetryConfigForm.endInterval.$error> <div translate ng-message=required>tb.rulenode.end-interval-value-required</div> <div ng-message=min translate>tb.rulenode.time-value-range</div> <div ng-message=max translate>tb.rulenode.time-value-range</div> </div> </md-input-container> <md-input-container flex class="md-block tb-time-unit"> <label translate class="tb-title no-padding">tb.rulenode.end-interval-time-unit</label> <md-select required name=endIntervalTimeUnit aria-label="{{ \'tb.rulenode.end-interval-time-unit\' | translate }}" ng-model=configuration.endIntervalTimeUnit> <md-option ng-repeat="timeUnit in ruleNodeTypes.timeUnit" ng-value=timeUnit.value> {{timeUnit.name | translate}} </md-option> </md-select> </md-input-container> </div> </section>'},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding">tb.rulenode.client-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.clientAttributeNames placeholder="{{\'tb.rulenode.client-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.shared-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.sharedAttributeNames placeholder="{{\'tb.rulenode.shared-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.server-attributes</label> <md-chips style=padding-bottom:15px ng-required=false readonly=readonly ng-model=configuration.serverAttributeNames placeholder="{{\'tb.rulenode.server-attributes\' | translate}}" md-separator-keys=separatorKeys> </md-chips> <label translate class="tb-title no-padding">tb.rulenode.latest-timeseries</label> <md-chips ng-required=false readonly=readonly ng-model=configuration.latestTsKeyNames placeholder="{{\'tb.rulenode.latest-timeseries\' | translate}}" md-separator-keys=separatorKeys> </md-chips> </section> '},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title tb-required">tb.rulenode.fields-mapping</label> <tb-kv-map-config ng-model=configuration.fieldsMapping ng-required=true required-text="\'tb.rulenode.fields-mapping-required\'" key-text="\'tb.rulenode.source-field\'" key-required-text="\'tb.rulenode.source-field-required\'" val-text="\'tb.rulenode.target-attribute\'" val-required-text="\'tb.rulenode.target-attribute-required\'"> </tb-kv-map-config> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title tb-required\">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> <label translate class=\"tb-title tb-required\">tb.rulenode.attr-mapping</label> <md-checkbox aria-label=\"{{ 'tb.rulenode.latest-telemetry' | translate }}\" ng-model=configuration.telemetry>{{ 'tb.rulenode.latest-telemetry' | translate }} </md-checkbox> <tb-kv-map-config ng-model=configuration.attrMapping ng-required=true required-text=\"'tb.rulenode.attr-mapping-required'\" key-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry' : 'tb.rulenode.source-attribute'\" key-required-text=\"configuration.telemetry ? 'tb.rulenode.source-telemetry-required' : 'tb.rulenode.source-attribute-required'\" val-text=\"'tb.rulenode.target-attribute'\" val-required-text=\"'tb.rulenode.target-attribute-required'\"> </tb-kv-map-config> </section> "},23,function(e,t){e.exports=" <section ng-form name=checkRelationConfigForm> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=configuration.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <div layout=row class=tb-entity-select> <tb-entity-type-select style=min-width:100px the-form=checkRelationConfigForm tb-required=true ng-model=configuration.entityType> </tb-entity-type-select> <tb-entity-autocomplete flex ng-if=configuration.entityType the-form=checkRelationConfigForm tb-required=true entity-type=configuration.entityType ng-model=configuration.entityId> </tb-entity-autocomplete> </div> <tb-relation-type-autocomplete hide-label ng-model=configuration.relationType tb-required=true> </tb-relation-type-autocomplete> </section> "},function(e,t){e.exports=' <section layout=column> <label translate class="tb-title no-padding" ng-class="{\'tb-required\': required}">tb.rulenode.message-types-filter</label> <md-chips id=message_type_chips ng-required=required readonly=readonly ng-model=messageTypes md-autocomplete-snap md-transform-chip=transformMessageTypeChip($chip) md-require-match=false> <md-autocomplete id=message_type md-no-cache=true md-selected-item=selectedMessageType md-search-text=messageTypeSearchText md-items="item in messageTypesSearch(messageTypeSearchText)" md-item-text=item.name md-min-length=0 placeholder="{{\'tb.rulenode.message-type\' | translate }}" md-menu-class=tb-message-type-autocomplete> <span md-highlight-text=messageTypeSearchText md-highlight-flags=^i>{{item}}</span> <md-not-found> <div class=tb-not-found> <div class=tb-no-entries ng-if="!messageTypeSearchText || !messageTypeSearchText.length"> <span translate>tb.rulenode.no-message-types-found</span> </div> <div ng-if="messageTypeSearchText && messageTypeSearchText.length"> <span translate translate-values=\'{ messageType: "{{messageTypeSearchText | truncate:true:6:&apos;...&apos;}}" }\'>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>&nbsp</span> </div> <div class=body> <div class=row ng-form name=kvForm flex layout=row layout-align="start center" ng-repeat="keyVal in kvList track by $index"> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ keyText | translate }}" ng-required=true name=key ng-model=keyVal.key> <div ng-messages=kvForm.key.$error> <div translate ng-message=required>{{keyRequiredText}}</div> </div> </md-input-container> <md-input-container class="cell md-block" flex md-no-float> <input placeholder="{{ valText | translate }}" ng-required=true name=value ng-model=keyVal.value> <div ng-messages=kvForm.value.$error> <div translate ng-message=required>{{valRequiredText}}</div> </div> </md-input-container> <md-button ng-show=!disabled ng-disabled=loading class="md-icon-button md-primary" ng-click=removeKeyVal($index) aria-label="{{ \'action.remove\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.remove-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.delete\' | translate }}" class=material-icons> close </md-icon> </md-button> </div> </div> <div class=tb-error-messages ng-messages=ngModelCtrl.$error role=alert> <div translate ng-message=kvMap class=tb-error-message>{{requiredText}}</div> </div> <div> <md-button ng-show=!disabled ng-disabled=loading class="md-primary md-raised" ng-click=addKeyVal() aria-label="{{ \'action.add\' | translate }}"> <md-tooltip md-direction=top> {{ \'tb.key-val.add-entry\' | translate }} </md-tooltip> <md-icon aria-label="{{ \'action.add\' | translate }}" class=material-icons> add </md-icon> {{ \'action.add\' | translate }} </md-button> </div> </section> '},function(e,t){e.exports=" <section layout=column> <div layout=row> <md-input-container class=md-block style=min-width:100px> <label translate>relation.direction</label> <md-select required ng-model=query.direction> <md-option ng-repeat=\"direction in types.entitySearchDirection\" ng-value=direction> {{ ('relation.search-direction.' + direction) | translate}} </md-option> </md-select> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.max-relation-level</label> <input name=maxRelationLevel type=number min=1 step=1 placeholder=\"{{ 'tb.rulenode.unlimited-level' | translate }}\" ng-model=query.maxLevel aria-label=\"{{ 'tb.rulenode.max-relation-level' | translate }}\"> </md-input-container> </div> <div class=md-caption style=padding-bottom:10px;color:rgba(0,0,0,.57) translate>relation.relation-filters</div> <tb-relation-filters ng-model=query.filters> </tb-relation-filters> </section> "},function(e,t){e.exports=' <section layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.originator-source</label> <md-select required ng-model=configuration.originatorSource> <md-option ng-repeat="source in ruleNodeTypes.originatorSource" ng-value=source.value> {{ source.name | translate}} </md-option> </md-select> </md-input-container> <section layout=column ng-if="configuration.originatorSource == ruleNodeTypes.originatorSource.RELATED.value"> <label translate class="tb-title tb-required">tb.rulenode.relations-query</label> <tb-relations-query-config style=padding-bottom:15px ng-model=configuration.relationsQuery> </tb-relations-query-config> </section> </section> '},function(e,t){e.exports=" <section layout=column> <label translate class=\"tb-title no-padding\">tb.rulenode.transform</label> <tb-js-func ng-model=configuration.jsScript function-name=Transform function-args=\"{{ ['msg', 'metadata', 'msgType'] }}\" no-validate=true> </tb-js-func> <div layout=row style=padding-bottom:15px> <md-button ng-click=testScript($event) class=\"md-primary md-raised\"> {{ 'tb.rulenode.test-transformer-function' | translate }} </md-button> </div> </section> "},function(e,t){e.exports=" <section ng-form name=toEmailConfigForm layout=column> <md-input-container class=md-block> <label translate>tb.rulenode.from-template</label> <textarea ng-required=true name=fromTemplate ng-model=configuration.fromTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.fromTemplate.$error> <div ng-message=required translate>tb.rulenode.from-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.from-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.to-template</label> <textarea ng-required=true name=toTemplate ng-model=configuration.toTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.toTemplate.$error> <div ng-message=required translate>tb.rulenode.to-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.cc-template</label> <textarea name=ccTemplate ng-model=configuration.ccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.bcc-template</label> <textarea name=ccTemplate ng-model=configuration.bccTemplate rows=2></textarea> <div class=tb-hint translate>tb.rulenode.mail-address-list-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.subject-template</label> <textarea ng-required=true name=subjectTemplate ng-model=configuration.subjectTemplate rows=2></textarea> <div ng-messages=toEmailConfigForm.subjectTemplate.$error> <div ng-message=required translate>tb.rulenode.subject-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.subject-template-hint</div> </md-input-container> <md-input-container class=md-block> <label translate>tb.rulenode.body-template</label> <textarea ng-required=true name=bodyTemplate ng-model=configuration.bodyTemplate rows=6></textarea> <div ng-messages=toEmailConfigForm.bodyTemplate.$error> <div ng-message=required translate>tb.rulenode.body-template-required</div> </div> <div class=tb-hint translate>tb.rulenode.body-template-hint</div> </md-input-container> </section> "},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(6),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(7),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue},r.testDetailsBuildJs=function(e){var n=angular.copy(r.configuration.alarmDetailsBuildJs);a.testNodeScript(e,n,"json",t.instant("tb.rulenode.details")+"","Details",["msg","metadata","msgType"],r.ruleNodeId).then(function(e){r.configuration.alarmDetailsBuildJs=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(8),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n,a){var r=function(r,i,l,s){var u=o.default;i.html(u),r.types=n,r.originator=null,r.$watch("configuration",function(e,t){angular.equals(e,t)||s.$setViewValue(r.configuration)}),s.$render=function(){r.configuration=s.$viewValue,r.configuration.originatorId&&r.configuration.originatorType?r.originator={id:r.configuration.originatorId,entityType:r.configuration.originatorType}:r.originator=null,r.$watch("originator",function(e,t){angular.equals(e,t)||(r.originator?(s.$viewValue.originatorId=r.originator.id,s.$viewValue.originatorType=r.originator.entityType):(s.$viewValue.originatorId=null,s.$viewValue.originatorType=null))},!0)},r.testScript=function(e){var n=angular.copy(r.configuration.jsScript);a.testNodeScript(e,n,"generate",t.instant("tb.rulenode.generator")+"","Generate",["prevMsg","prevMetadata","prevMsgType"],r.ruleNodeId).then(function(e){r.configuration.jsScript=e,s.$setDirty()})},e(i.contents())(r)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:r}}r.$inject=["$compile","$translate","types","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(1);var i=n(9),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(56),i=a(r),o=n(40),l=a(o),s=n(43),u=a(s),d=n(42),c=a(d),m=n(41),g=a(m),p=n(46),f=a(p),b=n(51),v=a(b),y=n(52),q=a(y),h=n(50),$=a(h),T=n(45),k=a(T),w=n(54),x=a(w),C=n(55),M=a(C),S=n(49),_=a(S),N=n(47),V=a(N),E=n(53),P=a(E),j=n(48),F=a(j);t.default=angular.module("thingsboard.ruleChain.config.action",[]).directive("tbActionNodeTimeseriesConfig",i.default).directive("tbActionNodeAttributesConfig",l.default).directive("tbActionNodeGeneratorConfig",u.default).directive("tbActionNodeCreateAlarmConfig",c.default).directive("tbActionNodeClearAlarmConfig",g.default).directive("tbActionNodeLogConfig",f.default).directive("tbActionNodeRpcReplyConfig",v.default).directive("tbActionNodeRpcRequestConfig",q.default).directive("tbActionNodeRestApiCallConfig",$.default).directive("tbActionNodeKafkaConfig",k.default).directive("tbActionNodeSnsConfig",x.default).directive("tbActionNodeSqsConfig",M.default).directive("tbActionNodeRabbitMqConfig",_.default).directive("tbActionNodeMqttConfig",V.default).directive("tbActionNodeSendEmailConfig",P.default).directive("tbActionNodeMsgDelayConfig",F.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.ackValues=["all","-1","0","1"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(10),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"string",t.instant("tb.rulenode.to-string")+"","ToString",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(11),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$mdExpansionPanel=t,a.ruleNodeTypes=n,a.credentialsTypeChanged=function(){var e=a.configuration.credentials.type;a.configuration.credentials={},a.configuration.credentials.type=e,a.updateValidity()},a.certFileAdded=function(e,t){var n=new FileReader;n.onload=function(n){a.$apply(function(){if(n.target.result){l.$setDirty();var r=n.target.result;r&&r.length>0&&("caCert"==t&&(a.configuration.credentials.caCertFileName=e.name,a.configuration.credentials.caCert=r),"privateKey"==t&&(a.configuration.credentials.privateKeyFileName=e.name,a.configuration.credentials.privateKey=r),"Cert"==t&&(a.configuration.credentials.certFileName=e.name,a.configuration.credentials.cert=r)),a.updateValidity()}})},n.readAsText(e.file)},a.clearCertFile=function(e){l.$setDirty(),"caCert"==e&&(a.configuration.credentials.caCertFileName=null,a.configuration.credentials.caCert=null),"privateKey"==e&&(a.configuration.credentials.privateKeyFileName=null,a.configuration.credentials.privateKey=null),"Cert"==e&&(a.configuration.credentials.certFileName=null,a.configuration.credentials.cert=null),a.updateValidity()},a.updateValidity=function(){var e=!0,t=a.configuration.credentials;t.type==n.mqttCredentialTypes["cert.PEM"].value&&(t.caCert&&t.cert&&t.privateKey||(e=!1)),l.$setValidity("Certs",e)},a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$mdExpansionPanel","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(2);var i=n(12),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(13),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(14),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(15),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(16),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(17),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.smtpProtocols=["smtp","smtps"],t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(18),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(19),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{readonly:"=ngReadonly"},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(20),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(21),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(22),o=a(i)},function(e,t){"use strict";function n(e){var t=function(t,n,a,r){n.html("<div></div>"),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}n.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(23),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration);
  3 +}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(24),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s);var u=186;a.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,u],a.ruleNodeTypes=n,a.aggPeriodTimeUnits={},a.aggPeriodTimeUnits.MINUTES=n.timeUnit.MINUTES,a.aggPeriodTimeUnits.HOURS=n.timeUnit.HOURS,a.aggPeriodTimeUnits.DAYS=n.timeUnit.DAYS,a.aggPeriodTimeUnits.MILLISECONDS=n.timeUnit.MILLISECONDS,a.aggPeriodTimeUnits.SECONDS=n.timeUnit.SECONDS,a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{},link:a}}r.$inject=["$compile","$mdConstant","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(25),o=a(i);n(3)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(63),i=a(r),o=n(64),l=a(o),s=n(60),u=a(s),d=n(65),c=a(d),m=n(59),g=a(m),p=n(66),f=a(p),b=n(61),v=a(b);t.default=angular.module("thingsboard.ruleChain.config.enrichment",[]).directive("tbEnrichmentNodeOriginatorAttributesConfig",i.default).directive("tbEnrichmentNodeOriginatorFieldsConfig",l.default).directive("tbEnrichmentNodeDeviceAttributesConfig",u.default).directive("tbEnrichmentNodeRelatedAttributesConfig",c.default).directive("tbEnrichmentNodeCustomerAttributesConfig",g.default).directive("tbEnrichmentNodeTenantAttributesConfig",f.default).directive("tbEnrichmentNodeGetTelemetryFromDatabase",v.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l);var s=186;n.separatorKeys=[t.KEY_CODE.ENTER,t.KEY_CODE.COMMA,s],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","$mdConstant"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(26),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(27),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(28),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(29),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(30),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(71),i=a(r),o=n(69),l=a(o),s=n(72),u=a(s),d=n(67),c=a(d),m=n(70),g=a(m);t.default=angular.module("thingsboard.ruleChain.config.filter",[]).directive("tbFilterNodeScriptConfig",i.default).directive("tbFilterNodeMessageTypeConfig",l.default).directive("tbFilterNodeSwitchConfig",u.default).directive("tbFilterNodeCheckRelationConfig",c.default).directive("tbFilterNodeOriginatorTypeConfig",g.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){function s(){if(l.$viewValue){for(var e=[],t=0;t<a.messageTypes.length;t++)e.push(a.messageTypes[t].value);l.$viewValue.messageTypes=e,u()}}function u(){if(a.required){var e=!(!l.$viewValue.messageTypes||!l.$viewValue.messageTypes.length);l.$setValidity("messageTypes",e)}else l.$setValidity("messageTypes",!0)}var d=o.default;r.html(d),a.selectedMessageType=null,a.messageTypeSearchText=null,a.ngModelCtrl=l;var c=[];for(var m in n.messageType){var g={name:n.messageType[m].name,value:n.messageType[m].value};c.push(g)}a.transformMessageTypeChip=function(e){var n,a=t("filter")(c,{name:e},!0);return n=a&&a.length?angular.copy(a[0]):{name:e,value:e}},a.messageTypesSearch=function(e){var n=e?t("filter")(c,{name:e}):c;return n.map(function(e){return e.name})},a.createMessageType=function(e,t){var n=angular.element(t,r)[0].firstElementChild,a=angular.element(n),i=a.scope().$mdChipsCtrl.getChipBuffer();e.preventDefault(),e.stopPropagation(),a.scope().$mdChipsCtrl.appendChip(i.trim()),a.scope().$mdChipsCtrl.resetChipBuffer()},l.$render=function(){a.messageTypesWatch&&(a.messageTypesWatch(),a.messageTypesWatch=null);var e=l.$viewValue,t=[];if(e&&e.messageTypes)for(var r=0;r<e.messageTypes.length;r++){var i=e.messageTypes[r];n.messageType[i]?t.push(angular.copy(n.messageType[i])):t.push({name:i,value:i})}a.messageTypes=t,a.messageTypesWatch=a.$watch("messageTypes",function(e,t){angular.equals(e,t)||s()},!0)},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",readonly:"=ngReadonly"},link:a}}r.$inject=["$compile","$filter","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r,n(4);var i=n(31),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.allowedEntityTypes=[t.entityType.device,t.entityType.asset,t.entityType.tenant,t.entityType.customer,t.entityType.user,t.entityType.dashboard,t.entityType.rulechain,t.entityType.rulenode],n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(32),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"filter",t.instant("tb.rulenode.filter")+"","Filter",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(33),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"switch",t.instant("tb.rulenode.switch")+"","Switch",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(34),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){function i(e){e>-1&&t.kvList.splice(e,1)}function l(){t.kvList||(t.kvList=[]),t.kvList.push({key:"",value:""})}function s(){var e={};t.kvList.forEach(function(t){t.key&&(e[t.key]=t.value)}),r.$setViewValue(e),u()}function u(){var e=!0;t.required&&!t.kvList.length&&(e=!1),r.$setValidity("kvMap",e)}var d=o.default;n.html(d),t.ngModelCtrl=r,t.removeKeyVal=i,t.addKeyVal=l,t.kvList=[],t.$watch("query",function(e,n){angular.equals(e,n)||r.$setViewValue(t.query)}),r.$render=function(){if(r.$viewValue){var e=r.$viewValue;t.kvList.length=0;for(var n in e)t.kvList.push({key:n,value:e[n]})}t.$watch("kvList",function(e,t){angular.equals(e,t)||s()},!0),u()},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{required:"=ngRequired",disabled:"=ngDisabled",requiredText:"=",keyText:"=",keyRequiredText:"=",valText:"=",valRequiredText:"="},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(35),o=a(i);n(5)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.types=t,n.$watch("query",function(e,t){angular.equals(e,t)||i.$setViewValue(n.query)}),i.$render=function(){n.query=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","types"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(36),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t){var n=function(n,a,r,i){var l=o.default;a.html(l),n.ruleNodeTypes=t,n.$watch("configuration",function(e,t){angular.equals(e,t)||i.$setViewValue(n.configuration)}),i.$render=function(){n.configuration=i.$viewValue},e(a.contents())(n)};return{restrict:"E",require:"^ngModel",scope:{},link:n}}r.$inject=["$compile","ruleNodeTypes"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(37),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(75),i=a(r),o=n(77),l=a(o),s=n(78),u=a(s);t.default=angular.module("thingsboard.ruleChain.config.transform",[]).directive("tbTransformationNodeChangeOriginatorConfig",i.default).directive("tbTransformationNodeScriptConfig",l.default).directive("tbTransformationNodeToEmailConfig",u.default).name},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e,t,n){var a=function(a,r,i,l){var s=o.default;r.html(s),a.$watch("configuration",function(e,t){angular.equals(e,t)||l.$setViewValue(a.configuration)}),l.$render=function(){a.configuration=l.$viewValue},a.testScript=function(e){var r=angular.copy(a.configuration.jsScript);n.testNodeScript(e,r,"update",t.instant("tb.rulenode.transformer")+"","Transform",["msg","metadata","msgType"],a.ruleNodeId).then(function(e){a.configuration.jsScript=e,l.$setDirty()})},e(r.contents())(a)};return{restrict:"E",require:"^ngModel",scope:{ruleNodeId:"="},link:a}}r.$inject=["$compile","$translate","ruleNodeScriptTest"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(38),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){var t=function(t,n,a,r){var i=o.default;n.html(i),t.$watch("configuration",function(e,n){angular.equals(e,n)||r.$setViewValue(t.configuration)}),r.$render=function(){t.configuration=r.$viewValue},e(n.contents())(t)};return{restrict:"E",require:"^ngModel",scope:{},link:t}}r.$inject=["$compile"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(39),o=a(i)},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}Object.defineProperty(t,"__esModule",{value:!0});var r=n(82),i=a(r),o=n(68),l=a(o),s=n(62),u=a(s),d=n(76),c=a(d),m=n(44),g=a(m),p=n(58),f=a(p),b=n(74),v=a(b),y=n(57),q=a(y),h=n(73),$=a(h),T=n(81),k=a(T);t.default=angular.module("thingsboard.ruleChain.config",[i.default,l.default,u.default,c.default,g.default]).directive("tbNodeEmptyConfig",f.default).directive("tbRelationsQueryConfig",v.default).directive("tbDeviceRelationsQueryConfig",q.default).directive("tbKvMapConfig",$.default).config(k.default).name},function(e,t){"use strict";function n(e){var t={tb:{rulenode:{"start-interval":"Start Interval","end-interval":"End Interval","start-interval-time-unit":"Start Interval Time Unit","end-interval-time-unit":"End Interval Time Unit","fetch-mode":"Fetch mode","time-unit-milliseconds":"Milliseconds","time-unit-seconds":"Seconds","time-unit-minutes":"Minutes","time-unit-hours":"Hours","time-unit-days":"Days","time-value-range":"Time value should be in a range from 1 to 2147483647'.","start-interval-value-required":"Start interval value is required.","end-interval-value-required":"End interval value is required.",filter:"Filter",switch:"Switch","message-type":"Message type","message-type-required":"Message type is required.","message-types-filter":"Message types filter","no-message-types-found":"No message types found","no-message-type-matching":"'{{messageType}}' not found.","create-new-message-type":"Create a new one!","message-types-required":"Message types are required.","client-attributes":"Client attributes","shared-attributes":"Shared attributes","server-attributes":"Server attributes","latest-timeseries":"Latest timeseries","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","attr-mapping":"Attributes mapping","source-attribute":"Source attribute","source-attribute-required":"Source attribute is required.","source-telemetry":"Source telemetry","source-telemetry-required":"Source telemetry is required.","target-attribute":"Target attribute","target-attribute-required":"Target attribute is required.","attr-mapping-required":"At least one attribute mapping should be specified.","fields-mapping":"Fields mapping","fields-mapping-required":"At least one field mapping should be specified.","source-field":"Source field","source-field-required":"Source field is required.","originator-source":"Originator source","originator-customer":"Customer","originator-tenant":"Tenant","originator-related":"Related","clone-message":"Clone message",transform:"Transform","default-ttl":"Default TTL in seconds","default-ttl-required":"Default TTL is required.","min-default-ttl-message":"Only 0 minimum TTL is allowed.","message-count":"Message count (0 - unlimited)","message-count-required":"Message count is required.","min-message-count-message":"Only 0 minimum message count is allowed.","period-seconds":"Period in seconds","period-seconds-required":"Period is required.","min-period-seconds-message":"Only 1 second minimum period is allowed.",originator:"Originator","message-body":"Message body","message-metadata":"Message metadata",generate:"Generate","test-generator-function":"Test generator function",generator:"Generator","test-filter-function":"Test filter function","test-switch-function":"Test switch function","test-transformer-function":"Test transformer function",transformer:"Transformer","alarm-create-condition":"Alarm create condition","test-condition-function":"Test condition function","alarm-clear-condition":"Alarm clear condition","alarm-details-builder":"Alarm details builder","test-details-function":"Test details function","alarm-type":"Alarm type","alarm-type-required":"Alarm type is required.","alarm-severity":"Alarm severity","alarm-severity-required":"Alarm severity is required",propagate:"Propagate",condition:"Condition",details:"Details","to-string":"To string","test-to-string-function":"Test to string function","from-template":"From Template","from-template-required":"From Template is required","from-template-hint":"From address template, use <code>${metaKeyName}</code> to substitute variables from metadata","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":"Comma separated address list, use <code>${metaKeyName}</code> to substitute variables from metadata","cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","subject-template-hint":"Mail subject template, use <code>${metaKeyName}</code> to substitute variables from metadata","body-template":"Body Template","body-template-required":"Body Template is required","body-template-hint":"Mail body template, use <code>${metaKeyName}</code> to substitute variables from metadata","request-id-metadata-attribute":"Request Id Metadata attribute name","timeout-sec":"Timeout in seconds","timeout-required":"Timeout is required","min-timeout-message":"Only 0 minimum timeout value is allowed.","endpoint-url-pattern":"Endpoint URL pattern","endpoint-url-pattern-required":"Endpoint URL pattern is required","endpoint-url-pattern-hint":"HTTP URL address pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","request-method":"Request method",headers:"Headers","headers-hint":"Use <code>${metaKeyName}</code> in header/value fields to substitute variables from metadata",header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","topic-pattern-required":"Topic pattern is required","mqtt-topic-pattern-hint":"MQTT topic pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","bootstrap-servers":"Bootstrap servers","bootstrap-servers-required":"Bootstrap servers value is required","other-properties":"Other properties",key:"Key","key-required":"Key is required",retries:"Automatically retry times if fails","min-retries-message":"Only 0 minimum retries is allowed.","batch-size-bytes":"Produces batch size in bytes","min-batch-size-bytes-message":"Only 0 minimum batch size is allowed.","linger-ms":"Time to buffer locally (ms)","min-linger-ms-message":"Only 0 ms minimum value is allowed.","buffer-memory-bytes":"Client buffer max size in bytes","min-buffer-memory-message":"Only 0 minimum buffer size is allowed.",acks:"Number of acknowledgments","key-serializer":"Key serializer","key-serializer-required":"Key serializer is required","value-serializer":"Value serializer","value-serializer-required":"Value serializer is required","topic-arn-pattern":"Topic ARN pattern","topic-arn-pattern-required":"Topic ARN pattern is required","topic-arn-pattern-hint":"Topic ARN pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","aws-access-key-id":"AWS Access Key ID","aws-access-key-id-required":"AWS Access Key ID is required","aws-secret-access-key":"AWS Secret Access Key","aws-secret-access-key-required":"AWS Secret Access Key is required","aws-region":"AWS Region","aws-region-required":"AWS Region is required","exchange-name-pattern":"Exchange name pattern","routing-key-pattern":"Routing key pattern","message-properties":"Message properties",host:"Host","host-required":"Host is required",port:"Port","port-required":"Port is required","port-range":"Port should be in a range from 1 to 65535.","virtual-host":"Virtual host",username:"Username",password:"Password","automatic-recovery":"Automatic recovery","connection-timeout-ms":"Connection timeout (ms)","min-connection-timeout-ms-message":"Only 0 ms minimum value is allowed.","handshake-timeout-ms":"Handshake timeout (ms)","min-handshake-timeout-ms-message":"Only 0 ms minimum value is allowed.","client-properties":"Client properties","queue-url-pattern":"Queue URL pattern","queue-url-pattern-required":"Queue URL pattern is required","queue-url-pattern-hint":"Queue URL pattern, use <code>${metaKeyName}</code> to substitute variables from metadata","delay-seconds":"Delay (seconds)","min-delay-seconds-message":"Only 0 seconds minimum value is allowed.","max-delay-seconds-message":"Only 900 seconds maximum value is allowed.",name:"Name","name-required":"Name is required","queue-type":"Queue type","sqs-queue-standard":"Standard","sqs-queue-fifo":"FIFO","message-attributes":"Message attributes","message-attributes-hint":"Use <code>${metaKeyName}</code> in name/value fields to substitute variables from metadata","connect-timeout":"Connection timeout (sec)","connect-timeout-required":"Connection timeout is required.","connect-timeout-range":"Connection timeout should be in a range from 1 to 200.","client-id":"Client ID","clean-session":"Clean session","enable-ssl":"Enable SSL",credentials:"Credentials","credentials-type":"Credentials type","credentials-type-required":"Credentials type is required.","credentials-anonymous":"Anonymous","credentials-basic":"Basic","credentials-pem":"PEM","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"CA certificate file *","private-key":"Private key file *",cert:"Certificate file *","no-file":"No file selected.","drop-file":"Drop a file or click to select a file to upload.","private-key-password":"Private key password","use-system-smtp-settings":"Use system SMTP settings","smtp-protocol":"Protocol","smtp-host":"SMTP host","smtp-host-required":"SMTP host is required.","smtp-port":"SMTP port","smtp-port-required":"You must supply a smtp port.","smtp-port-range":"SMTP port should be in a range from 1 to 65535.","timeout-msec":"Timeout ms","min-timeout-msec-message":"Only 0 ms minimum value is allowed.","enter-username":"Enter username","enter-password":"Enter password","enable-tls":"Enable TLS","min-period-0-seconds-message":"Only 0 second minimum period is allowed.","max-pending-messages":"Maximum pending messages","max-pending-messages-required":"Maximum pending messages is required.","max-pending-messages-range":"Maximum pending messages should be in a range from 1 to 100000.","originator-types-filter":"Originator types filter"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"}}};e.translations("en_US",t)}Object.defineProperty(t,"__esModule",{value:!0}),t.default=n},function(e,t,n){"use strict";function a(e){return e&&e.__esModule?e:{default:e}}function r(e){(0,o.default)(e)}r.$inject=["$translateProvider"],Object.defineProperty(t,"__esModule",{value:!0}),t.default=r;var i=n(80),o=a(i)},function(e,t){"use strict";Object.defineProperty(t,"__esModule",{value:!0}),t.default=angular.module("thingsboard.ruleChain.config.types",[]).constant("ruleNodeTypes",{originatorSource:{CUSTOMER:{name:"tb.rulenode.originator-customer",value:"CUSTOMER"},TENANT:{name:"tb.rulenode.originator-tenant",value:"TENANT"},RELATED:{name:"tb.rulenode.originator-related",value:"RELATED"}},fetchModeType:["FIRST","LAST","ALL"],httpRequestType:["GET","POST","PUT","DELETE"],sqsQueueType:{STANDARD:{name:"tb.rulenode.sqs-queue-standard",value:"STANDARD"},FIFO:{name:"tb.rulenode.sqs-queue-fifo",value:"FIFO"}},timeUnit:{MILLISECONDS:{value:"MILLISECONDS",name:"tb.rulenode.time-unit-milliseconds"},SECONDS:{value:"SECONDS",name:"tb.rulenode.time-unit-seconds"},MINUTES:{value:"MINUTES",name:"tb.rulenode.time-unit-minutes"},HOURS:{value:"HOURS",name:"tb.rulenode.time-unit-hours"},DAYS:{value:"DAYS",name:"tb.rulenode.time-unit-days"}},mqttCredentialTypes:{anonymous:{value:"anonymous",name:"tb.rulenode.credentials-anonymous"},basic:{value:"basic",name:"tb.rulenode.credentials-basic"},"cert.PEM":{value:"cert.PEM",name:"tb.rulenode.credentials-pem"}}}).name}]));
4 4 //# sourceMappingURL=rulenode-core-config.js.map
\ No newline at end of file
... ...
  1 +{
  2 + "extends": ["stylelint-config-standard", "stylelint-config-recommended-scss"],
  3 + "plugins": [
  4 + "stylelint-order"
  5 + ],
  6 + "rules": {
  7 + "at-rule-empty-line-before": ["always", {
  8 + "except": ["first-nested"],
  9 + "ignore": ["after-comment"]
  10 + }],
  11 + "at-rule-name-space-after": "always",
  12 + "at-rule-no-vendor-prefix": true,
  13 + "at-rule-semicolon-space-before": "never",
  14 + "block-closing-brace-empty-line-before": "never",
  15 + "block-closing-brace-newline-after": null,
  16 + "block-opening-brace-space-before": null,
  17 + "color-named": "never",
  18 + "declaration-block-semicolon-newline-after": "always-multi-line",
  19 + "declaration-block-semicolon-newline-before": "never-multi-line",
  20 + "declaration-block-semicolon-space-after": "always-single-line",
  21 + "declaration-empty-line-before": null,
  22 + "declaration-no-important": null,
  23 + "font-family-name-quotes": "always-where-recommended",
  24 + "font-weight-notation": [
  25 + "numeric", {
  26 + "ignore": ["relative"]
  27 + }],
  28 + "function-url-no-scheme-relative": true,
  29 + "function-url-quotes": "always",
  30 + "length-zero-no-unit": true,
  31 + "max-empty-lines": 2,
  32 + "max-line-length": null,
  33 + "media-feature-name-no-vendor-prefix": true,
  34 + "media-feature-parentheses-space-inside": "never",
  35 + "media-feature-range-operator-space-after": "always",
  36 + "media-feature-range-operator-space-before": "never",
  37 + "no-descending-specificity": null,
  38 + "no-duplicate-selectors": true,
  39 + "number-leading-zero": "never",
  40 + "media-feature-name-no-unknown": [true, {
  41 + "ignoreMediaFeatureNames": ["prefers-reduced-motion"]
  42 + }],
  43 + "order/properties-order": [
  44 + "position",
  45 + "top",
  46 + "right",
  47 + "bottom",
  48 + "left",
  49 + "z-index",
  50 + "box-sizing",
  51 + "display",
  52 + "flex",
  53 + "flex-align",
  54 + "flex-basis",
  55 + "flex-direction",
  56 + "flex-wrap",
  57 + "flex-flow",
  58 + "flex-shrink",
  59 + "flex-grow",
  60 + "flex-order",
  61 + "flex-pack",
  62 + "align-content",
  63 + "align-items",
  64 + "align-self",
  65 + "justify-content",
  66 + "order",
  67 + "float",
  68 + "width",
  69 + "min-width",
  70 + "max-width",
  71 + "height",
  72 + "min-height",
  73 + "max-height",
  74 + "padding",
  75 + "padding-top",
  76 + "padding-right",
  77 + "padding-bottom",
  78 + "padding-left",
  79 + "margin",
  80 + "margin-top",
  81 + "margin-right",
  82 + "margin-bottom",
  83 + "margin-left",
  84 + "overflow",
  85 + "overflow-x",
  86 + "overflow-y",
  87 + "-webkit-overflow-scrolling",
  88 + "-ms-overflow-x",
  89 + "-ms-overflow-y",
  90 + "-ms-overflow-style",
  91 + "columns",
  92 + "column-count",
  93 + "column-fill",
  94 + "column-gap",
  95 + "column-rule",
  96 + "column-rule-width",
  97 + "column-rule-style",
  98 + "column-rule-color",
  99 + "column-span",
  100 + "column-width",
  101 + "orphans",
  102 + "widows",
  103 + "clip",
  104 + "clear",
  105 + "font",
  106 + "font-family",
  107 + "font-size",
  108 + "font-style",
  109 + "font-weight",
  110 + "font-variant",
  111 + "font-size-adjust",
  112 + "font-stretch",
  113 + "font-effect",
  114 + "font-emphasize",
  115 + "font-emphasize-position",
  116 + "font-emphasize-style",
  117 + "font-smooth",
  118 + "src",
  119 + "hyphens",
  120 + "line-height",
  121 + "color",
  122 + "text-align",
  123 + "text-align-last",
  124 + "text-emphasis",
  125 + "text-emphasis-color",
  126 + "text-emphasis-style",
  127 + "text-emphasis-position",
  128 + "text-decoration",
  129 + "text-indent",
  130 + "text-justify",
  131 + "text-outline",
  132 + "-ms-text-overflow",
  133 + "text-overflow",
  134 + "text-overflow-ellipsis",
  135 + "text-overflow-mode",
  136 + "text-shadow",
  137 + "text-transform",
  138 + "text-wrap",
  139 + "-webkit-text-size-adjust",
  140 + "-ms-text-size-adjust",
  141 + "letter-spacing",
  142 + "-ms-word-break",
  143 + "word-break",
  144 + "word-spacing",
  145 + "-ms-word-wrap",
  146 + "word-wrap",
  147 + "overflow-wrap",
  148 + "tab-size",
  149 + "white-space",
  150 + "vertical-align",
  151 + "direction",
  152 + "unicode-bidi",
  153 + "list-style",
  154 + "list-style-position",
  155 + "list-style-type",
  156 + "list-style-image",
  157 + "pointer-events",
  158 + "-ms-touch-action",
  159 + "touch-action",
  160 + "cursor",
  161 + "visibility",
  162 + "zoom",
  163 + "table-layout",
  164 + "empty-cells",
  165 + "caption-side",
  166 + "border-spacing",
  167 + "border-collapse",
  168 + "content",
  169 + "quotes",
  170 + "counter-reset",
  171 + "counter-increment",
  172 + "resize",
  173 + "user-select",
  174 + "nav-index",
  175 + "nav-up",
  176 + "nav-right",
  177 + "nav-down",
  178 + "nav-left",
  179 + "background",
  180 + "background-color",
  181 + "background-image",
  182 + "filter",
  183 + "background-repeat",
  184 + "background-attachment",
  185 + "background-position",
  186 + "background-position-x",
  187 + "background-position-y",
  188 + "background-clip",
  189 + "background-origin",
  190 + "background-size",
  191 + "border",
  192 + "border-color",
  193 + "border-style",
  194 + "border-width",
  195 + "border-top",
  196 + "border-top-color",
  197 + "border-top-style",
  198 + "border-top-width",
  199 + "border-right",
  200 + "border-right-color",
  201 + "border-right-style",
  202 + "border-right-width",
  203 + "border-bottom",
  204 + "border-bottom-color",
  205 + "border-bottom-style",
  206 + "border-bottom-width",
  207 + "border-left",
  208 + "border-left-color",
  209 + "border-left-style",
  210 + "border-left-width",
  211 + "border-radius",
  212 + "border-top-left-radius",
  213 + "border-top-right-radius",
  214 + "border-bottom-right-radius",
  215 + "border-bottom-left-radius",
  216 + "border-image",
  217 + "border-image-source",
  218 + "border-image-slice",
  219 + "border-image-width",
  220 + "border-image-outset",
  221 + "border-image-repeat",
  222 + "outline",
  223 + "outline-width",
  224 + "outline-style",
  225 + "outline-color",
  226 + "outline-offset",
  227 + "box-shadow",
  228 + "opacity",
  229 + "-ms-interpolation-mode",
  230 + "page-break-after",
  231 + "page-break-before",
  232 + "page-break-inside",
  233 + "transition",
  234 + "transition-delay",
  235 + "transition-timing-function",
  236 + "transition-duration",
  237 + "transition-property",
  238 + "transform",
  239 + "transform-origin",
  240 + "perspective",
  241 + "appearance",
  242 + "animation",
  243 + "animation-name",
  244 + "animation-duration",
  245 + "animation-play-state",
  246 + "animation-timing-function",
  247 + "animation-delay",
  248 + "animation-iteration-count",
  249 + "animation-direction",
  250 + "animation-fill-mode",
  251 + "fill",
  252 + "stroke"
  253 + ],
  254 + "property-no-vendor-prefix": null,
  255 + "rule-empty-line-before": ["always", {
  256 + "except": ["first-nested"],
  257 + "ignore": ["after-comment"]
  258 + }],
  259 + "scss/dollar-variable-default": [true, { "ignore": "local" }],
  260 + "selector-attribute-quotes": "always",
  261 + "selector-list-comma-newline-after": "always",
  262 + "selector-list-comma-newline-before": "never-multi-line",
  263 + "selector-list-comma-space-after": "always-single-line",
  264 + "selector-list-comma-space-before": "never-single-line",
  265 + "selector-max-attribute": 2,
  266 + "selector-max-class": 6,
  267 + "selector-max-combinators": 8,
  268 + "selector-max-compound-selectors": 9,
  269 + "selector-max-empty-lines": 1,
  270 + "selector-max-id": 1,
  271 + "selector-max-specificity": null,
  272 + "selector-max-type": 5,
  273 + "selector-max-universal": 1,
  274 + "selector-no-qualifying-type": null,
  275 + "selector-no-vendor-prefix": null,
  276 + "selector-type-no-unknown": [true, {
  277 + "ignoreTypes": [
  278 + "/^md-/",
  279 + "/^mdp-/",
  280 + "/^ng-/",
  281 + "/^tb-/",
  282 + "/^v-pane/"
  283 + ]
  284 + }],
  285 + "string-quotes": "double",
  286 + "value-keyword-case": "lower",
  287 + "value-list-comma-newline-after": "always-multi-line",
  288 + "value-list-comma-newline-before": "never-multi-line",
  289 + "value-list-comma-space-after": "always-single-line",
  290 + "value-no-vendor-prefix": null
  291 + }
  292 +}
... ...
... ... @@ -100,6 +100,7 @@
100 100 "copy-webpack-plugin": "^3.0.1",
101 101 "cross-env": "^3.2.4",
102 102 "css-loader": "^0.25.0",
  103 + "directory-tree": "^2.1.0",
103 104 "eslint": "^3.4.0",
104 105 "eslint-config-angular": "^0.5.0",
105 106 "eslint-loader": "^1.5.0",
... ... @@ -112,6 +113,7 @@
112 113 "html-minifier-loader": "^1.3.4",
113 114 "html-webpack-plugin": "^2.30.1",
114 115 "img-loader": "^1.3.1",
  116 + "jsonminify": "^0.4.1",
115 117 "less": "^2.7.1",
116 118 "less-loader": "^2.2.3",
117 119 "ng-annotate-loader": "^0.1.1",
... ... @@ -122,14 +124,18 @@
122 124 "react-hot-loader": "^3.0.0-beta.6",
123 125 "sass-loader": "^4.0.2",
124 126 "style-loader": "^0.13.1",
  127 + "stylelint": "^9.5.0",
  128 + "stylelint-config-recommended-scss": "^3.2.0",
  129 + "stylelint-config-standard": "^18.2.0",
  130 + "stylelint-order": "^1.0.0",
  131 + "stylelint-scss": "^3.3.0",
  132 + "stylelint-webpack-plugin": "^0.10.5",
125 133 "url-loader": "^0.5.7",
126 134 "webpack": "^1.13.2",
127 135 "webpack-dev-middleware": "^1.6.1",
128 136 "webpack-dev-server": "^1.15.1",
129 137 "webpack-hot-middleware": "^2.12.2",
130   - "webpack-material-design-icons": "^0.1.0",
131   - "directory-tree": "^2.1.0",
132   - "jsonminify": "^0.4.1"
  138 + "webpack-material-design-icons": "^0.1.0"
133 139 },
134 140 "engine": "node >= 5.9.0",
135 141 "nyc": {
... ...
... ... @@ -13,14 +13,16 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-alarm-details-panel {
17   - margin-left: 15px;
18   - border: 1px solid #C0C0C0;
19 18 height: 100%;
  19 + margin-left: 15px;
  20 + border: 1px solid #c0c0c0;
  21 +
20 22 #tb-alarm-details {
21   - min-width: 600px;
22   - min-height: 200px;
23 23 width: 100%;
  24 + min-width: 600px;
24 25 height: 100%;
  26 + min-height: 200px;
25 27 }
26 28 }
... ...
... ... @@ -13,26 +13,27 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-alarm-container {
17 18 overflow-x: auto;
18 19 }
19 20
20 21 md-list.tb-alarm-table {
21   - padding: 0px;
22 22 min-width: 700px;
  23 + padding: 0;
23 24
24 25 md-list-item {
25   - padding: 0px;
  26 + padding: 0;
26 27 }
27 28
28 29 .tb-row {
29 30 height: 48px;
30   - padding: 0px;
  31 + padding: 0;
31 32 overflow: hidden;
32 33 }
33 34
34 35 .tb-row:hover {
35   - background-color: #EEEEEE;
  36 + background-color: #eee;
36 37 }
37 38
38 39 .tb-header:hover {
... ... @@ -41,9 +42,9 @@ md-list.tb-alarm-table {
41 42
42 43 .tb-header {
43 44 .tb-cell {
44   - color: rgba(0,0,0,.54);
45 45 font-size: 12px;
46 46 font-weight: 700;
  47 + color: rgba(0, 0, 0, .54);
47 48 white-space: nowrap;
48 49 background: none;
49 50 }
... ... @@ -52,11 +53,12 @@ md-list.tb-alarm-table {
52 53 .tb-cell {
53 54 padding: 0 24px;
54 55 margin: auto 0;
55   - color: rgba(0,0,0,.87);
  56 + overflow: hidden;
56 57 font-size: 13px;
57   - vertical-align: middle;
  58 + color: rgba(0, 0, 0, .87);
58 59 text-align: left;
59   - overflow: hidden;
  60 + vertical-align: middle;
  61 +
60 62 .md-button {
61 63 padding: 0;
62 64 margin: 0;
... ... @@ -66,12 +68,11 @@ md-list.tb-alarm-table {
66 68 .tb-cell.tb-number {
67 69 text-align: right;
68 70 }
69   -
70 71 }
71 72
72 73 #tb-alarm-content {
73   - min-width: 400px;
74   - min-height: 50px;
75 74 width: 100%;
  75 + min-width: 400px;
76 76 height: 100%;
  77 + min-height: 50px;
77 78 }
... ...
... ... @@ -13,10 +13,12 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -#tb-audit-log-action-data, #tb-audit-log-failure-details {
17   - min-width: 400px;
18   - min-height: 50px;
  16 +
  17 +#tb-audit-log-action-data,
  18 +#tb-audit-log-failure-details {
19 19 width: 100%;
  20 + min-width: 400px;
20 21 height: 100%;
21   - border: 1px solid #C0C0C0;
22   -}
\ No newline at end of file
  22 + min-height: 50px;
  23 + border: 1px solid #c0c0c0;
  24 +}
... ...
... ... @@ -13,17 +13,21 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-audit-logs {
17 18 background-color: #fff;
  19 +
18 20 .tb-audit-log-margin-18px {
19 21 margin-bottom: 18px;
20 22 }
  23 +
21 24 .tb-audit-log-toolbar {
22 25 font-size: 20px;
23 26 }
  27 +
24 28 md-input-container.tb-audit-log-search-input {
25 29 .md-errors-spacer {
26   - min-height: 0px;
  30 + min-height: 0;
27 31 }
28 32 }
29 33 }
... ... @@ -32,27 +36,26 @@
32 36 overflow-x: auto;
33 37 }
34 38
35   -
36   -
37 39 md-list.tb-audit-log-table {
38   - padding: 0px;
39 40 min-width: 700px;
  41 + padding: 0;
  42 +
40 43 &.tb-audit-log-table-full {
41 44 min-width: 900px;
42 45 }
43 46
44 47 md-list-item {
45   - padding: 0px;
  48 + padding: 0;
46 49 }
47 50
48 51 .tb-row {
49 52 height: 48px;
50   - padding: 0px;
  53 + padding: 0;
51 54 overflow: hidden;
52 55 }
53 56
54 57 .tb-row:hover {
55   - background-color: #EEEEEE;
  58 + background-color: #eee;
56 59 }
57 60
58 61 .tb-header:hover {
... ... @@ -61,9 +64,9 @@ md-list.tb-audit-log-table {
61 64
62 65 .tb-header {
63 66 .tb-cell {
64   - color: rgba(0,0,0,.54);
65 67 font-size: 12px;
66 68 font-weight: 700;
  69 + color: rgba(0, 0, 0, .54);
67 70 white-space: nowrap;
68 71 background: none;
69 72 }
... ... @@ -72,11 +75,12 @@ md-list.tb-audit-log-table {
72 75 .tb-cell {
73 76 padding: 0 24px;
74 77 margin: auto 0;
75   - color: rgba(0,0,0,.87);
  78 + overflow: hidden;
76 79 font-size: 13px;
77   - vertical-align: middle;
  80 + color: rgba(0, 0, 0, .87);
78 81 text-align: left;
79   - overflow: hidden;
  82 + vertical-align: middle;
  83 +
80 84 .md-button {
81 85 padding: 0;
82 86 margin: 0;
... ... @@ -86,5 +90,4 @@ md-list.tb-audit-log-table {
86 90 .tb-cell.tb-number {
87 91 text-align: right;
88 92 }
89   -
90 93 }
... ...
... ... @@ -13,16 +13,19 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-dashboard-autocomplete {
17 18 .tb-not-found {
18 19 display: block;
19   - line-height: 1.5;
20 20 height: 48px;
  21 + line-height: 1.5;
21 22 }
  23 +
22 24 .tb-dashboard-item {
23 25 display: block;
24 26 height: 48px;
25 27 }
  28 +
26 29 li {
27 30 height: auto !important;
28 31 white-space: normal !important;
... ...
... ... @@ -13,18 +13,20 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -@import '../../scss/constants';
  16 +@import "../../scss/constants";
17 17
18 18 tb-dashboard-select {
19 19 min-width: 52px;
  20 +
20 21 md-select {
21   - pointer-events: all;
22 22 max-width: 300px;
  23 + pointer-events: all;
23 24 }
24 25 }
25 26
26 27 .tb-dashboard-select {
27 28 min-height: 32px;
  29 +
28 30 span {
29 31 pointer-events: all;
30 32 cursor: pointer;
... ... @@ -38,23 +40,27 @@ tb-dashboard-select {
38 40 }
39 41
40 42 .tb-dashboard-select-panel {
  43 + min-width: 300px;
  44 + max-width: 320px;
41 45 max-height: 150px;
  46 + overflow-x: hidden;
  47 + overflow-y: auto;
  48 + background: #fff;
  49 + border-radius: 4px;
  50 + box-shadow:
  51 + 0 7px 8px -4px rgba(0, 0, 0, .2),
  52 + 0 13px 19px 2px rgba(0, 0, 0, .14),
  53 + 0 5px 24px 4px rgba(0, 0, 0, .12);
  54 +
42 55 @media (min-height: 350px) {
43 56 max-height: 250px;
44 57 }
45   - max-width: 320px;
  58 +
46 59 @media (min-width: $layout-breakpoint-xs) {
47 60 max-width: 100%;
48 61 }
49   - min-width: 300px;
50   - background: white;
51   - border-radius: 4px;
52   - box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
53   - 0 13px 19px 2px rgba(0, 0, 0, 0.14),
54   - 0 5px 24px 4px rgba(0, 0, 0, 0.12);
55   - overflow-x: hidden;
56   - overflow-y: auto;
  62 +
57 63 md-content {
58 64 background-color: #fff;
59 65 }
60   -}
\ No newline at end of file
  66 +}
... ...
... ... @@ -21,6 +21,7 @@ div.tb-widget {
21 21 margin: 0;
22 22 overflow: hidden;
23 23 outline: none;
  24 +
24 25 @include transition(all .2s ease-in-out);
25 26
26 27 .tb-widget-title {
... ... @@ -32,7 +33,7 @@ div.tb-widget {
32 33
33 34 tb-timewindow {
34 35 font-size: 14px;
35   - opacity: 0.85;
  36 + opacity: .85;
36 37 }
37 38 }
38 39
... ... @@ -44,17 +45,19 @@ div.tb-widget {
44 45 margin: 0;
45 46
46 47 .md-button.md-icon-button {
47   - margin: 0 !important;
48   - padding: 0 !important;
49   - line-height: 20px;
50 48 width: 32px;
51   - height: 32px;
52 49 min-width: 32px;
  50 + height: 32px;
53 51 min-height: 32px;
54   - md-icon, ng-md-icon {
  52 + padding: 0 !important;
  53 + margin: 0 !important;
  54 + line-height: 20px;
  55 +
  56 + md-icon,
  57 + ng-md-icon {
55 58 width: 20px;
56   - height: 20px;
57 59 min-width: 20px;
  60 + height: 20px;
58 61 min-height: 20px;
59 62 font-size: 20px;
60 63 }
... ... @@ -63,8 +66,8 @@ div.tb-widget {
63 66
64 67 .tb-widget-content {
65 68 tb-widget {
66   - width: 100%;
67 69 position: relative;
  70 + width: 100%;
68 71 }
69 72 }
70 73 }
... ... @@ -75,40 +78,41 @@ div.tb-widget.tb-highlighted {
75 78 }
76 79
77 80 div.tb-widget.tb-not-highlighted {
78   - opacity: 0.5;
  81 + opacity: .5;
79 82 }
80 83
81 84 tb-dashboard {
82 85 position: absolute;
83 86 top: 0;
84   - left: 0;
85 87 right: 0;
86 88 bottom: 0;
  89 + left: 0;
87 90 }
88 91
89 92 md-content.tb-dashboard-content {
90 93 position: absolute;
91 94 top: 0;
92   - left: 0;
93 95 right: 0;
94 96 bottom: 0;
95   - outline: none;
  97 + left: 0;
96 98 background: none;
  99 + outline: none;
  100 +
97 101 .gridster-item {
98   - @include transition(none);
  102 + @include transition(none);
99 103 }
100 104 }
101 105
102 106 .tb-widget-error-container {
103 107 position: absolute;
104   - background-color: #fff;
105 108 width: 100%;
106 109 height: 100%;
  110 + background-color: #fff;
107 111 }
108 112
109 113 .tb-widget-error-msg {
110   - color: red;
  114 + padding: 5px;
111 115 font-size: 16px;
  116 + color: #f00;
112 117 word-wrap: break-word;
113   - padding: 5px;
114 118 }
... ...
... ... @@ -13,9 +13,11 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-datakey-config {
17 18 min-width: 500px !important;
18 19 min-height: 500px !important;
  20 +
19 21 md-content {
20 22 background-color: #fff;
21 23 }
... ...
... ... @@ -13,17 +13,22 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -@import '../../scss/constants';
  16 +@import "../../scss/constants";
17 17
18   -.tb-entity-alias-autocomplete, .tb-timeseries-datakey-autocomplete, .tb-attribute-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
  18 +.tb-entity-alias-autocomplete,
  19 +.tb-timeseries-datakey-autocomplete,
  20 +.tb-attribute-datakey-autocomplete,
  21 +.tb-alarm-datakey-autocomplete {
19 22 .tb-not-found {
20 23 display: block;
21   - line-height: 1.5;
22 24 height: 48px;
  25 + line-height: 1.5;
  26 +
23 27 .tb-no-entries {
24 28 line-height: 48px;
25 29 }
26 30 }
  31 +
27 32 li {
28 33 height: auto !important;
29 34 white-space: normal !important;
... ... @@ -32,13 +37,14 @@
32 37
33 38 tb-datasource-entity {
34 39 @media (min-width: $layout-breakpoint-sm) {
35   - padding-left: 4px;
36 40 padding-right: 4px;
  41 + padding-left: 4px;
37 42 }
  43 +
38 44 tb-entity-alias-select {
39 45 @media (min-width: $layout-breakpoint-sm) {
40 46 width: 200px;
41 47 max-width: 200px;
42 48 }
43 49 }
44   -}
\ No newline at end of file
  50 +}
... ...
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -@import '../../scss/constants';
  16 +@import "../../scss/constants";
17 17
18 18 .tb-datasource-func {
19 19 @media (min-width: $layout-breakpoint-sm) {
... ... @@ -22,19 +22,22 @@
22 22
23 23 md-input-container.tb-datasource-name {
24 24 .md-errors-spacer {
25   - display: none;
  25 + display: none;
26 26 }
27 27 }
28 28
29   - .tb-func-datakey-autocomplete, .tb-alarm-datakey-autocomplete {
  29 + .tb-func-datakey-autocomplete,
  30 + .tb-alarm-datakey-autocomplete {
30 31 .tb-not-found {
31 32 display: block;
32   - line-height: 1.5;
33 33 height: 48px;
  34 + line-height: 1.5;
  35 +
34 36 .tb-no-entries {
35 37 line-height: 48px;
36 38 }
37 39 }
  40 +
38 41 li {
39 42 height: auto !important;
40 43 white-space: normal !important;
... ...
... ... @@ -13,39 +13,43 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-datasource {
17   - #entity-autocomplete {
18   - height: 30px;
19   - margin-top: 18px;
20   - md-autocomplete-wrap {
21   - height: 30px;
22   - }
23   - input, input:not(.md-input) {
24   - height: 30px;
25   - }
26   - }
27   - #datasourceType {
  18 + #entity-autocomplete {
  19 + height: 30px;
  20 + margin-top: 18px;
  21 +
  22 + md-autocomplete-wrap {
  23 + height: 30px;
  24 + }
  25 +
  26 + input,
  27 + input:not(.md-input) {
  28 + height: 30px;
  29 + }
28 30 }
29 31 }
30 32
31 33 @mixin tb-checkered-bg() {
32 34 background-color: #fff;
33   - background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
34   - linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
35   - background-size: 8px 8px;
  35 + background-image:
  36 + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
  37 + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
36 38 background-position: 0 0, 4px 4px;
  39 + background-size: 8px 8px;
37 40 }
38 41
39 42 .tb-color-preview {
40   - content: '';
41   - min-width: 24px;
  43 + position: relative;
42 44 width: 24px;
  45 + min-width: 24px;
43 46 height: 24px;
  47 + overflow: hidden;
  48 + content: "";
44 49 border: 2px solid #fff;
45 50 border-radius: 50%;
46 51 box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
47   - position: relative;
48   - overflow: hidden;
  52 +
49 53 @include tb-checkered-bg();
50 54
51 55 .tb-color-result {
... ... @@ -60,6 +64,7 @@
60 64 text-overflow: ellipsis;
61 65 white-space: nowrap;
62 66 }
  67 +
63 68 .tb-chip-separator {
64 69 white-space: pre;
65 70 }
... ...
... ... @@ -13,13 +13,16 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 tb-datetime-period {
17 18 md-input-container {
18   - margin-bottom: 0px;
  19 + margin-bottom: 0;
  20 +
19 21 .md-errors-spacer {
20   - min-height: 0px;
  22 + min-height: 0;
21 23 }
22 24 }
  25 +
23 26 mdp-date-picker {
24 27 .md-input {
25 28 width: 150px !important;
... ...
... ... @@ -13,47 +13,51 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -@import '../../scss/constants';
  16 +@import "../../scss/constants";
17 17
18 18 .tb-details-title {
19   - font-size: 1.000rem;
20   - @media (min-width: $layout-breakpoint-gt-sm) {
21   - font-size: 1.600rem;
22   - }
23   - font-weight: 400;
24   - text-transform: uppercase;
  19 + width: inherit;
25 20 margin: 20px 8px 0 0;
26 21 overflow: hidden;
  22 + font-size: 1rem;
  23 + font-weight: 400;
27 24 text-overflow: ellipsis;
  25 + text-transform: uppercase;
28 26 white-space: nowrap;
29   - width: inherit;
  27 +
  28 + @media (min-width: $layout-breakpoint-gt-sm) {
  29 + font-size: 1.6rem;
  30 + }
30 31 }
31 32
32 33 .tb-details-subtitle {
33   - font-size: 1.000rem;
  34 + width: inherit;
34 35 margin: 10px 0;
35   - opacity: 0.8;
36 36 overflow: hidden;
  37 + font-size: 1rem;
37 38 text-overflow: ellipsis;
38 39 white-space: nowrap;
39   - width: inherit;
  40 + opacity: .8;
40 41 }
41 42
42 43 md-sidenav.tb-sidenav-details {
43 44 .md-toolbar-tools {
44   - min-height: 100px;
45   - max-height: 120px;
46   - height: 100%;
  45 + height: 100%;
  46 + min-height: 100px;
  47 + max-height: 120px;
47 48 }
  49 + z-index: 59 !important;
48 50 width: 100% !important;
49 51 max-width: 100% !important;
50   - z-index: 59 !important;
  52 +
51 53 @media (min-width: $layout-breakpoint-gt-sm) {
52 54 width: 80% !important;
53 55 }
  56 +
54 57 @media (min-width: $layout-breakpoint-gt-md) {
55 58 width: 65% !important;
56 59 }
  60 +
57 61 tb-dashboard {
58 62 md-content {
59 63 background-color: $primary-hue-3;
... ...
... ... @@ -13,15 +13,18 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-entity-alias-autocomplete {
17 18 .tb-not-found {
18 19 display: block;
19   - line-height: 1.5;
20 20 height: 48px;
  21 + line-height: 1.5;
  22 +
21 23 .tb-no-entries {
22 24 line-height: 48px;
23 25 }
24 26 }
  27 +
25 28 li {
26 29 height: auto !important;
27 30 white-space: normal !important;
... ...
... ... @@ -16,37 +16,38 @@
16 16 @import "../../scss/constants";
17 17
18 18 .tb-fullscreen {
19   - section.header-buttons {
20   - top: 25px;
21   - }
22   -}
23   -
24   -.tb-fullscreen {
25   - width: 100% !important;
26   - height: 100% !important;
27 19 position: fixed !important;
28 20 top: 0;
29 21 left: 0;
  22 + width: 100% !important;
  23 + height: 100% !important;
  24 +
  25 + section.header-buttons {
  26 + top: 25px;
  27 + }
30 28 }
31 29
32 30 .tb-fullscreen-parent {
33   - background-color: $gray;
34   - width: 100%;
35   - height: 100%;
36 31 position: fixed;
37 32 top: 0;
38 33 left: 0;
  34 + width: 100%;
  35 + height: 100%;
  36 + background-color: $gray;
39 37 }
40 38
41   -.md-button.tb-fullscreen-button-style, .tb-fullscreen-button-style {
  39 +.md-button.tb-fullscreen-button-style,
  40 +.tb-fullscreen-button-style {
42 41 background: #ccc;
43   - opacity: 0.85;
  42 + opacity: .85;
  43 +
44 44 ng-md-icon {
45 45 color: #666;
46 46 }
47 47 }
48 48
49   -.md-button.tb-fullscreen-button-pos, .tb-fullscreen-button-pos {
  49 +.md-button.tb-fullscreen-button-pos,
  50 +.tb-fullscreen-button-pos {
50 51 position: absolute;
51 52 top: 10px;
52 53 right: 10px;
... ...
... ... @@ -21,15 +21,19 @@
21 21
22 22 .tb-card-item {
23 23 @include transition(all .2s ease-in-out);
  24 +
24 25 md-card-content {
25   - padding-top: 0px;
26 26 max-height: 53px;
  27 + padding-top: 0;
27 28 }
  29 +
28 30 md-card-title {
29 31 width: 100%;
  32 +
30 33 md-card-title-text {
31   - max-height: 32px;
32 34 min-width: 50%;
  35 + max-height: 32px;
  36 +
33 37 .md-headline {
34 38 overflow: hidden;
35 39 text-overflow: ellipsis;
... ... @@ -40,14 +44,15 @@
40 44 }
41 45
42 46 .tb-current-item {
43   - opacity: 0.5;
  47 + opacity: .5;
  48 +
44 49 @include transform(scale(1.05));
45 50 }
46 51
47 52 #tb-vertical-container {
48 53 position: absolute;
49 54 top: 0;
50   - left: 0;
51 55 right: 0;
52 56 bottom: 0;
  57 + left: 0;
53 58 }
... ...
... ... @@ -13,11 +13,14 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 tb-js-func {
17 18 position: relative;
  19 +
18 20 .tb-disabled {
19   - color: rgba(0,0,0,0.38);
  21 + color: rgba(0, 0, 0, .38);
20 22 }
  23 +
21 24 .fill-height {
22 25 height: 100%;
23 26 }
... ... @@ -25,25 +28,27 @@ tb-js-func {
25 28
26 29 .tb-js-func-toolbar {
27 30 .md-button.tidy {
28   - color: #7B7B7B;
29 31 min-width: 32px;
30 32 min-height: 15px;
31   - line-height: 15px;
32   - font-size: 0.800rem;
33   - margin: 0 5px 0 0;
34 33 padding: 4px;
35   - background: rgba(220, 220, 220, 0.35);
  34 + margin: 0 5px 0 0;
  35 + font-size: .8rem;
  36 + line-height: 15px;
  37 + color: #7b7b7b;
  38 + background: rgba(220, 220, 220, .35);
36 39 }
37 40 }
38 41
39 42 .tb-js-func-panel {
40   - margin-left: 15px;
41   - border: 1px solid #C0C0C0;
42 43 height: calc(100% - 80px);
  44 + margin-left: 15px;
  45 + border: 1px solid #c0c0c0;
  46 +
43 47 #tb-javascript-input {
44   - min-width: 200px;
45 48 width: 100%;
  49 + min-width: 200px;
46 50 height: 100%;
  51 +
47 52 &:not(.fill-height) {
48 53 min-height: 200px;
49 54 }
... ...
... ... @@ -13,8 +13,10 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 tb-json-content {
17 18 position: relative;
  19 +
18 20 .fill-height {
19 21 height: 100%;
20 22 }
... ... @@ -22,25 +24,27 @@ tb-json-content {
22 24
23 25 .tb-json-content-toolbar {
24 26 .md-button.tidy {
25   - color: #7B7B7B;
26 27 min-width: 32px;
27 28 min-height: 15px;
28   - line-height: 15px;
29   - font-size: 0.800rem;
30   - margin: 0 5px 0 0;
31 29 padding: 4px;
32   - background: rgba(220, 220, 220, 0.35);
  30 + margin: 0 5px 0 0;
  31 + font-size: .8rem;
  32 + line-height: 15px;
  33 + color: #7b7b7b;
  34 + background: rgba(220, 220, 220, .35);
33 35 }
34 36 }
35 37
36 38 .tb-json-content-panel {
37   - margin-left: 15px;
38   - border: 1px solid #C0C0C0;
39 39 height: 100%;
  40 + margin-left: 15px;
  41 + border: 1px solid #c0c0c0;
  42 +
40 43 #tb-json-input {
41   - min-width: 200px;
42 44 width: 100%;
  45 + min-width: 200px;
43 46 height: 100%;
  47 +
44 48 &:not(.fill-height) {
45 49 min-height: 200px;
46 50 }
... ...
... ... @@ -13,7 +13,8 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 tb-json-form {
17   - overflow: auto;
18 18 padding-bottom: 14px !important;
19   -}
\ No newline at end of file
  19 + overflow: auto;
  20 +}
... ...
... ... @@ -13,21 +13,25 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 tb-json-object-edit {
17 18 position: relative;
  19 +
18 20 .fill-height {
19 21 height: 100%;
20 22 }
21 23 }
22 24
23 25 .tb-json-object-panel {
24   - margin-left: 15px;
25   - border: 1px solid #C0C0C0;
26 26 height: 100%;
  27 + margin-left: 15px;
  28 + border: 1px solid #c0c0c0;
  29 +
27 30 #tb-json-input {
28   - min-width: 200px;
29 31 width: 100%;
  32 + min-width: 200px;
30 33 height: 100%;
  34 +
31 35 &:not(.fill-height) {
32 36 min-height: 200px;
33 37 }
... ...
... ... @@ -13,14 +13,16 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-kv-map {
17 18 span.no-data-found {
18 19 position: relative;
  20 + display: flex;
19 21 height: 40px;
20 22 text-transform: uppercase;
21   - display: flex;
  23 +
22 24 &.disabled {
23   - color: rgba(0,0,0,0.38);
  25 + color: rgba(0, 0, 0, .38);
24 26 }
25 27 }
26   -}
\ No newline at end of file
  28 +}
... ...
... ... @@ -13,6 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .md-panel {
17 18 &.tb-legend-config-panel {
18 19 position: absolute;
... ... @@ -20,21 +21,26 @@
20 21 }
21 22
22 23 .tb-legend-config-panel {
23   - max-height: 220px;
24 24 min-width: 220px;
25   - background: white;
26   - border-radius: 4px;
27   - box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
28   - 0 13px 19px 2px rgba(0, 0, 0, 0.14),
29   - 0 5px 24px 4px rgba(0, 0, 0, 0.12);
  25 + max-height: 220px;
30 26 overflow: hidden;
31   - form, fieldset {
  27 + background: #fff;
  28 + border-radius: 4px;
  29 + box-shadow:
  30 + 0 7px 8px -4px rgba(0, 0, 0, .2),
  31 + 0 13px 19px 2px rgba(0, 0, 0, .14),
  32 + 0 5px 24px 4px rgba(0, 0, 0, .12);
  33 +
  34 + form,
  35 + fieldset {
32 36 height: 100%;
33 37 }
  38 +
34 39 md-content {
35   - background-color: #fff;
36 40 overflow: hidden;
  41 + background-color: #fff;
37 42 }
  43 +
38 44 .md-padding {
39 45 padding: 0 16px;
40 46 }
... ...
... ... @@ -13,39 +13,49 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 table.tb-legend {
17 18 width: 100%;
18 19 font-size: 12px;
19   - .tb-legend-header, .tb-legend-value {
20   - text-align: right;
  20 +
  21 + .tb-legend-header,
  22 + .tb-legend-value {
  23 + text-align: right;
21 24 }
  25 +
22 26 .tb-legend-header {
23 27 th {
24   - color: rgb(255,110,64);
25   - white-space: nowrap;
26 28 padding: 0 10px 1px 0;
  29 + color: rgb(255, 110, 64);
  30 + white-space: nowrap;
27 31 }
28 32 }
  33 +
29 34 .tb-legend-keys {
30   - td.tb-legend-label, td.tb-legend-value {
31   - white-space: nowrap;
  35 + td.tb-legend-label,
  36 + td.tb-legend-value {
32 37 padding: 2px 10px;
  38 + white-space: nowrap;
33 39 }
  40 +
34 41 .tb-legend-line {
  42 + display: inline-block;
35 43 width: 15px;
36 44 height: 3px;
37   - display: inline-block;
38 45 vertical-align: middle;
39 46 }
  47 +
40 48 .tb-legend-label {
41 49 text-align: left;
42 50 outline: none;
  51 +
43 52 &.tb-horizontal {
44 53 width: 95%;
45 54 }
  55 +
46 56 &.tb-hidden-label {
47 57 text-decoration: line-through;
48   - opacity: 0.6;
  58 + opacity: .6;
49 59 }
50 60 }
51 61 }
... ...
... ... @@ -13,14 +13,16 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-material-icon-select {
17   - md-icon {
18   - padding: 4px;
19   - margin: 8px 4px 4px;
20   - cursor: pointer;
21   - border: solid 1px rgba(0,0,0,0.27);
22   - }
23   - md-input-container {
24   - margin-bottom: 0px;
25   - }
26   -}
\ No newline at end of file
  18 + md-icon {
  19 + padding: 4px;
  20 + margin: 8px 4px 4px;
  21 + cursor: pointer;
  22 + border: solid 1px rgba(0, 0, 0, .27);
  23 + }
  24 +
  25 + md-input-container {
  26 + margin-bottom: 0;
  27 + }
  28 +}
... ...
... ... @@ -13,18 +13,20 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-material-icons-dialog {
17 18 button.md-icon-button.tb-select-icon-button {
18   - border: solid 1px orange;
19   - border-radius: 0%;
20   - padding: 16px;
21   - height: 56px;
22 19 width: 56px;
  20 + height: 56px;
  21 + padding: 16px;
23 22 margin: 10px;
  23 + border: solid 1px #ffa500;
  24 + border-radius: 0%;
24 25 }
  26 +
25 27 .tb-icons-load {
26 28 top: 64px;
27   - background: rgba(255,255,255,0.75);
28 29 z-index: 3;
  30 + background: rgba(255, 255, 255, .75);
29 31 }
30   -}
\ No newline at end of file
  32 +}
... ...
... ... @@ -24,9 +24,11 @@
24 24 }
25 25
26 26 .tb-menu-toggle-list {
27   - overflow: hidden;
28 27 position: relative;
29 28 z-index: 1;
30   - @include transition(0.75s cubic-bezier(0.35, 0, 0.25, 1));
  29 + overflow: hidden;
  30 +
  31 + @include transition(.75s cubic-bezier(.35, 0, .25, 1));
  32 +
31 33 @include transition-property(height);
32 34 }
... ...
... ... @@ -13,30 +13,35 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .json-form-ace-editor {
17   - position: relative;
18   - border: 1px solid #C0C0C0;
19   - height: 100%;
20   - .title-panel {
21   - position: absolute;
22   - font-size: 0.800rem;
23   - font-weight: 500;
24   - top: 10px;
25   - right: 20px;
26   - z-index: 5;
27   - label {
28   - color: #00acc1;
29   - background: rgba(220, 220, 220, 0.35);
30   - border-radius: 5px;
31   - padding: 4px;
32   - text-transform: uppercase;
33   - }
34   - button.tidy-button {
35   - background: rgba(220, 220, 220, 0.35) !important;
36   - span {
37   - padding: 0px !important;
38   - font-size: 12px !important;
39   - }
40   - }
  18 + position: relative;
  19 + height: 100%;
  20 + border: 1px solid #c0c0c0;
  21 +
  22 + .title-panel {
  23 + position: absolute;
  24 + top: 10px;
  25 + right: 20px;
  26 + z-index: 5;
  27 + font-size: .8rem;
  28 + font-weight: 500;
  29 +
  30 + label {
  31 + padding: 4px;
  32 + color: #00acc1;
  33 + text-transform: uppercase;
  34 + background: rgba(220, 220, 220, .35);
  35 + border-radius: 5px;
41 36 }
42   -}
\ No newline at end of file
  37 +
  38 + button.tidy-button {
  39 + background: rgba(220, 220, 220, .35) !important;
  40 +
  41 + span {
  42 + padding: 0 !important;
  43 + font-size: 12px !important;
  44 + }
  45 + }
  46 + }
  47 +}
... ...
... ... @@ -15,21 +15,23 @@
15 15 */
16 16 @mixin tb-checkered-bg() {
17 17 background-color: #fff;
18   - background-image: linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
19   - linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
20   - background-size: 8px 8px;
  18 + background-image:
  19 + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd),
  20 + linear-gradient(45deg, #ddd 25%, transparent 25%, transparent 75%, #ddd 75%, #ddd);
21 21 background-position: 0 0, 4px 4px;
  22 + background-size: 8px 8px;
22 23 }
23 24
24 25 .tb-color-preview {
25   - content: '';
  26 + position: relative;
26 27 width: 24px;
27 28 height: 24px;
  29 + overflow: hidden;
  30 + content: "";
28 31 border: 2px solid #fff;
29 32 border-radius: 50%;
30 33 box-shadow: 0 3px 1px -2px rgba(0, 0, 0, .14), 0 2px 2px 0 rgba(0, 0, 0, .098), 0 1px 5px 0 rgba(0, 0, 0, .084);
31   - position: relative;
32   - overflow: hidden;
  34 +
33 35 @include tb-checkered-bg();
34 36
35 37 .tb-color-result {
... ...
... ... @@ -13,66 +13,69 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -$previewSize: 100px;
  16 +$previewSize: 100px !default;
17 17
18 18 .tb-image-select-container {
19 19 position: relative;
20   - height: $previewSize;
21 20 width: 100%;
  21 + height: $previewSize;
22 22 }
23 23
24 24 .tb-image-preview {
25   - max-width: $previewSize;
26   - max-height: $previewSize;
27   - width: 100%;
28   - height: 100%;
  25 + width: 100%;
  26 + max-width: $previewSize;
  27 + height: 100%;
  28 + max-height: $previewSize;
29 29 }
30 30
31 31 .tb-image-preview-container {
32   - position: relative;
33   - width: $previewSize;
34   - height: $previewSize;
35   - margin-right: 12px;
36   - border: solid 1px;
37   - vertical-align: top;
38   - float: left;
39   - div {
40   - width: 100%;
41   - font-size: 18px;
42   - text-align: center;
43   - position: absolute;
44   - top: 50%;
45   - left: 50%;
46   - transform: translate(-50%,-50%);
47   - }
  32 + position: relative;
  33 + float: left;
  34 + width: $previewSize;
  35 + height: $previewSize;
  36 + margin-right: 12px;
  37 + vertical-align: top;
  38 + border: solid 1px;
  39 +
  40 + div {
  41 + position: absolute;
  42 + top: 50%;
  43 + left: 50%;
  44 + width: 100%;
  45 + font-size: 18px;
  46 + text-align: center;
  47 + transform: translate(-50%, -50%);
  48 + }
48 49 }
49 50
50 51 .tb-dropzone {
51   - position: relative;
52   - border: dashed 2px;
53   - height: $previewSize;
54   - vertical-align: top;
55   - padding: 0 8px;
56   - overflow: hidden;
57   - div {
58   - width: 100%;
59   - font-size: 24px;
60   - text-align: center;
61   - position: absolute;
62   - top: 50%;
63   - left: 50%;
64   - transform: translate(-50%,-50%);
65   - }
  52 + position: relative;
  53 + height: $previewSize;
  54 + padding: 0 8px;
  55 + overflow: hidden;
  56 + vertical-align: top;
  57 + border: dashed 2px;
  58 +
  59 + div {
  60 + position: absolute;
  61 + top: 50%;
  62 + left: 50%;
  63 + width: 100%;
  64 + font-size: 24px;
  65 + text-align: center;
  66 + transform: translate(-50%, -50%);
  67 + }
66 68 }
67 69
68 70 .tb-image-clear-container {
69   - width: 48px;
70   - height: $previewSize;
71   - position: relative;
72   - float: right;
  71 + position: relative;
  72 + float: right;
  73 + width: 48px;
  74 + height: $previewSize;
73 75 }
  76 +
74 77 .tb-image-clear-btn {
75   - position: absolute !important;
76   - top: 50%;
77   - transform: translate(0%,-50%) !important;
  78 + position: absolute !important;
  79 + top: 50%;
  80 + transform: translate(0%, -50%) !important;
78 81 }
... ...
... ... @@ -15,87 +15,92 @@
15 15 */
16 16 @import "~compass-sass-mixins/lib/compass";
17 17
18   -$swift-ease-out-duration: 0.4s !default;
19   -$swift-ease-out-timing-function: cubic-bezier(0.25, 0.8, 0.25, 1) !default;
  18 +$swift-ease-out-duration: .4s !default;
  19 +$swift-ease-out-timing-function: cubic-bezier(.25, .8, .25, 1) !default;
20 20
21 21 $input-label-float-offset: 6px !default;
22   -$input-label-float-scale: 0.75 !default;
  22 +$input-label-float-scale: .75 !default;
23 23
24 24 .json-form-error {
25   - position: relative;
26   - bottom: -5px;
27   - font-size: 12px;
28   - line-height: 12px;
29   - color: rgb(244, 67, 54);
30   - @include transition(all 450ms cubic-bezier(0.23, 1, 0.32, 1) 0ms);
  25 + position: relative;
  26 + bottom: -5px;
  27 + font-size: 12px;
  28 + line-height: 12px;
  29 + color: rgb(244, 67, 54);
  30 +
  31 + @include transition(all 450ms cubic-bezier(.23, 1, .32, 1) 0ms);
31 32 }
32 33
33 34 .tb-container {
34   - position: relative;
35   - margin-top: 32px;
36   - padding: 10px 0;
  35 + position: relative;
  36 + padding: 10px 0;
  37 + margin-top: 32px;
37 38 }
38 39
39 40 .tb-field {
40   - &.tb-required {
41   - label:after {
42   - content: ' *';
43   - font-size: 13px;
44   - vertical-align: top;
45   - color: rgba(0,0,0,0.54);
46   - }
  41 + &.tb-required {
  42 + label::after {
  43 + font-size: 13px;
  44 + color: rgba(0, 0, 0, .54);
  45 + vertical-align: top;
  46 + content: " *";
47 47 }
48   - &.tb-focused:not(.tb-readonly) {
49   - label:after {
50   - color: rgb(221,44,0);
51   - }
  48 + }
  49 +
  50 + &.tb-focused:not(.tb-readonly) {
  51 + label::after {
  52 + color: rgb(221, 44, 0);
52 53 }
  54 + }
53 55 }
54 56
55 57 .tb-date-field {
56   - &.tb-required {
57   - div>div:first-child:after {
58   - content: ' *';
59   - font-size: 13px;
60   - vertical-align: top;
61   - color: rgba(0,0,0,0.54);
62   - }
  58 + &.tb-required {
  59 + div > div:first-child::after {
  60 + font-size: 13px;
  61 + color: rgba(0, 0, 0, .54);
  62 + vertical-align: top;
  63 + content: " *";
63 64 }
64   - &.tb-focused:not(.tb-readonly) {
65   - div>div:first-child:after {
66   - color: rgb(221,44,0);
67   - }
  65 + }
  66 +
  67 + &.tb-focused:not(.tb-readonly) {
  68 + div > div:first-child::after {
  69 + color: rgb(221, 44, 0);
68 70 }
  71 + }
69 72 }
70 73
71 74 label.tb-label {
72   - color: rgba(0,0,0,0.54);
73   - -webkit-font-smoothing: antialiased;
74   - position: absolute;
75   - bottom: 100%;
76   - left: 0;
77   - right: auto;
78   - @include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
79   - @include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
80   - width $swift-ease-out-timing-function $swift-ease-out-duration);
81   - transform-origin: left top;
82   -
83   - &.tb-focused {
84   - color: rgb(96,125,139);
85   - }
  75 + position: absolute;
  76 + right: auto;
  77 + bottom: 100%;
  78 + left: 0;
  79 + color: rgba(0, 0, 0, .54);
  80 + transform-origin: left top;
  81 + -webkit-font-smoothing: antialiased;
86 82
87   - &.tb-required:after {
88   - content: ' *';
89   - font-size: 13px;
90   - vertical-align: top;
91   - color: rgba(0,0,0,0.54);
92   - }
  83 + @include transform(translate3d(0, $input-label-float-offset, 0) scale($input-label-float-scale));
93 84
94   - &.tb-focused:not(.tb-readonly):after {
95   - color: rgb(221,44,0);
96   - }
  85 + @include transition(transform $swift-ease-out-timing-function $swift-ease-out-duration,
  86 + width $swift-ease-out-timing-function $swift-ease-out-duration);
  87 +
  88 + &.tb-focused {
  89 + color: rgb(96, 125, 139);
  90 + }
  91 +
  92 + &.tb-required::after {
  93 + font-size: 13px;
  94 + color: rgba(0, 0, 0, .54);
  95 + vertical-align: top;
  96 + content: " *";
  97 + }
  98 +
  99 + &.tb-focused:not(.tb-readonly)::after {
  100 + color: rgb(221, 44, 0);
  101 + }
97 102 }
98 103
99 104 .tb-head-label {
100   - color: rgba(0,0,0,0.54);
  105 + color: rgba(0, 0, 0, .54);
101 106 }
... ...
... ... @@ -13,16 +13,19 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-related-entity-autocomplete {
17 18 .tb-not-found {
18 19 display: block;
19   - line-height: 1.5;
20 20 height: 48px;
  21 + line-height: 1.5;
21 22 }
  23 +
22 24 .tb-entity-item {
23 25 display: block;
24 26 height: 48px;
25 27 }
  28 +
26 29 li {
27 30 height: auto !important;
28 31 white-space: normal !important;
... ...
... ... @@ -16,21 +16,22 @@
16 16 @import "~compass-sass-mixins/lib/compass";
17 17
18 18 .tb-side-menu .md-button.tb-active {
19   - background-color: rgba(255, 255, 255, 0.15);
20 19 font-weight: 500;
  20 + background-color: rgba(255, 255, 255, .15);
21 21 }
22 22
23   -.tb-side-menu, .tb-side-menu ul {
24   - list-style: none;
  23 +.tb-side-menu,
  24 +.tb-side-menu ul {
25 25 padding: 0;
26 26 margin-top: 0;
  27 + list-style: none;
27 28 }
28 29
29 30 .tb-side-menu .tb-menu-toggle-list a.md-button {
30 31 padding: 0 16px 0 32px;
  32 + font-weight: 500;
31 33 text-transform: none;
32 34 text-rendering: optimizeLegibility;
33   - font-weight: 500;
34 35 }
35 36
36 37 .tb-side-menu .tb-menu-toggle-list .md-button {
... ... @@ -43,36 +44,38 @@
43 44 }
44 45
45 46 .tb-side-menu > li {
46   - border-bottom: 1px solid rgba(0, 0, 0, 0.12);
  47 + border-bottom: 1px solid rgba(0, 0, 0, .12);
47 48 }
48 49
49 50 .tb-side-menu .md-button-toggle .md-toggle-icon {
50   - background-size: 100% auto;
51 51 display: inline-block;
52   - margin: auto 0 auto auto;
53 52 width: 15px;
  53 + margin: auto 0 auto auto;
  54 + background-size: 100% auto;
  55 +
54 56 @include transition(transform .3s, ease-in-out);
55 57 }
56 58
57 59 .tb-side-menu .md-button {
58 60 display: flex;
59   - border-radius: 0;
60   - color: inherit;
61   - cursor: pointer;
62   - line-height: 40px;
63   - margin: 0;
  61 + width: 100%;
64 62 max-height: 40px;
  63 + padding: 0 16px;
  64 + margin: 0;
65 65 overflow: hidden;
66   - padding: 0px 16px;
  66 + line-height: 40px;
  67 + color: inherit;
67 68 text-align: left;
68 69 text-decoration: none;
69   - white-space: nowrap;
70 70 text-overflow: ellipsis;
71   - width: 100%;
  71 + white-space: nowrap;
  72 + cursor: pointer;
  73 + border-radius: 0;
  74 +
72 75 span {
73 76 overflow: hidden;
74   - white-space: nowrap;
75 77 text-overflow: ellipsis;
  78 + white-space: nowrap;
76 79 }
77 80 }
78 81
... ... @@ -83,10 +86,10 @@
83 86
84 87 .tb-side-menu ng-md-icon {
85 88 margin-right: 8px;
86   - margin-left: 0px;
  89 + margin-left: 0;
87 90 }
88 91
89 92 .tb-side-menu md-icon {
90 93 margin-right: 8px;
91   - margin-left: 0px;
  94 + margin-left: 0;
92 95 }
... ...
... ... @@ -13,25 +13,32 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 tb-timeinterval {
17 18 min-width: 355px;
  19 +
18 20 md-input-container {
19   - margin-bottom: 0px;
  21 + margin-bottom: 0;
  22 +
20 23 .md-errors-spacer {
21   - min-height: 0px;
  24 + min-height: 0;
22 25 }
23 26 }
  27 +
24 28 mdp-date-picker {
25 29 .md-input {
26 30 width: 150px;
27 31 }
28 32 }
  33 +
29 34 .md-input {
30 35 width: 70px !important;
31 36 }
  37 +
32 38 .advanced-switch {
33 39 margin-top: 0;
34 40 }
  41 +
35 42 .advanced-label {
36 43 margin: 5px 0;
37 44 }
... ...
... ... @@ -13,6 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .md-panel {
17 18 &.tb-timewindow-panel {
18 19 position: absolute;
... ... @@ -20,38 +21,48 @@
20 21 }
21 22
22 23 .tb-timewindow-panel {
23   - max-height: 440px;
24 24 min-width: 417px;
25   - background: white;
26   - border-radius: 4px;
27   - box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
28   - 0 13px 19px 2px rgba(0, 0, 0, 0.14),
29   - 0 5px 24px 4px rgba(0, 0, 0, 0.12);
  25 + max-height: 440px;
30 26 overflow: hidden;
31   - form, fieldset {
  27 + background: #fff;
  28 + border-radius: 4px;
  29 + box-shadow:
  30 + 0 7px 8px -4px rgba(0, 0, 0, .2),
  31 + 0 13px 19px 2px rgba(0, 0, 0, .14),
  32 + 0 5px 24px 4px rgba(0, 0, 0, .12);
  33 +
  34 + form,
  35 + fieldset {
32 36 height: 100%;
33 37 }
  38 +
34 39 md-content {
35   - background-color: #fff;
36 40 overflow: hidden;
  41 + background-color: #fff;
37 42 }
  43 +
38 44 .md-padding {
39 45 padding: 0 16px;
40 46 }
  47 +
41 48 .md-radio-interactive {
42   - md-select, md-switch {
  49 + md-select,
  50 + md-switch {
43 51 pointer-events: all;
44 52 }
45 53 }
  54 +
46 55 md-radio-button {
47 56 .md-label {
48 57 width: 100%;
49 58 }
  59 +
50 60 tb-timeinterval {
51 61 width: 355px;
  62 +
52 63 .advanced-switch {
53   - min-height: 30px;
54 64 max-width: 44px;
  65 + min-height: 30px;
55 66 }
56 67 }
57 68 }
... ... @@ -59,15 +70,17 @@
59 70
60 71 tb-timewindow {
61 72 min-width: 52px;
  73 +
62 74 section.tb-timewindow {
63 75 min-height: 32px;
64 76 padding: 0 6px;
  77 +
65 78 span {
66   - pointer-events: all;
67   - cursor: pointer;
68 79 overflow: hidden;
69 80 text-overflow: ellipsis;
70 81 white-space: nowrap;
  82 + pointer-events: all;
  83 + cursor: pointer;
71 84 }
72 85 }
73 86 }
... ...
... ... @@ -13,18 +13,19 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-manage-widget-actions {
17 18 table.md-table {
18 19 tbody {
19 20 tr {
20 21 td {
21 22 &.tb-action-cell {
  23 + width: 100px;
  24 + min-width: 100px;
  25 + max-width: 100px;
22 26 overflow: hidden;
23 27 text-overflow: ellipsis;
24 28 white-space: nowrap;
25   - min-width: 100px;
26   - max-width: 100px;
27   - width: 100px;
28 29 }
29 30 }
30 31 }
... ...
... ... @@ -13,11 +13,13 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-widget-config {
17 18 md-tab-content.md-active > div {
18 19 height: 100%;
19 20 }
  21 +
20 22 .tb-advanced-widget-config {
21 23 height: 100%;
22 24 }
23   -}
\ No newline at end of file
  25 +}
... ...
... ... @@ -13,18 +13,21 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-widget {
17 18 .tb-widget-error {
18 19 display: flex;
19   - justify-content: center;
20 20 align-items: center;
21   - background: rgba(255,255,255,0.5);
  21 + justify-content: center;
  22 + background: rgba(255, 255, 255, .5);
  23 +
22 24 span {
23   - color: red;
  25 + color: #f00;
24 26 }
25 27 }
  28 +
26 29 .tb-widget-loading {
27   - background: rgba(255,255,255,0.15);
28 30 z-index: 3;
  31 + background: rgba(255, 255, 255, .15);
29 32 }
30 33 }
... ...
... ... @@ -17,9 +17,10 @@
17 17
18 18 tb-widgets-bundle-select {
19 19 md-select {
20   - margin: 0;
21 20 padding: 5px 20px;
  21 + margin: 0;
22 22 }
  23 +
23 24 .tb-bundle-item {
24 25 height: 24px;
25 26 line-height: 24px;
... ... @@ -33,24 +34,29 @@ tb-widgets-bundle-select {
33 34 }
34 35 }
35 36
36   -tb-widgets-bundle-select, .tb-widgets-bundle-select {
  37 +tb-widgets-bundle-select,
  38 +.tb-widgets-bundle-select {
37 39 .md-text {
38 40 display: block;
39 41 width: 100%;
40 42 }
  43 +
41 44 .tb-bundle-item {
42 45 display: inline-block;
43 46 width: 100%;
  47 +
44 48 span {
45 49 display: inline-block;
46 50 vertical-align: middle;
47 51 }
  52 +
48 53 .tb-bundle-system {
49   - font-size: 0.8rem;
50   - opacity: 0.8;
51 54 float: right;
  55 + font-size: .8rem;
  56 + opacity: .8;
52 57 }
53 58 }
  59 +
54 60 md-option {
55 61 height: auto !important;
56 62 white-space: normal !important;
... ... @@ -60,19 +66,23 @@ tb-widgets-bundle-select, .tb-widgets-bundle-select {
60 66 md-toolbar {
61 67 tb-widgets-bundle-select {
62 68 md-select {
63   - background: rgba(255,255,255,0.2);
  69 + background: rgba(255, 255, 255, .2);
  70 +
64 71 .md-select-value {
65   - color: #fff;
66 72 font-size: 1.2rem;
67   - span:first-child:after {
68   - color: #fff;
  73 + color: #fff;
  74 +
  75 + span:first-child::after {
  76 + color: #fff;
69 77 }
70 78 }
  79 +
71 80 .md-select-value.md-select-placeholder {
72 81 color: #fff;
73   - opacity: 0.8;
  82 + opacity: .8;
74 83 }
75 84 }
  85 +
76 86 md-select.ng-invalid.ng-touched {
77 87 .md-select-value {
78 88 color: #fff !important;
... ...
... ... @@ -13,14 +13,15 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-dashboard-assigned-customers {
17 18 display: block;
18 19 display: -webkit-box;
19 20 height: 34px;
  21 + margin-bottom: 4px;
20 22 overflow: hidden;
21 23 text-overflow: ellipsis;
22 24 -webkit-line-clamp: 2;
23 25 -webkit-box-orient: vertical;
24   - margin-bottom: 4px;
25 26 }
26 27
... ...
... ... @@ -13,50 +13,54 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -$previewSize: 100px;
  16 +$previewSize: 100px !default;
17 17
18 18 .tb-image-select-container {
19 19 position: relative;
20   - height: $previewSize;
21 20 width: 100%;
  21 + height: $previewSize;
22 22 }
23 23
24 24 .tb-image-preview {
25   - max-width: $previewSize;
26   - max-height: $previewSize;
27 25 width: auto;
  26 + max-width: $previewSize;
28 27 height: auto;
  28 + max-height: $previewSize;
29 29 }
30 30
31 31 .tb-image-preview-container {
32 32 position: relative;
  33 + float: left;
33 34 width: $previewSize;
34 35 height: $previewSize;
35 36 margin-right: 12px;
36   - border: solid 1px;
37 37 vertical-align: top;
38   - float: left;
  38 + border: solid 1px;
  39 +
39 40 div {
40 41 width: 100%;
41 42 font-size: 18px;
42 43 text-align: center;
43 44 }
44   - div, .tb-image-preview {
  45 +
  46 + div,
  47 + .tb-image-preview {
45 48 position: absolute;
46 49 top: 50%;
47 50 left: 50%;
48   - transform: translate(-50%,-50%);
  51 + transform: translate(-50%, -50%);
49 52 }
50 53 }
51 54
52 55 .tb-image-clear-container {
53   - width: 48px;
54   - height: $previewSize;
55 56 position: relative;
56 57 float: right;
  58 + width: 48px;
  59 + height: $previewSize;
57 60 }
  61 +
58 62 .tb-image-clear-btn {
59 63 position: absolute !important;
60 64 top: 50%;
61   - transform: translate(0%,-50%) !important;
  65 + transform: translate(0%, -50%) !important;
62 66 }
... ...
... ... @@ -14,13 +14,14 @@
14 14 * limitations under the License.
15 15 */
16 16 @import "~compass-sass-mixins/lib/compass";
17   -@import '../../scss/constants';
18 17
19   -$toolbar-height: 50px;
20   -$fullscreen-toolbar-height: 64px;
21   -$mobile-toolbar-height: 80px;
22   -$half-mobile-toolbar-height: 40px;
23   -$mobile-toolbar-height-total: 84px;
  18 +@import "../../scss/constants";
  19 +
  20 +$toolbar-height: 50px !default;
  21 +$fullscreen-toolbar-height: 64px !default;
  22 +$mobile-toolbar-height: 80px !default;
  23 +$half-mobile-toolbar-height: 40px !default;
  24 +$mobile-toolbar-height-total: 84px !default;
24 25
25 26 tb-dashboard-toolbar {
26 27 md-fab-toolbar {
... ... @@ -29,126 +30,156 @@ tb-dashboard-toolbar {
29 30 .md-button {
30 31 &.md-fab {
31 32 opacity: 1;
  33 +
32 34 @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
  35 +
33 36 .md-fab-toolbar-background {
34   - background-color: $primary-default !important;
  37 + background-color: $primary-default !important;
35 38 }
36 39 }
37 40 }
38 41 }
39 42 }
  43 +
40 44 md-fab-trigger {
41 45 .md-button {
42 46 &.md-fab {
43   - line-height: 36px;
44 47 width: 36px;
45 48 height: 36px;
46 49 margin: 4px 0 0 4px;
47   - opacity: 0.5;
  50 + line-height: 36px;
  51 + opacity: .5;
  52 +
48 53 @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
  54 +
49 55 md-icon {
50 56 position: absolute;
51 57 top: 25%;
52   - margin: 0;
53   - line-height: 18px;
54   - height: 18px;
55 58 width: 18px;
56   - min-height: 18px;
57 59 min-width: 18px;
  60 + height: 18px;
  61 + min-height: 18px;
  62 + margin: 0;
  63 + line-height: 18px;
58 64 }
59 65 }
60 66 }
61 67 }
  68 +
62 69 &.is-fullscreen {
63 70 &.md-is-open {
64 71 md-fab-trigger {
65 72 .md-button {
66 73 &.md-fab {
67 74 .md-fab-toolbar-background {
68   - transition-delay: 0ms !important;
69   - transition-duration: 0ms !important;
  75 + transition-delay: 0ms !important;
  76 + transition-duration: 0ms !important;
70 77 }
71 78 }
72 79 }
73 80 }
74 81 }
  82 +
75 83 .md-fab-toolbar-wrapper {
76 84 height: $mobile-toolbar-height-total;
  85 +
77 86 @media (min-width: $layout-breakpoint-sm) {
78 87 height: $fullscreen-toolbar-height;
79 88 }
  89 +
80 90 md-toolbar {
81   - min-height: $mobile-toolbar-height;
82 91 height: $mobile-toolbar-height;
  92 + min-height: $mobile-toolbar-height;
  93 +
83 94 @media (min-width: $layout-breakpoint-sm) {
84   - min-height: $fullscreen-toolbar-height;
85 95 height: $fullscreen-toolbar-height;
  96 + min-height: $fullscreen-toolbar-height;
86 97 }
87 98 }
88 99 }
89 100 }
  101 +
90 102 .md-fab-toolbar-wrapper {
91 103 height: $mobile-toolbar-height-total;
  104 +
92 105 @media (min-width: $layout-breakpoint-sm) {
93 106 height: $toolbar-height;
94 107 }
  108 +
95 109 md-toolbar {
96   - min-height: $mobile-toolbar-height;
97 110 height: $mobile-toolbar-height;
  111 + min-height: $mobile-toolbar-height;
  112 +
98 113 @media (min-width: $layout-breakpoint-sm) {
99   - min-height: $toolbar-height;
100 114 height: $toolbar-height;
  115 + min-height: $toolbar-height;
101 116 }
  117 +
102 118 md-fab-actions {
  119 + margin-top: 0;
103 120 font-size: 16px;
104   - margin-top: 0px;
  121 +
105 122 @media (max-width: $layout-breakpoint-sm) {
106 123 height: $mobile-toolbar-height;
107 124 max-height: $mobile-toolbar-height;
108 125 }
  126 +
109 127 .close-action {
110 128 margin-right: -18px;
111 129 }
  130 +
112 131 .md-fab-action-item {
113 132 width: 100%;
114 133 height: $mobile-toolbar-height;
  134 +
115 135 @media (min-width: $layout-breakpoint-sm) {
116 136 height: 46px;
117 137 }
  138 +
118 139 .tb-dashboard-action-panels {
  140 + flex-direction: column-reverse;
119 141 height: $mobile-toolbar-height;
  142 +
120 143 @media (min-width: $layout-breakpoint-sm) {
121 144 height: 46px;
122 145 }
123   - flex-direction: column-reverse;
  146 +
124 147 @media (min-width: $layout-breakpoint-sm) {
125 148 flex-direction: row-reverse;
126 149 }
  150 +
127 151 .tb-dashboard-action-panel {
128   - min-width: 0px;
  152 + flex-direction: row-reverse;
  153 + min-width: 0;
129 154 height: $half-mobile-toolbar-height;
  155 +
130 156 @media (min-width: $layout-breakpoint-sm) {
131 157 height: 46px;
132 158 }
133   - flex-direction: row-reverse;
  159 +
134 160 div {
135 161 height: $half-mobile-toolbar-height;
  162 +
136 163 @media (min-width: $layout-breakpoint-sm) {
137 164 height: 46px;
138 165 }
139 166 }
  167 +
140 168 md-select {
141 169 pointer-events: all;
142 170 }
  171 +
143 172 tb-states-component {
144 173 pointer-events: all;
145 174 }
  175 +
146 176 button.md-icon-button {
147 177 min-width: 40px;
  178 +
148 179 @media (max-width: $layout-breakpoint-sm) {
149 180 min-width: 28px;
150   - margin: 0px;
151 181 padding: 2px;
  182 + margin: 0;
152 183 }
153 184 }
154 185 }
... ... @@ -158,4 +189,4 @@ tb-dashboard-toolbar {
158 189 }
159 190 }
160 191 }
161   -}
\ No newline at end of file
  192 +}
... ...
... ... @@ -14,11 +14,12 @@
14 14 * limitations under the License.
15 15 */
16 16 @import "~compass-sass-mixins/lib/compass";
17   -@import '../../scss/constants';
18 17
19   -$toolbar-height: 50px;
20   -$fullscreen-toolbar-height: 64px;
21   -$mobile-toolbar-height: 84px;
  18 +@import "../../scss/constants";
  19 +
  20 +$toolbar-height: 50px !default;
  21 +$fullscreen-toolbar-height: 64px !default;
  22 +$mobile-toolbar-height: 84px !default;
22 23
23 24 section.tb-dashboard-title {
24 25 position: absolute;
... ... @@ -27,10 +28,10 @@ section.tb-dashboard-title {
27 28 }
28 29
29 30 input.tb-dashboard-title {
30   - font-size: 2.000rem;
31   - font-weight: 500;
32   - letter-spacing: 0.005em;
33 31 height: 38px;
  32 + font-size: 2rem;
  33 + font-weight: 500;
  34 + letter-spacing: .005em;
34 35 }
35 36
36 37 div.tb-padded {
... ... @@ -50,9 +51,11 @@ tb-details-sidenav.tb-widget-details-sidenav {
50 51 @media (min-width: $layout-breakpoint-sm) {
51 52 width: 85% !important;
52 53 }
  54 +
53 55 @media (min-width: $layout-breakpoint-md) {
54 56 width: 75% !important;
55 57 }
  58 +
56 59 @media (min-width: $layout-breakpoint-lg) {
57 60 width: 60% !important;
58 61 }
... ... @@ -65,47 +68,55 @@ tb-details-sidenav.tb-widget-details-sidenav {
65 68
66 69 section.tb-dashboard-toolbar {
67 70 position: absolute;
68   - top: 0px;
69   - left: 0px;
  71 + top: 0;
  72 + left: 0;
70 73 z-index: 13;
71 74 pointer-events: none;
  75 +
72 76 &.tb-dashboard-toolbar-opened {
73   - right: 0px;
74   - // @include transition(right .3s cubic-bezier(.55,0,.55,.2));
  77 + right: 0;
  78 + // @include transition(right .3s cubic-bezier(.55,0,.55,.2));
75 79 }
  80 +
76 81 &.tb-dashboard-toolbar-closed {
77 82 right: 18px;
  83 +
78 84 @include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
79 85 }
80 86 }
81 87
82 88 .tb-dashboard-container {
83   - &.tb-dashboard-toolbar-opened {
84   - &.is-fullscreen {
85   - margin-top: $mobile-toolbar-height;
86   - @media (min-width: $layout-breakpoint-sm) {
87   - margin-top: $fullscreen-toolbar-height;
88   - }
89   - }
90   - &:not(.is-fullscreen) {
91   - margin-top: $mobile-toolbar-height;
92   - @media (min-width: $layout-breakpoint-sm) {
93   - margin-top: $toolbar-height;
94   - }
95   - @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
96   - }
97   - }
98   - &.tb-dashboard-toolbar-closed {
99   - margin-top: 0px;
100   - @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
  89 + &.tb-dashboard-toolbar-opened {
  90 + &.is-fullscreen {
  91 + margin-top: $mobile-toolbar-height;
  92 +
  93 + @media (min-width: $layout-breakpoint-sm) {
  94 + margin-top: $fullscreen-toolbar-height;
  95 + }
  96 + }
  97 +
  98 + &:not(.is-fullscreen) {
  99 + margin-top: $mobile-toolbar-height;
  100 +
  101 + @media (min-width: $layout-breakpoint-sm) {
  102 + margin-top: $toolbar-height;
  103 + }
  104 +
  105 + @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
  106 + }
  107 + }
  108 +
  109 + &.tb-dashboard-toolbar-closed {
  110 + margin-top: 0;
  111 +
  112 + @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2) .2s);
101 113 }
  114 +
102 115 .tb-dashboard-layouts {
103 116 md-backdrop {
104 117 z-index: 1;
105 118 }
106   - #tb-main-layout {
107 119
108   - }
109 120 #tb-right-layout {
110 121 md-sidenav {
111 122 z-index: 1;
... ... @@ -124,13 +135,15 @@ section.tb-powered-by-footer {
124 135 bottom: 5px;
125 136 z-index: 3;
126 137 pointer-events: none;
  138 +
127 139 span {
128 140 font-size: 12px;
  141 +
129 142 a {
130   - font-weight: bold;
  143 + font-weight: 700;
131 144 text-decoration: none;
132   - border: none;
133 145 pointer-events: all;
  146 + border: none;
134 147 }
135 148 }
136 149 }
... ...
... ... @@ -13,6 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 md-select.default-state-controller {
17   - margin: 0px;
18   -}
\ No newline at end of file
  18 + margin: 0;
  19 +}
... ...
... ... @@ -13,34 +13,37 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -@import '../../../scss/constants';
  16 +@import "../../../scss/constants";
17 17
18 18 tb-states-component {
19   - min-width: 0px;
  19 + min-width: 0;
20 20 }
21 21
22 22 .entity-state-controller {
23   - .state-divider {
24   - font-size: 18px;
25   - padding-left: 15px;
26   - padding-right: 15px;
27   - overflow: hidden;
28   - text-overflow: ellipsis;
29   - white-space: nowrap;
30   - pointer-events: none;
31   - }
32   - .state-entry {
33   - font-size: 18px;
34   - overflow: hidden;
35   - text-overflow: ellipsis;
36   - white-space: nowrap;
37   - outline: none;
38   - }
39   - md-select {
40   - margin: 0px;
41   - .md-text {
42   - font-size: 18px;
43   - font-weight: bold;
44   - }
  23 + .state-divider {
  24 + padding-right: 15px;
  25 + padding-left: 15px;
  26 + overflow: hidden;
  27 + font-size: 18px;
  28 + text-overflow: ellipsis;
  29 + white-space: nowrap;
  30 + pointer-events: none;
  31 + }
  32 +
  33 + .state-entry {
  34 + overflow: hidden;
  35 + font-size: 18px;
  36 + text-overflow: ellipsis;
  37 + white-space: nowrap;
  38 + outline: none;
  39 + }
  40 +
  41 + md-select {
  42 + margin: 0;
  43 +
  44 + .md-text {
  45 + font-size: 18px;
  46 + font-weight: 700;
45 47 }
46   -}
\ No newline at end of file
  48 + }
  49 +}
... ...
... ... @@ -13,21 +13,22 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .manage-dashboard-states {
17 18 table.md-table {
18 19 tbody {
19 20 tr {
20 21 td {
21 22 &.tb-action-cell {
  23 + width: 100px;
  24 + min-width: 100px;
  25 + max-width: 100px;
22 26 overflow: hidden;
23 27 text-overflow: ellipsis;
24 28 white-space: nowrap;
25   - min-width: 100px;
26   - max-width: 100px;
27   - width: 100px;
28 29 }
29 30 }
30 31 }
31 32 }
32 33 }
33   -}
\ No newline at end of file
  34 +}
... ...
... ... @@ -13,7 +13,7 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
16   -@import '../../../scss/constants';
  16 +@import "../../../scss/constants";
17 17
18 18 tb-aliases-entity-select {
19 19 min-width: 52px;
... ... @@ -26,18 +26,21 @@ tb-aliases-entity-select {
26 26 }
27 27
28 28 .tb-aliases-entity-select-panel {
  29 + min-width: 300px;
29 30 max-height: 150px;
  31 + overflow-x: hidden;
  32 + overflow-y: auto;
  33 + background: #fff;
  34 + border-radius: 4px;
  35 + box-shadow:
  36 + 0 7px 8px -4px rgba(0, 0, 0, .2),
  37 + 0 13px 19px 2px rgba(0, 0, 0, .14),
  38 + 0 5px 24px 4px rgba(0, 0, 0, .12);
  39 +
30 40 @media (min-height: 350px) {
31 41 max-height: 250px;
32 42 }
33   - min-width: 300px;
34   - background: white;
35   - border-radius: 4px;
36   - box-shadow: 0 7px 8px -4px rgba(0, 0, 0, 0.2),
37   - 0 13px 19px 2px rgba(0, 0, 0, 0.14),
38   - 0 5px 24px 4px rgba(0, 0, 0, 0.12);
39   - overflow-x: hidden;
40   - overflow-y: auto;
  43 +
41 44 md-content {
42 45 background-color: #fff;
43 46 }
... ... @@ -46,15 +49,17 @@ tb-aliases-entity-select {
46 49 section.tb-aliases-entity-select {
47 50 min-height: 32px;
48 51 padding: 0 6px;
  52 +
49 53 @media (max-width: $layout-breakpoint-sm) {
50   - padding: 0px;
  54 + padding: 0;
51 55 }
  56 +
52 57 span {
53 58 max-width: 200px;
54   - pointer-events: all;
55   - cursor: pointer;
56 59 overflow: hidden;
57 60 text-overflow: ellipsis;
58 61 white-space: nowrap;
  62 + pointer-events: all;
  63 + cursor: pointer;
59 64 }
60 65 }
... ...
... ... @@ -13,14 +13,17 @@
13 13 * See the License for the specific language governing permissions and
14 14 * limitations under the License.
15 15 */
  16 +
16 17 .tb-entity-alias-dialog {
17 18 .tb-resolve-multiple-switch {
18 19 padding-left: 10px;
  20 +
19 21 .resolve-multiple-switch {
20 22 margin: 0;
21 23 }
  24 +
22 25 .resolve-multiple-label {
23 26 margin: 5px 0;
24 27 }
25 28 }
26   -}
\ No newline at end of file
  29 +}
... ...