diff --git a/application/pom.xml b/application/pom.xml index 96c9ab7b39..598d58c447 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -110,7 +110,15 @@ queue - org.thingsboard.common + org.thingsboard.common.script + script-api + + + org.thingsboard.common.script + remote-js-client + + + org.thingsboard.common stats diff --git a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json index da9a95b423..549d1e4dc2 100644 --- a/application/src/main/data/json/tenant/device_profile/rule_chain_template.json +++ b/application/src/main/data/json/tenant/device_profile/rule_chain_template.json @@ -57,7 +57,9 @@ "name": "Log RPC from Device", "debugMode": false, "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + "scriptLang": "MVEL", + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", + "mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { @@ -69,7 +71,9 @@ "name": "Log Other", "debugMode": false, "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + "scriptLang": "MVEL", + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", + "mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { diff --git a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json index 88f8232328..ae2e43fa73 100644 --- a/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json +++ b/application/src/main/data/json/tenant/edge_management/rule_chains/edge_root_rule_chain.json @@ -70,7 +70,9 @@ "name": "Log RPC from Device", "debugMode": false, "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + "scriptLang": "MVEL", + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", + "mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { @@ -82,7 +84,9 @@ "name": "Log Other", "debugMode": false, "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + "scriptLang": "MVEL", + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", + "mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { diff --git a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json index 8231ca81ff..7927a4380d 100644 --- a/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json +++ b/application/src/main/data/json/tenant/rule_chains/root_rule_chain.json @@ -57,7 +57,9 @@ "name": "Log RPC from Device", "debugMode": false, "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + "scriptLang": "MVEL", + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", + "mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { @@ -69,7 +71,9 @@ "name": "Log Other", "debugMode": false, "configuration": { - "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" + "scriptLang": "MVEL", + "jsScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);", + "mvelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { diff --git a/application/src/main/data/upgrade/3.4.1/schema_update.sql b/application/src/main/data/upgrade/3.4.1/schema_update.sql index 79cde8167f..8dceb0115d 100644 --- a/application/src/main/data/upgrade/3.4.1/schema_update.sql +++ b/application/src/main/data/upgrade/3.4.1/schema_update.sql @@ -14,6 +14,7 @@ -- limitations under the License. -- +-- AUDIT LOGS MIGRATION START DO $$ DECLARE table_partition RECORD; @@ -68,7 +69,69 @@ BEGIN END LOOP; INSERT INTO audit_log - SELECT * FROM old_audit_log + SELECT id, created_time, tenant_id, customer_id, entity_id, entity_type, entity_name, user_id, user_name, action_type, action_data, action_status, action_failure_details + FROM old_audit_log WHERE created_time >= start_time_ms AND created_time < end_time_ms; END; $$; +-- AUDIT LOGS MIGRATION END + + +-- EDGE EVENTS MIGRATION START +DO +$$ + DECLARE table_partition RECORD; + BEGIN + -- in case of running the upgrade script a second time: + IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_edge_event')) THEN + ALTER TABLE edge_event RENAME TO old_edge_event; + ALTER INDEX IF EXISTS idx_edge_event_tenant_id_and_created_time RENAME TO idx_old_edge_event_tenant_id_and_created_time; + + FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts + FROM pg_tables WHERE tablename LIKE 'edge_event_%' + LOOP + EXECUTE format('ALTER TABLE %s RENAME TO old_edge_event_%s', table_partition.name, table_partition.partition_ts); + END LOOP; + ELSE + RAISE NOTICE 'Table old_edge_event already exists, leaving as is'; + END IF; +END; +$$; + +CREATE TABLE IF NOT EXISTS edge_event ( + id uuid NOT NULL, + created_time bigint NOT NULL, + edge_id uuid, + edge_event_type varchar(255), + edge_event_uid varchar(255), + entity_id uuid, + edge_event_action varchar(255), + body varchar(10000000), + tenant_id uuid, + ts bigint NOT NULL + ) PARTITION BY RANGE (created_time); +CREATE INDEX IF NOT EXISTS idx_edge_event_tenant_id_and_created_time ON edge_event(tenant_id, created_time DESC); + +CREATE OR REPLACE PROCEDURE migrate_edge_event(IN start_time_ms BIGINT, IN end_time_ms BIGINT, IN partition_size_ms BIGINT) + LANGUAGE plpgsql AS +$$ +DECLARE + p RECORD; + partition_end_ts BIGINT; +BEGIN + FOR p IN SELECT DISTINCT (created_time - created_time % partition_size_ms) AS partition_ts FROM old_edge_event + WHERE created_time >= start_time_ms AND created_time < end_time_ms + LOOP + partition_end_ts = p.partition_ts + partition_size_ms; + RAISE NOTICE '[edge_event] Partition to create : [%-%]', p.partition_ts, partition_end_ts; + EXECUTE format('CREATE TABLE IF NOT EXISTS edge_event_%s PARTITION OF edge_event ' || + 'FOR VALUES FROM ( %s ) TO ( %s )', p.partition_ts, p.partition_ts, partition_end_ts); + END LOOP; + + INSERT INTO edge_event + SELECT id, created_time, edge_id, edge_event_type, edge_event_uid, entity_id, edge_event_action, body, tenant_id, ts + FROM old_edge_event + WHERE created_time >= start_time_ms AND created_time < end_time_ms; +END; +$$; +-- EDGE EVENTS MIGRATION END diff --git a/application/src/main/data/upgrade/3.4.1/schema_update_before.sql b/application/src/main/data/upgrade/3.4.1/schema_update_before.sql index 59566e42b5..27f772aba3 100644 --- a/application/src/main/data/upgrade/3.4.1/schema_update_before.sql +++ b/application/src/main/data/upgrade/3.4.1/schema_update_before.sql @@ -37,9 +37,10 @@ CREATE OR REPLACE PROCEDURE update_asset_profiles() LANGUAGE plpgsql AS $$ BEGIN -UPDATE asset as a SET asset_profile_id = p.id - FROM - (SELECT id, tenant_id, name from asset_profile) as p -WHERE a.asset_profile_id IS NULL AND p.tenant_id = a.tenant_id AND a.type = p.name; + UPDATE asset a SET asset_profile_id = COALESCE( + (SELECT id from asset_profile p WHERE p.tenant_id = a.tenant_id AND a.type = p.name), + (SELECT id from asset_profile p WHERE p.tenant_id = a.tenant_id AND p.name = 'default') + ) + WHERE a.asset_profile_id IS NULL; END; $$; diff --git a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java index 43559cb45a..a9c462ee05 100644 --- a/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java +++ b/application/src/main/java/org/thingsboard/server/ThingsboardServerApplication.java @@ -26,7 +26,7 @@ import java.util.Arrays; @SpringBootConfiguration @EnableAsync @EnableScheduling -@ComponentScan({"org.thingsboard.server"}) +@ComponentScan({"org.thingsboard.server", "org.thingsboard.script"}) public class ThingsboardServerApplication { private static final String SPRING_CONFIG_NAME_KEY = "--spring.config.name"; diff --git a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java index f4e7eb2463..3ccc9bbaef 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -15,9 +15,7 @@ */ package org.thingsboard.server.actors; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -34,6 +32,8 @@ import org.springframework.stereotype.Component; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.script.api.js.JsInvokeService; +import org.thingsboard.script.api.mvel.MvelInvokeService; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.cluster.TbClusterService; @@ -49,6 +49,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.tools.TbRateLimits; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.audit.AuditLogService; @@ -75,9 +76,10 @@ import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.component.ComponentDiscoveryService; @@ -92,7 +94,6 @@ import org.thingsboard.server.service.profile.TbDeviceProfileCache; import org.thingsboard.server.service.rpc.TbCoreDeviceRpcService; import org.thingsboard.server.service.rpc.TbRpcService; import org.thingsboard.server.service.rpc.TbRuleEngineDeviceRpcService; -import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.session.DeviceSessionCacheService; import org.thingsboard.server.service.sms.SmsExecutorService; import org.thingsboard.server.service.state.DeviceStateService; @@ -105,7 +106,6 @@ import javax.annotation.PostConstruct; import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; -import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ScheduledExecutorService; @@ -152,7 +152,7 @@ public class ActorSystemContext { @Autowired @Getter - private TbApiUsageClient apiUsageClient; + private TbApiUsageReportClient apiUsageClient; @Autowired @Getter @@ -270,7 +270,11 @@ public class ActorSystemContext { @Autowired @Getter - private JsInvokeService jsSandbox; + private JsInvokeService jsInvokeService; + + @Autowired(required = false) + @Getter + private MvelInvokeService mvelInvokeService; @Autowired @Getter @@ -377,6 +381,16 @@ public class ActorSystemContext { @Getter private QueueService queueService; + @Lazy + @Autowired(required = false) + @Getter + private WidgetsBundleService widgetsBundleService; + + @Lazy + @Autowired(required = false) + @Getter + private WidgetTypeService widgetTypeService; + @Value("${actors.session.max_concurrent_sessions_per_device:1}") @Getter private long maxConcurrentSessionsPerDevice; diff --git a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java index b0830317a7..8a365b4e56 100644 --- a/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/device/DeviceActorMessageProcessor.java @@ -205,8 +205,7 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { saveRpcRequestToEdgeQueue(request, rpcRequest.getRequestId()).get(); sent = true; } catch (InterruptedException | ExecutionException e) { - String errMsg = String.format("[%s][%s][%s] Failed to save rpc request to edge queue %s", tenantId, deviceId, edgeId.getId(), request); - log.error(errMsg, e); + log.error("[{}][{}][{}] Failed to save rpc request to edge queue {}", tenantId, deviceId, edgeId.getId(), request, e); } } else if (isSendNewRpcAvailable()) { sent = rpcSubscriptions.size() > 0; @@ -824,6 +823,9 @@ class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcessor { body.put("expirationTime", msg.getExpirationTime()); body.put("method", msg.getBody().getMethod()); body.put("params", msg.getBody().getParams()); + body.put("persisted", msg.isPersisted()); + body.put("retries", msg.getRetries()); + body.put("additionalInfo", msg.getAdditionalInfo()); EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(tenantId, edgeId, EdgeEventType.DEVICE, EdgeEventActionType.RPC_CALL, deviceId, body); diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java index 62d4829085..677a9c2641 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java @@ -21,10 +21,12 @@ import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import io.netty.channel.EventLoopGroup; import lombok.extern.slf4j.Slf4j; +import org.bouncycastle.util.Arrays; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.RuleEngineAlarmService; +import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService; import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache; import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; import org.thingsboard.rule.engine.api.RuleEngineRpcService; @@ -34,6 +36,7 @@ import org.thingsboard.rule.engine.api.SmsService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.sms.SmsSenderFactory; +import org.thingsboard.rule.engine.util.TenantIdLoader; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.TbActorRef; import org.thingsboard.server.cluster.TbClusterService; @@ -61,6 +64,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNodeState; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbActorMsg; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -87,10 +91,13 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; +import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine; import java.util.Collections; import java.util.List; @@ -462,8 +469,38 @@ class DefaultTbContext implements TbContext { } @Override + @Deprecated public ScriptEngine createJsScriptEngine(String script, String... argNames) { - return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsSandbox(), nodeCtx.getSelf().getId(), script, argNames); + return new RuleNodeJsScriptEngine(getTenantId(), mainCtx.getJsInvokeService(), script, argNames); + } + + private ScriptEngine createMvelScriptEngine(String script, String... argNames) { + if (mainCtx.getMvelInvokeService() == null) { + throw new RuntimeException("MVEL execution is disabled!"); + } + return new RuleNodeMvelScriptEngine(getTenantId(), mainCtx.getMvelInvokeService(), script, argNames); + } + + @Override + public ScriptEngine createScriptEngine(ScriptLanguage scriptLang, String script, String... argNames) { + if (scriptLang == null) { + scriptLang = ScriptLanguage.JS; + } + if (StringUtils.isBlank(script)) { + throw new RuntimeException(scriptLang.name() + " script is blank!"); + } + switch (scriptLang) { + case JS: + return createJsScriptEngine(script, argNames); + case MVEL: + if (Arrays.isNullOrEmpty(argNames)) { + return createMvelScriptEngine(script, "msg", "metadata", "msgType"); + } else { + return createMvelScriptEngine(script, argNames); + } + default: + throw new RuntimeException("Unsupported script language: " + scriptLang.name()); + } } @Override @@ -718,12 +755,34 @@ class DefaultTbContext implements TbContext { return mainCtx.getTenantProfileCache().get(getTenantId()); } + @Override + public WidgetsBundleService getWidgetBundleService() { + return mainCtx.getWidgetsBundleService(); + } + + @Override + public WidgetTypeService getWidgetTypeService() { + return mainCtx.getWidgetTypeService(); + } + + @Override + public RuleEngineApiUsageStateService getRuleEngineApiUsageStateService() { + return mainCtx.getApiUsageStateService(); + } + private TbMsgMetaData getActionMetaData(RuleNodeId ruleNodeId) { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("ruleNodeId", ruleNodeId.toString()); return metaData; } + @Override + public void checkTenantEntity(EntityId entityId) { + if (!this.getTenantId().equals(TenantIdLoader.findTenantId(this, entityId))) { + throw new RuntimeException("Entity with id: '" + entityId + "' specified in the configuration doesn't belong to the current tenant."); + } + } + private class SimpleTbQueueCallback implements TbQueueCallback { private final Runnable onSuccess; private final Consumer onFailure; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java index b4911650f3..279d750f6e 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java @@ -43,12 +43,12 @@ import org.thingsboard.server.common.msg.queue.RuleEngineException; import org.thingsboard.server.common.msg.queue.RuleNodeException; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.gen.transport.TransportProtos.ToRuleEngineMsg; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.common.MultipleTbQueueTbMsgCallbackWrapper; import org.thingsboard.server.queue.common.TbQueueTbMsgCallbackWrapper; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.cluster.TbClusterService; import java.util.ArrayList; @@ -73,7 +73,7 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor> nodeRoutes; private final RuleChainService service; private final TbClusterService clusterService; - private final TbApiUsageClient apiUsageClient; + private final TbApiUsageReportClient apiUsageClient; private String ruleChainName; private RuleNodeId firstId; diff --git a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java index 0821f018e9..4d102337aa 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java +++ b/application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleNodeActorMessageProcessor.java @@ -31,7 +31,7 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; import org.thingsboard.server.common.msg.queue.RuleNodeException; import org.thingsboard.server.common.msg.queue.RuleNodeInfo; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; /** * @author Andrew Shvayka @@ -40,7 +40,7 @@ public class RuleNodeActorMessageProcessor extends ComponentMsgProcessor perCustomerLimits = new ConcurrentHashMap<>(); @Override - public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { + public void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { SecurityUser user = getCurrentUser(); if (user != null && !user.isSystemAdmin()) { var profile = tenantProfileCache.get(user.getTenantId()); if (profile == null) { log.debug("[{}] Failed to lookup tenant profile", user.getTenantId()); - errorResponseHandler.handle(new BadCredentialsException("Failed to lookup tenant profile"), (HttpServletResponse) response); + errorResponseHandler.handle(new BadCredentialsException("Failed to lookup tenant profile"), response); return; } var profileConfiguration = profile.getDefaultProfileConfiguration(); @@ -80,6 +80,16 @@ public class RateLimitProcessingFilter extends GenericFilterBean { chain.doFilter(request, response); } + @Override + protected boolean shouldNotFilterAsyncDispatch() { + return false; + } + + @Override + protected boolean shouldNotFilterErrorDispatch() { + return false; + } + private boolean checkRateLimits(I ownerId, String rateLimitConfig, Map rateLimitsMap, ServletResponse response) { if (StringUtils.isNotEmpty(rateLimitConfig)) { TbRateLimits rateLimits = rateLimitsMap.get(ownerId); diff --git a/application/src/main/java/org/thingsboard/server/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 54cbdd9b0f..772c034f62 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -36,6 +36,7 @@ import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.sms.config.TestSmsRequest; import org.thingsboard.server.common.data.sync.vc.AutoCommitSettings; import org.thingsboard.server.common.data.sync.vc.RepositorySettings; +import org.thingsboard.server.common.data.sync.vc.RepositorySettingsInfo; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.permission.Operation; @@ -195,7 +196,6 @@ public class AdminController extends BaseController { notes = "Get the repository settings object. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/repositorySettings") - @ResponseBody public RepositorySettings getRepositorySettings() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); @@ -213,7 +213,6 @@ public class AdminController extends BaseController { notes = "Check whether the repository settings exists. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/repositorySettings/exists") - @ResponseBody public Boolean repositorySettingsExists() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); @@ -223,6 +222,23 @@ public class AdminController extends BaseController { } } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @GetMapping("/repositorySettings/info") + public RepositorySettingsInfo getRepositorySettingsInfo() throws Exception { + accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); + RepositorySettings repositorySettings = versionControlService.getVersionControlSettings(getTenantId()); + if (repositorySettings != null) { + return RepositorySettingsInfo.builder() + .configured(true) + .readOnly(repositorySettings.isReadOnly()) + .build(); + } else { + return RepositorySettingsInfo.builder() + .configured(false) + .build(); + } + } + @ApiOperation(value = "Creates or Updates the repository settings (saveRepositorySettings)", notes = "Creates or Updates the repository settings object. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @@ -274,7 +290,6 @@ public class AdminController extends BaseController { notes = "Get the auto commit settings object. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/autoCommitSettings") - @ResponseBody public AutoCommitSettings getAutoCommitSettings() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); @@ -288,7 +303,6 @@ public class AdminController extends BaseController { notes = "Check whether the auto commit settings exists. " + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @GetMapping("/autoCommitSettings/exists") - @ResponseBody public Boolean autoCommitSettingsExists() throws ThingsboardException { try { accessControlService.checkPermission(getCurrentUser(), Resource.VERSION_CONTROL, Operation.READ); diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java index 8450c67ea3..968829b65a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java @@ -38,7 +38,6 @@ import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetInfo; import org.thingsboard.server.common.data.asset.AssetSearchQuery; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.AssetProfileId; @@ -86,7 +85,6 @@ import static org.thingsboard.server.controller.ControllerConstants.TENANT_AUTHO import static org.thingsboard.server.controller.ControllerConstants.TENANT_OR_CUSTOMER_AUTHORITY_PARAGRAPH; import static org.thingsboard.server.controller.ControllerConstants.UUID_WIKI_LINK; import static org.thingsboard.server.controller.EdgeController.EDGE_ID; -import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; @RestController @TbCoreComponent @@ -148,9 +146,6 @@ public class AssetController extends BaseController { @RequestMapping(value = "/asset", method = RequestMethod.POST) @ResponseBody public Asset saveAsset(@ApiParam(value = "A JSON value representing the asset.") @RequestBody Asset asset) throws Exception { - if (TB_SERVICE_QUEUE.equals(asset.getType())) { - throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); - } asset.setTenantId(getTenantId()); checkEntity(asset.getId(), asset, Resource.ASSET); return tbAssetService.save(asset, getCurrentUser()); diff --git a/application/src/main/java/org/thingsboard/server/controller/AuthController.java b/application/src/main/java/org/thingsboard/server/controller/AuthController.java index 72be55a135..8384599c4d 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -42,13 +42,12 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; -import org.thingsboard.server.common.data.security.model.JwtToken; +import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; +import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.dao.audit.AuditLogService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.ActivateUserRequest; import org.thingsboard.server.service.security.model.ChangePasswordRequest; @@ -59,7 +58,6 @@ import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.system.SystemSecurityService; -import ua_parser.Client; import javax.servlet.http.HttpServletRequest; import java.net.URI; @@ -73,7 +71,6 @@ import java.net.URISyntaxException; public class AuthController extends BaseController { private final BCryptPasswordEncoder passwordEncoder; private final JwtTokenFactory tokenFactory; - private final RefreshTokenRepository refreshTokenRepository; private final MailService mailService; private final SystemSecurityService systemSecurityService; private final AuditLogService auditLogService; @@ -128,7 +125,7 @@ public class AuthController extends BaseController { sendEntityNotificationMsg(getTenantId(), userCredentials.getUserId(), EdgeEventActionType.CREDENTIALS_UPDATED); - eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId())); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); ObjectNode response = JacksonUtil.newObjectNode(); response.put("token", tokenFactory.createAccessJwtToken(securityUser).getToken()); response.put("refreshToken", tokenFactory.createRefreshToken(securityUser).getToken()); @@ -268,10 +265,7 @@ public class AuthController extends BaseController { sendEntityNotificationMsg(user.getTenantId(), user.getId(), EdgeEventActionType.CREDENTIALS_UPDATED); - JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); - JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); - - return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken()); + return tokenFactory.createTokenPair(securityUser); } catch (Exception e) { throw handleException(e); } @@ -309,11 +303,9 @@ public class AuthController extends BaseController { String email = user.getEmail(); mailService.sendPasswordWasResetEmail(loginUrl, email); - eventPublisher.publishEvent(new UserAuthDataChangedEvent(securityUser.getId())); - JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); - JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); - return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken()); + return tokenFactory.createTokenPair(securityUser); } else { throw new ThingsboardException("Invalid reset token!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); } @@ -324,49 +316,9 @@ public class AuthController extends BaseController { private void logLogoutAction(HttpServletRequest request) throws ThingsboardException { try { - SecurityUser user = getCurrentUser(); - RestAuthenticationDetails details = new RestAuthenticationDetails(request); - String clientAddress = details.getClientAddress(); - String browser = "Unknown"; - String os = "Unknown"; - String device = "Unknown"; - if (details.getUserAgent() != null) { - Client userAgent = details.getUserAgent(); - if (userAgent.userAgent != null) { - browser = userAgent.userAgent.family; - if (userAgent.userAgent.major != null) { - browser += " " + userAgent.userAgent.major; - if (userAgent.userAgent.minor != null) { - browser += "." + userAgent.userAgent.minor; - if (userAgent.userAgent.patch != null) { - browser += "." + userAgent.userAgent.patch; - } - } - } - } - if (userAgent.os != null) { - os = userAgent.os.family; - if (userAgent.os.major != null) { - os += " " + userAgent.os.major; - if (userAgent.os.minor != null) { - os += "." + userAgent.os.minor; - if (userAgent.os.patch != null) { - os += "." + userAgent.os.patch; - if (userAgent.os.patchMinor != null) { - os += "." + userAgent.os.patchMinor; - } - } - } - } - } - if (userAgent.device != null) { - device = userAgent.device.family; - } - } - auditLogService.logEntityAction( - user.getTenantId(), user.getCustomerId(), user.getId(), - user.getName(), user.getId(), null, ActionType.LOGOUT, null, clientAddress, browser, os, device); - + var user = getCurrentUser(); + systemSecurityService.logLoginAction(user, new RestAuthenticationDetails(request), ActionType.LOGOUT, null); + eventPublisher.publishEvent(new UserSessionInvalidationEvent(user.getSessionId())); } catch (Exception e) { throw handleException(e); } diff --git a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java index 5c79f9eca7..85b8e21095 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -37,6 +37,8 @@ import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.bind.annotation.RestController; import org.thingsboard.rule.engine.api.ScriptEngine; +import org.thingsboard.script.api.js.JsInvokeService; +import org.thingsboard.script.api.mvel.MvelInvokeService; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.EventInfo; @@ -59,14 +61,15 @@ import org.thingsboard.server.common.data.rule.RuleChainImportResult; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainOutputLabelsUsage; import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.event.EventService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.rule.TbRuleChainService; -import org.thingsboard.server.service.script.JsInvokeService; import org.thingsboard.server.service.script.RuleNodeJsScriptEngine; +import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -117,10 +120,10 @@ public class RuleChainController extends BaseController { private static final String RULE_CHAIN_DESCRIPTION = "The rule chain object is lightweight and contains general information about the rule chain. " + "List of rule nodes and their connection is stored in a separate 'metadata' object."; private static final String RULE_CHAIN_METADATA_DESCRIPTION = "The metadata object contains information about the rule nodes and their connections."; - private static final String TEST_JS_FUNCTION = "Execute the JavaScript function and return the result. The format of request: \n\n" + private static final String TEST_SCRIPT_FUNCTION = "Execute the Script function and return the result. The format of request: \n\n" + MARKDOWN_CODE_BLOCK_START + "{\n" + - " \"script\": \"Your JS Function as String\",\n" + + " \"script\": \"Your Function as String\",\n" + " \"scriptType\": \"One of: update, generate, filter, switch, json, string\",\n" + " \"argNames\": [\"msg\", \"metadata\", \"type\"],\n" + " \"msg\": \"{\\\"temperature\\\": 42}\", \n" + @@ -142,12 +145,18 @@ public class RuleChainController extends BaseController { @Autowired private JsInvokeService jsInvokeService; + @Autowired(required = false) + private MvelInvokeService mvelInvokeService; + @Autowired(required = false) private ActorSystemContext actorContext; @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}") private boolean debugPerTenantEnabled; + @Value("${mvel.enabled:true}") + private boolean mvelEnabled; + @ApiOperation(value = "Get Rule Chain (getRuleChainById)", notes = "Fetch the Rule Chain object based on the provided Rule Chain Id. " + RULE_CHAIN_DESCRIPTION + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAnyAuthority('TENANT_ADMIN')") @@ -369,13 +378,23 @@ public class RuleChainController extends BaseController { } } + @ApiOperation(value = "Is MVEL script executor enabled", + notes = "Returns 'True' if the MVEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH) + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/ruleChain/mvelEnabled", method = RequestMethod.GET) + @ResponseBody + public Boolean isMvelEnabled() { + return mvelEnabled; + } - @ApiOperation(value = "Test JavaScript function", - notes = TEST_JS_FUNCTION + TENANT_AUTHORITY_PARAGRAPH) + @ApiOperation(value = "Test Script function", + notes = TEST_SCRIPT_FUNCTION + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST) @ResponseBody public JsonNode testScript( + @ApiParam(value = "Script language: JS or MVEL") + @RequestParam(required = false) ScriptLanguage scriptLang, @ApiParam(value = "Test JS request. See API call description above.") @RequestBody JsonNode inputParams) throws ThingsboardException { try { @@ -393,7 +412,17 @@ public class RuleChainController extends BaseController { String errorText = ""; ScriptEngine engine = null; try { - engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, getCurrentUser().getId(), script, argNames); + if (scriptLang == null) { + scriptLang = ScriptLanguage.JS; + } + if (ScriptLanguage.JS.equals(scriptLang)) { + engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames); + } else { + if (mvelInvokeService == null) { + throw new IllegalArgumentException("MVEL script engine is disabled!"); + } + engine = new RuleNodeMvelScriptEngine(getTenantId(), mvelInvokeService, script, argNames); + } TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); switch (scriptType) { case "update": diff --git a/application/src/main/java/org/thingsboard/server/controller/UserController.java b/application/src/main/java/org/thingsboard/server/controller/UserController.java index e96a73b65c..cea9d2e95a 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -43,11 +43,9 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; -import org.thingsboard.server.common.data.security.model.JwtToken; +import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.user.TbUserService; -import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.model.JwtTokenPair; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; @@ -95,7 +93,6 @@ public class UserController extends BaseController { private final MailService mailService; private final JwtTokenFactory tokenFactory; - private final RefreshTokenRepository refreshTokenRepository; private final SystemSecurityService systemSecurityService; private final ApplicationEventPublisher eventPublisher; private final TbUserService tbUserService; @@ -163,9 +160,7 @@ public class UserController extends BaseController { UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail()); UserCredentials credentials = userService.findUserCredentialsByUserId(authUser.getTenantId(), userId); SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal); - JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); - JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); - return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken()); + return tokenFactory.createTokenPair(securityUser); } catch (Exception e) { throw handleException(e); } @@ -376,7 +371,7 @@ public class UserController extends BaseController { userService.setUserCredentialsEnabled(tenantId, userId, userCredentialsEnabled); if (!userCredentialsEnabled) { - eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId)); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(userId)); } } catch (Exception e) { throw handleException(e); diff --git a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java index 33a6d4bdd1..d49cf2dccb 100644 --- a/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java +++ b/application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java @@ -26,6 +26,7 @@ import org.thingsboard.server.service.component.ComponentDiscoveryService; import org.thingsboard.server.service.install.DatabaseEntitiesUpgradeService; import org.thingsboard.server.service.install.DatabaseTsUpgradeService; import org.thingsboard.server.service.install.EntityDatabaseSchemaService; +import org.thingsboard.server.service.install.NoSqlKeyspaceService; import org.thingsboard.server.service.install.SystemDataLoaderService; import org.thingsboard.server.service.install.TsDatabaseSchemaService; import org.thingsboard.server.service.install.TsLatestDatabaseSchemaService; @@ -51,6 +52,9 @@ public class ThingsboardInstallService { @Autowired private EntityDatabaseSchemaService entityDatabaseSchemaService; + @Autowired(required = false) + private NoSqlKeyspaceService noSqlKeyspaceService; + @Autowired private TsDatabaseSchemaService tsDatabaseSchemaService; @@ -252,6 +256,10 @@ public class ThingsboardInstallService { log.info("Installing DataBase schema for timeseries..."); + if (noSqlKeyspaceService != null) { + noSqlKeyspaceService.createDatabaseSchema(); + } + tsDatabaseSchemaService.createDatabaseSchema(); if (tsLatestDatabaseSchemaService != null) { diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java index f6a590eee2..b8ca8b37c0 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/DefaultTbApiUsageStateService.java @@ -395,6 +395,11 @@ public class DefaultTbApiUsageStateService extends AbstractPartitionBasedService } } + @Override + public ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id) { + return apiUsageStateService.findApiUsageStateById(tenantId, id); + } + private interface StateChecker { boolean check(long threshold, long warnThreshold, long value); } diff --git a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java index e6d8ba00ae..5f01d34877 100644 --- a/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/apiusage/TbApiUsageStateService.java @@ -16,21 +16,20 @@ package org.thingsboard.server.service.apiusage; import org.springframework.context.ApplicationListener; -import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.TenantProfileId; import org.thingsboard.server.common.msg.queue.TbCallback; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; -public interface TbApiUsageStateService extends ApplicationListener { +public interface TbApiUsageStateService extends TbApiUsageStateClient, RuleEngineApiUsageStateService, ApplicationListener { void process(TbProtoQueueMsg msg, TbCallback callback); - ApiUsageState getApiUsageState(TenantId tenantId); - void onTenantProfileUpdate(TenantProfileId tenantProfileId); void onTenantUpdate(TenantId tenantId); diff --git a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java index f93c6e34e4..5142311da9 100644 --- a/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/asset/AssetBulkImportService.java @@ -20,19 +20,18 @@ import com.fasterxml.jackson.databind.node.TextNode; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.service.entitiy.asset.TbAssetService; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; import java.util.Map; import java.util.Optional; @@ -47,7 +46,7 @@ public class AssetBulkImportService extends AbstractBulkImportService { @Override protected void setEntityFields(Asset entity, Map fields) { - ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(entity.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); + ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(entity); fields.forEach((columnType, value) -> { switch (columnType) { case NAME: diff --git a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java index 01f7fa282e..7337497194 100644 --- a/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/device/DeviceBulkImportService.java @@ -76,7 +76,7 @@ public class DeviceBulkImportService extends AbstractBulkImportService { @Override protected void setEntityFields(Device entity, Map fields) { - ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(entity.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); + ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(entity); fields.forEach((columnType, value) -> { switch (columnType) { case NAME: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java index 23fb8fd4be..1bb30d6960 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/DefaultEdgeNotificationService.java @@ -40,10 +40,21 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.AssetProfileEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.QueueEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.RelationEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.RuleChainEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.UserEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.WidgetBundleEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.WidgetTypeEdgeProcessor; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; @@ -69,17 +80,50 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { private EdgeProcessor edgeProcessor; @Autowired - private EntityEdgeProcessor entityProcessor; + private AssetEdgeProcessor assetProcessor; @Autowired - private AlarmEdgeProcessor alarmProcessor; + private DeviceEdgeProcessor deviceProcessor; @Autowired - private RelationEdgeProcessor relationProcessor; + private EntityViewEdgeProcessor entityViewProcessor; + + @Autowired + private DashboardEdgeProcessor dashboardProcessor; + + @Autowired + private RuleChainEdgeProcessor ruleChainProcessor; + + @Autowired + private UserEdgeProcessor userProcessor; @Autowired private CustomerEdgeProcessor customerProcessor; + @Autowired + private DeviceProfileEdgeProcessor deviceProfileProcessor; + + @Autowired + private AssetProfileEdgeProcessor assetProfileProcessor; + + @Autowired + private OtaPackageEdgeProcessor otaPackageProcessor; + + @Autowired + private WidgetBundleEdgeProcessor widgetBundleProcessor; + + @Autowired + private WidgetTypeEdgeProcessor widgetTypeProcessor; + + @Autowired + private QueueEdgeProcessor queueProcessor; + + @Autowired + private AlarmEdgeProcessor alarmProcessor; + + @Autowired + private RelationEdgeProcessor relationProcessor; + private ExecutorService dbCallBackExecutor; @PostConstruct @@ -130,24 +174,44 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { case EDGE: future = edgeProcessor.processEdgeNotification(tenantId, edgeNotificationMsg); break; - case USER: case ASSET: + future = assetProcessor.processAssetNotification(tenantId, edgeNotificationMsg); + break; case DEVICE: - case DEVICE_PROFILE: - case ASSET_PROFILE: + future = deviceProcessor.processDeviceNotification(tenantId, edgeNotificationMsg); + break; case ENTITY_VIEW: + future = entityViewProcessor.processEntityViewNotification(tenantId, edgeNotificationMsg); + break; case DASHBOARD: + future = dashboardProcessor.processDashboardNotification(tenantId, edgeNotificationMsg); + break; case RULE_CHAIN: - case OTA_PACKAGE: - future = entityProcessor.processEntityNotification(tenantId, edgeNotificationMsg); + future = ruleChainProcessor.processRuleChainNotification(tenantId, edgeNotificationMsg); + break; + case USER: + future = userProcessor.processUserNotification(tenantId, edgeNotificationMsg); break; case CUSTOMER: future = customerProcessor.processCustomerNotification(tenantId, edgeNotificationMsg); break; + case DEVICE_PROFILE: + future = deviceProfileProcessor.processDeviceProfileNotification(tenantId, edgeNotificationMsg); + break; + case ASSET_PROFILE: + future = assetProfileProcessor.processAssetProfileNotification(tenantId, edgeNotificationMsg); + break; + case OTA_PACKAGE: + future = otaPackageProcessor.processOtaPackageNotification(tenantId, edgeNotificationMsg); + break; case WIDGETS_BUNDLE: + future = widgetBundleProcessor.processWidgetsBundleNotification(tenantId, edgeNotificationMsg); + break; case WIDGET_TYPE: + future = widgetTypeProcessor.processWidgetTypeNotification(tenantId, edgeNotificationMsg); + break; case QUEUE: - future = entityProcessor.processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + future = queueProcessor.processQueueNotification(tenantId, edgeNotificationMsg); break; case ALARM: future = alarmProcessor.processAlarmNotification(tenantId, edgeNotificationMsg); @@ -176,8 +240,7 @@ public class DefaultEdgeNotificationService implements EdgeNotificationService { } private void callBackFailure(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, TbCallback callback, Throwable throwable) { - String errMsg = String.format("Can't push to edge updates, edgeNotificationMsg [%s]", edgeNotificationMsg); - log.error(errMsg, throwable); + log.error("Can't push to edge updates, edgeNotificationMsg [{}]", edgeNotificationMsg, throwable); callback.onFailure(throwable); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java index 8372e2ba0e..f8c23ece1e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeBulkImportService.java @@ -20,18 +20,17 @@ import com.fasterxml.jackson.databind.node.TextNode; import lombok.RequiredArgsConstructor; import lombok.SneakyThrows; import org.springframework.stereotype.Service; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; -import org.thingsboard.server.common.data.sync.ie.importing.csv.BulkImportColumnType; import org.thingsboard.server.service.entitiy.edge.TbEdgeService; import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.sync.ie.importing.csv.AbstractBulkImportService; import java.util.Map; import java.util.Optional; @@ -46,7 +45,7 @@ public class EdgeBulkImportService extends AbstractBulkImportService { @Override protected void setEntityFields(Edge entity, Map fields) { - ObjectNode additionalInfo = (ObjectNode) Optional.ofNullable(entity.getAdditionalInfo()).orElseGet(JacksonUtil::newObjectNode); + ObjectNode additionalInfo = getOrCreateAdditionalInfoObj(entity); fields.forEach((columnType, value) -> { switch (columnType) { case NAME: diff --git a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java index 8bc781b5fc..4adb73ebbb 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/EdgeContextComponent.java @@ -24,10 +24,13 @@ import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceProfileService; +import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.ota.OtaPackageService; import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.rule.RuleChainService; @@ -36,6 +39,7 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetsBundleService; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.edge.rpc.EdgeEventStorageSettings; +import org.thingsboard.server.service.edge.rpc.constructor.EdgeMsgConstructor; import org.thingsboard.server.service.edge.rpc.processor.AdminSettingsEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.AlarmEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.AssetEdgeProcessor; @@ -44,7 +48,7 @@ import org.thingsboard.server.service.edge.rpc.processor.CustomerEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.DashboardEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.DeviceEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.DeviceProfileEdgeProcessor; -import org.thingsboard.server.service.edge.rpc.processor.EntityEdgeProcessor; +import org.thingsboard.server.service.edge.rpc.processor.EdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.EntityViewEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.OtaPackageEdgeProcessor; import org.thingsboard.server.service.edge.rpc.processor.QueueEdgeProcessor; @@ -79,9 +83,15 @@ public class EdgeContextComponent { @Autowired private Configuration freemarkerConfig; + @Autowired + private DeviceService deviceService; + @Autowired private AssetService assetService; + @Autowired + private EntityViewService entityViewService; + @Autowired private DeviceProfileService deviceProfileService; @@ -100,6 +110,9 @@ public class EdgeContextComponent { @Autowired private UserService userService; + @Autowired + private CustomerService customerService; + @Autowired private WidgetsBundleService widgetsBundleService; @@ -122,10 +135,10 @@ public class EdgeContextComponent { private AssetProfileEdgeProcessor assetProfileProcessor; @Autowired - private DeviceEdgeProcessor deviceProcessor; + private EdgeProcessor edgeProcessor; @Autowired - private EntityEdgeProcessor entityProcessor; + private DeviceEdgeProcessor deviceProcessor; @Autowired private AssetEdgeProcessor assetProcessor; @@ -166,6 +179,9 @@ public class EdgeContextComponent { @Autowired private QueueEdgeProcessor queueEdgeProcessor; + @Autowired + private EdgeMsgConstructor edgeMsgConstructor; + @Autowired private EdgeEventStorageSettings edgeEventStorageSettings; diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java index 4cb6fcdb0a..fea89d6336 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcService.java @@ -271,7 +271,7 @@ public class EdgeGrpcService extends EdgeRpcServiceGrpc.EdgeRpcServiceImplBase i if (session != null) { boolean success = false; if (session.isConnected()) { - session.startSyncProcess(tenantId, edgeId); + session.startSyncProcess(tenantId, edgeId, true); success = true; } clusterService.pushEdgeSyncResponseToCore(new FromEdgeSyncResponse(requestId, tenantId, edgeId, success)); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java index 5f1c03b35a..957c699e32 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeGrpcSession.java @@ -24,12 +24,10 @@ import io.grpc.stub.StreamObserver; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; -import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; @@ -38,14 +36,12 @@ import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AssetProfileAssetsRequestMsg; import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; import org.thingsboard.server.gen.edge.v1.ConnectRequestMsg; import org.thingsboard.server.gen.edge.v1.ConnectResponseCode; import org.thingsboard.server.gen.edge.v1.ConnectResponseMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceProfileDevicesRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -62,7 +58,6 @@ import org.thingsboard.server.gen.edge.v1.RequestMsgType; import org.thingsboard.server.gen.edge.v1.ResponseMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; import org.thingsboard.server.gen.edge.v1.SyncCompletedMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UplinkMsg; import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; @@ -91,6 +86,8 @@ public final class EdgeGrpcSession implements Closeable { private static final ReentrantLock downlinkMsgLock = new ReentrantLock(); + private static final int MAX_DOWNLINK_ATTEMPTS = 10; // max number of attemps to send downlink message if edge connected + private static final String QUEUE_START_TS_ATTR_KEY = "queueStartTs"; private final UUID sessionId; @@ -139,7 +136,11 @@ public final class EdgeGrpcSession implements Closeable { if (connected) { if (requestMsg.getMsgType().equals(RequestMsgType.SYNC_REQUEST_RPC_MESSAGE)) { if (requestMsg.hasSyncRequestMsg() && requestMsg.getSyncRequestMsg().getSyncRequired()) { - startSyncProcess(edge.getTenantId(), edge.getId()); + boolean fullSync = true; + if (requestMsg.getSyncRequestMsg().hasFullSync()) { + fullSync = requestMsg.getSyncRequestMsg().getFullSync(); + } + startSyncProcess(edge.getTenantId(), edge.getId(), fullSync); } else { syncCompleted = true; } @@ -157,12 +158,13 @@ public final class EdgeGrpcSession implements Closeable { @Override public void onError(Throwable t) { - log.error("Failed to deliver message from client!", t); + log.error("[{}] Stream was terminated due to error:", sessionId, t); closeSession(); } @Override public void onCompleted() { + log.info("[{}] Stream was closed and completed successfully!", sessionId); closeSession(); } @@ -182,10 +184,14 @@ public final class EdgeGrpcSession implements Closeable { }; } - public void startSyncProcess(TenantId tenantId, EdgeId edgeId) { + public void startSyncProcess(TenantId tenantId, EdgeId edgeId, boolean fullSync) { log.trace("[{}][{}] Staring edge sync process", tenantId, edgeId); syncCompleted = false; - doSync(new EdgeSyncCursor(ctx, edge)); + if (sessionState.getSendDownlinkMsgsFuture() != null && sessionState.getSendDownlinkMsgsFuture().isDone()) { + String errorMsg = String.format("[%s][%s] Sync process started. General processing interrupted!", tenantId, edgeId); + sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(errorMsg)); + } + doSync(new EdgeSyncCursor(ctx, edge, fullSync)); } private void doSync(EdgeSyncCursor cursor) { @@ -253,9 +259,9 @@ public final class EdgeGrpcSession implements Closeable { try { if (msg.getSuccess()) { sessionState.getPendingMsgsMap().remove(msg.getDownlinkMsgId()); - log.debug("[{}] Msg has been processed successfully! {}", edge.getRoutingKey(), msg); + log.debug("[{}] Msg has been processed successfully!Msd Id: [{}], Msg: {}", edge.getRoutingKey(), msg.getDownlinkMsgId(), msg); } else { - log.error("[{}] Msg processing failed! Error msg: {}", edge.getRoutingKey(), msg.getErrorMsg()); + log.error("[{}] Msg processing failed! Msd Id: [{}], Error msg: {}", edge.getRoutingKey(), msg.getDownlinkMsgId(), msg.getErrorMsg()); } if (sessionState.getPendingMsgsMap().isEmpty()) { log.debug("[{}] Pending msgs map is empty. Stopping current iteration", edge.getRoutingKey()); @@ -290,7 +296,7 @@ public final class EdgeGrpcSession implements Closeable { log.debug("[{}] onConfigurationUpdate [{}]", this.sessionId, edge); this.edge = edge; EdgeUpdateMsg edgeConfig = EdgeUpdateMsg.newBuilder() - .setConfiguration(constructEdgeConfigProto(edge)).build(); + .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)).build(); ResponseMsg edgeConfigMsg = ResponseMsg.newBuilder() .setEdgeUpdateMsg(edgeConfig) .build(); @@ -386,24 +392,24 @@ public final class EdgeGrpcSession implements Closeable { private ListenableFuture sendDownlinkMsgsPack(List downlinkMsgsPack) { if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { - String erroMsg = "[" + this.sessionId + "] Previous send downdlink future was not properly completed, stopping it now"; - log.error(erroMsg); - sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(erroMsg)); + String errorMsg = "[" + this.sessionId + "] Previous send downlink future was not properly completed, stopping it now"; + log.error(errorMsg); + sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(errorMsg)); } sessionState.setSendDownlinkMsgsFuture(SettableFuture.create()); sessionState.getPendingMsgsMap().clear(); downlinkMsgsPack.forEach(msg -> sessionState.getPendingMsgsMap().put(msg.getDownlinkMsgId(), msg)); - scheduleDownlinkMsgsPackSend(true); + scheduleDownlinkMsgsPackSend(1); return sessionState.getSendDownlinkMsgsFuture(); } - private void scheduleDownlinkMsgsPackSend(boolean firstRun) { + private void scheduleDownlinkMsgsPackSend(int attempt) { Runnable sendDownlinkMsgsTask = () -> { try { if (isConnected() && sessionState.getPendingMsgsMap().values().size() > 0) { List copy = new ArrayList<>(sessionState.getPendingMsgsMap().values()); - if (!firstRun) { - log.warn("[{}] Failed to deliver the batch: {}", this.sessionId, copy); + if (attempt > 1) { + log.warn("[{}] Failed to deliver the batch: {}, attempt: {}", this.sessionId, copy, attempt); } log.trace("[{}] [{}] downlink msg(s) are going to be send.", this.sessionId, copy.size()); for (DownlinkMsg downlinkMsg : copy) { @@ -411,7 +417,13 @@ public final class EdgeGrpcSession implements Closeable { .setDownlinkMsg(downlinkMsg) .build()); } - scheduleDownlinkMsgsPackSend(false); + if (attempt < MAX_DOWNLINK_ATTEMPTS) { + scheduleDownlinkMsgsPackSend(attempt + 1); + } else { + log.warn("[{}] Failed to deliver the batch after {} attempts. Next messages are going to be discarded {}", + this.sessionId, MAX_DOWNLINK_ATTEMPTS, copy); + sessionState.getSendDownlinkMsgsFuture().set(null); + } } else { sessionState.getSendDownlinkMsgsFuture().set(null); } @@ -420,7 +432,7 @@ public final class EdgeGrpcSession implements Closeable { } }; - if (firstRun) { + if (attempt == 1) { sendDownlinkExecutorService.submit(sendDownlinkMsgsTask); } else { sessionState.setScheduledSendDownlinkTask( @@ -430,7 +442,6 @@ public final class EdgeGrpcSession implements Closeable { TimeUnit.MILLISECONDS) ); } - } private DownlinkMsg convertToDownlinkMsg(EdgeEvent edgeEvent) { @@ -450,23 +461,17 @@ public final class EdgeGrpcSession implements Closeable { case RELATION_DELETED: case ASSIGNED_TO_CUSTOMER: case UNASSIGNED_FROM_CUSTOMER: - downlinkMsg = processEntityMessage(edgeEvent, edgeEvent.getAction()); + case CREDENTIALS_REQUEST: + case ENTITY_MERGE_REQUEST: + case RPC_CALL: + downlinkMsg = convertEntityEventToDownlink(edgeEvent); log.trace("[{}][{}] entity message processed [{}]", edgeEvent.getTenantId(), this.sessionId, downlinkMsg); break; case ATTRIBUTES_UPDATED: case POST_ATTRIBUTES: case ATTRIBUTES_DELETED: case TIMESERIES_UPDATED: - downlinkMsg = ctx.getTelemetryProcessor().processTelemetryMessageToEdge(edgeEvent); - break; - case CREDENTIALS_REQUEST: - downlinkMsg = ctx.getEntityProcessor().processCredentialsRequestMessageToEdge(edgeEvent); - break; - case ENTITY_MERGE_REQUEST: - downlinkMsg = ctx.getEntityProcessor().processEntityMergeRequestMessageToEdge(edge, edgeEvent); - break; - case RPC_CALL: - downlinkMsg = ctx.getDeviceProcessor().processRpcCallMsgToEdge(edgeEvent); + downlinkMsg = ctx.getTelemetryProcessor().convertTelemetryEventToDownlink(edgeEvent); break; default: log.warn("[{}][{}] Unsupported action type [{}]", edge.getTenantId(), this.sessionId, edgeEvent.getAction()); @@ -506,80 +511,57 @@ public final class EdgeGrpcSession implements Closeable { return ctx.getAttributesService().save(edge.getTenantId(), edge.getId(), DataConstants.SERVER_SCOPE, attributes); } - private DownlinkMsg processEntityMessage(EdgeEvent edgeEvent, EdgeEventActionType action) { - UpdateMsgType msgType = getResponseMsgType(edgeEvent.getAction()); - log.trace("Executing processEntityMessage, edgeEvent [{}], action [{}], msgType [{}]", edgeEvent, action, msgType); + private DownlinkMsg convertEntityEventToDownlink(EdgeEvent edgeEvent) { + log.trace("Executing convertEntityEventToDownlink, edgeEvent [{}], action [{}]", edgeEvent, edgeEvent.getAction()); switch (edgeEvent.getType()) { + case EDGE: + return ctx.getEdgeProcessor().convertEdgeEventToDownlink(edgeEvent); case DEVICE: - return ctx.getDeviceProcessor().processDeviceToEdge(edge, edgeEvent, msgType, action); + return ctx.getDeviceProcessor().convertDeviceEventToDownlink(edgeEvent); case DEVICE_PROFILE: - return ctx.getDeviceProfileProcessor().processDeviceProfileToEdge(edgeEvent, msgType, action); + return ctx.getDeviceProfileProcessor().convertDeviceProfileEventToDownlink(edgeEvent); case ASSET_PROFILE: - return ctx.getAssetProfileProcessor().processAssetProfileToEdge(edgeEvent, msgType, action); + return ctx.getAssetProfileProcessor().convertAssetProfileEventToDownlink(edgeEvent); case ASSET: - return ctx.getAssetProcessor().processAssetToEdge(edge, edgeEvent, msgType, action); + return ctx.getAssetProcessor().convertAssetEventToDownlink(edgeEvent); case ENTITY_VIEW: - return ctx.getEntityViewProcessor().processEntityViewToEdge(edge, edgeEvent, msgType, action); + return ctx.getEntityViewProcessor().convertEntityViewEventToDownlink(edgeEvent); case DASHBOARD: - return ctx.getDashboardProcessor().processDashboardToEdge(edge, edgeEvent, msgType, action); + return ctx.getDashboardProcessor().convertDashboardEventToDownlink(edgeEvent); case CUSTOMER: - return ctx.getCustomerProcessor().processCustomerToEdge(edgeEvent, msgType, action); + return ctx.getCustomerProcessor().convertCustomerEventToDownlink(edgeEvent); case RULE_CHAIN: - return ctx.getRuleChainProcessor().processRuleChainToEdge(edge, edgeEvent, msgType, action); + return ctx.getRuleChainProcessor().convertRuleChainEventToDownlink(edge, edgeEvent); case RULE_CHAIN_METADATA: - return ctx.getRuleChainProcessor().processRuleChainMetadataToEdge(edgeEvent, msgType, this.edgeVersion); + return ctx.getRuleChainProcessor().convertRuleChainMetadataEventToDownlink(edgeEvent, this.edgeVersion); case ALARM: - return ctx.getAlarmProcessor().processAlarmToEdge(edge, edgeEvent, msgType, action); + return ctx.getAlarmProcessor().convertAlarmEventToDownlink(edgeEvent); case USER: - return ctx.getUserProcessor().processUserToEdge(edge, edgeEvent, msgType, action); + return ctx.getUserProcessor().convertUserEventToDownlink(edgeEvent); case RELATION: - return ctx.getRelationProcessor().processRelationToEdge(edgeEvent, msgType); + return ctx.getRelationProcessor().convertRelationEventToDownlink(edgeEvent); case WIDGETS_BUNDLE: - return ctx.getWidgetBundleProcessor().processWidgetsBundleToEdge(edgeEvent, msgType, action); + return ctx.getWidgetBundleProcessor().convertWidgetsBundleEventToDownlink(edgeEvent); case WIDGET_TYPE: - return ctx.getWidgetTypeProcessor().processWidgetTypeToEdge(edgeEvent, msgType, action); + return ctx.getWidgetTypeProcessor().convertWidgetTypeEventToDownlink(edgeEvent); case ADMIN_SETTINGS: - return ctx.getAdminSettingsProcessor().processAdminSettingsToEdge(edgeEvent); + return ctx.getAdminSettingsProcessor().convertAdminSettingsEventToDownlink(edgeEvent); case OTA_PACKAGE: - return ctx.getOtaPackageEdgeProcessor().processOtaPackageToEdge(edgeEvent, msgType, action); + return ctx.getOtaPackageEdgeProcessor().convertOtaPackageEventToDownlink(edgeEvent); case QUEUE: - return ctx.getQueueEdgeProcessor().processQueueToEdge(edgeEvent, msgType, action); + return ctx.getQueueEdgeProcessor().convertQueueEventToDownlink(edgeEvent); default: log.warn("Unsupported edge event type [{}]", edgeEvent); return null; } } - private UpdateMsgType getResponseMsgType(EdgeEventActionType actionType) { - switch (actionType) { - case UPDATED: - case CREDENTIALS_UPDATED: - case ASSIGNED_TO_CUSTOMER: - case UNASSIGNED_FROM_CUSTOMER: - return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE; - case ADDED: - case ASSIGNED_TO_EDGE: - case RELATION_ADD_OR_UPDATE: - return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE; - case DELETED: - case UNASSIGNED_FROM_EDGE: - case RELATION_DELETED: - return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE; - case ALARM_ACK: - return UpdateMsgType.ALARM_ACK_RPC_MESSAGE; - case ALARM_CLEAR: - return UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE; - default: - throw new RuntimeException("Unsupported actionType [" + actionType + "]"); - } - } - private ListenableFuture> processUplinkMsg(UplinkMsg uplinkMsg) { List> result = new ArrayList<>(); try { if (uplinkMsg.getEntityDataCount() > 0) { for (EntityDataProto entityData : uplinkMsg.getEntityDataList()) { - result.addAll(ctx.getTelemetryProcessor().processTelemetryFromEdge(edge.getTenantId(), edge.getCustomerId(), entityData)); + result.addAll(ctx.getTelemetryProcessor().processTelemetryFromEdge(edge.getTenantId(), entityData)); } } if (uplinkMsg.getDeviceUpdateMsgCount() > 0) { @@ -629,17 +611,7 @@ public final class EdgeGrpcSession implements Closeable { } if (uplinkMsg.getDeviceRpcCallMsgCount() > 0) { for (DeviceRpcCallMsg deviceRpcCallMsg : uplinkMsg.getDeviceRpcCallMsgList()) { - result.add(ctx.getDeviceProcessor().processDeviceRpcCallResponseFromEdge(edge.getTenantId(), deviceRpcCallMsg)); - } - } - if (uplinkMsg.getDeviceProfileDevicesRequestMsgCount() > 0) { - for (DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg : uplinkMsg.getDeviceProfileDevicesRequestMsgList()) { - result.add(ctx.getEdgeRequestsService().processDeviceProfileDevicesRequestMsg(edge.getTenantId(), edge, deviceProfileDevicesRequestMsg)); - } - } - if (uplinkMsg.getAssetProfileAssetsRequestMsgCount() > 0) { - for (AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg : uplinkMsg.getAssetProfileAssetsRequestMsgList()) { - result.add(ctx.getEdgeRequestsService().processAssetProfileAssetsRequestMsg(edge.getTenantId(), edge, assetProfileAssetsRequestMsg)); + result.add(ctx.getDeviceProcessor().processDeviceRpcCallFromEdge(edge.getTenantId(), edge, deviceRpcCallMsg)); } } if (uplinkMsg.getWidgetBundleTypesRequestMsgCount() > 0) { @@ -653,8 +625,7 @@ public final class EdgeGrpcSession implements Closeable { } } } catch (Exception e) { - String errMsg = String.format("[%s] Can't process uplink msg [%s]", this.sessionId, uplinkMsg); - log.error(errMsg, e); + log.error("[{}] Can't process uplink msg [{}]", this.sessionId, uplinkMsg, e); return Futures.immediateFailedFuture(e); } return Futures.allAsList(result); @@ -672,7 +643,7 @@ public final class EdgeGrpcSession implements Closeable { return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.ACCEPTED) .setErrorMsg("") - .setConfiguration(constructEdgeConfigProto(edge)).build(); + .setConfiguration(ctx.getEdgeMsgConstructor().constructEdgeConfiguration(edge)).build(); } return ConnectResponseMsg.newBuilder() .setResponseCode(ConnectResponseCode.BAD_CREDENTIALS) @@ -692,26 +663,6 @@ public final class EdgeGrpcSession implements Closeable { .setConfiguration(EdgeConfiguration.getDefaultInstance()).build(); } - private EdgeConfiguration constructEdgeConfigProto(Edge edge) { - EdgeConfiguration.Builder builder = EdgeConfiguration.newBuilder() - .setEdgeIdMSB(edge.getId().getId().getMostSignificantBits()) - .setEdgeIdLSB(edge.getId().getId().getLeastSignificantBits()) - .setTenantIdMSB(edge.getTenantId().getId().getMostSignificantBits()) - .setTenantIdLSB(edge.getTenantId().getId().getLeastSignificantBits()) - .setName(edge.getName()) - .setType(edge.getType()) - .setRoutingKey(edge.getRoutingKey()) - .setSecret(edge.getSecret()) - .setAdditionalInfo(JacksonUtil.toString(edge.getAdditionalInfo())) - .setCloudType("CE"); - if (edge.getCustomerId() != null) { - builder.setCustomerIdMSB(edge.getCustomerId().getId().getMostSignificantBits()) - .setCustomerIdLSB(edge.getCustomerId().getId().getLeastSignificantBits()); - } - return builder - .build(); - } - @Override public void close() { log.debug("[{}] Closing session", sessionId); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java index d6bfff76f6..e9232584be 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSyncCursor.java @@ -25,7 +25,9 @@ import org.thingsboard.server.service.edge.rpc.fetch.CustomerEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.CustomerUsersEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.DashboardsEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.DeviceProfilesEdgeEventFetcher; +import org.thingsboard.server.service.edge.rpc.fetch.DevicesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.EdgeEventFetcher; +import org.thingsboard.server.service.edge.rpc.fetch.EntityViewsEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.OtaPackagesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.QueuesEdgeEventFetcher; import org.thingsboard.server.service.edge.rpc.fetch.RuleChainsEdgeEventFetcher; @@ -43,22 +45,28 @@ public class EdgeSyncCursor { int currentIdx = 0; - public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge) { - fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService())); - fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); - fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig())); - fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService())); - fetchers.add(new AssetProfilesEdgeEventFetcher(ctx.getAssetProfileService())); - fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); - if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) { - fetchers.add(new CustomerEdgeEventFetcher()); - fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId())); + public EdgeSyncCursor(EdgeContextComponent ctx, Edge edge, boolean fullSync) { + if (fullSync) { + fetchers.add(new QueuesEdgeEventFetcher(ctx.getQueueService())); + fetchers.add(new RuleChainsEdgeEventFetcher(ctx.getRuleChainService())); + fetchers.add(new AdminSettingsEdgeEventFetcher(ctx.getAdminSettingsService(), ctx.getFreemarkerConfig())); + fetchers.add(new DeviceProfilesEdgeEventFetcher(ctx.getDeviceProfileService())); + fetchers.add(new AssetProfilesEdgeEventFetcher(ctx.getAssetProfileService())); + fetchers.add(new TenantAdminUsersEdgeEventFetcher(ctx.getUserService())); + if (edge.getCustomerId() != null && !EntityId.NULL_UUID.equals(edge.getCustomerId().getId())) { + fetchers.add(new CustomerEdgeEventFetcher()); + fetchers.add(new CustomerUsersEdgeEventFetcher(ctx.getUserService(), edge.getCustomerId())); + } } + fetchers.add(new DevicesEdgeEventFetcher(ctx.getDeviceService())); fetchers.add(new AssetsEdgeEventFetcher(ctx.getAssetService())); - fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); - fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); + fetchers.add(new EntityViewsEdgeEventFetcher(ctx.getEntityViewService())); fetchers.add(new DashboardsEdgeEventFetcher(ctx.getDashboardService())); - fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService())); + if (fullSync) { + fetchers.add(new SystemWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); + fetchers.add(new TenantWidgetsBundlesEdgeEventFetcher(ctx.getWidgetsBundleService())); + fetchers.add(new OtaPackagesEdgeEventFetcher(ctx.getOtaPackageService())); + } } public boolean hasNext() { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java index fb84d7f85a..f8cff82897 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetMsgConstructor.java @@ -19,7 +19,6 @@ import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -28,19 +27,19 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class AssetMsgConstructor { - public AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset, CustomerId customerId) { + public AssetUpdateMsg constructAssetUpdatedMsg(UpdateMsgType msgType, Asset asset) { AssetUpdateMsg.Builder builder = AssetUpdateMsg.newBuilder() .setMsgType(msgType) - .setIdMSB(asset.getId().getId().getMostSignificantBits()) - .setIdLSB(asset.getId().getId().getLeastSignificantBits()) + .setIdMSB(asset.getUuidId().getMostSignificantBits()) + .setIdLSB(asset.getUuidId().getLeastSignificantBits()) .setName(asset.getName()) .setType(asset.getType()); if (asset.getLabel() != null) { builder.setLabel(asset.getLabel()); } - if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + if (asset.getCustomerId() != null) { + builder.setCustomerIdMSB(asset.getCustomerId().getId().getMostSignificantBits()); + builder.setCustomerIdLSB(asset.getCustomerId().getId().getLeastSignificantBits()); } if (asset.getAssetProfileId() != null) { builder.setAssetProfileIdMSB(asset.getAssetProfileId().getId().getMostSignificantBits()); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java index 78f7b0327e..ec71217955 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AssetProfileMsgConstructor.java @@ -16,13 +16,11 @@ package org.thingsboard.server.service.edge.rpc.constructor; import com.google.protobuf.ByteString; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; import java.nio.charset.StandardCharsets; @@ -31,9 +29,6 @@ import java.nio.charset.StandardCharsets; @TbCoreComponent public class AssetProfileMsgConstructor { - @Autowired - private DataDecodingEncodingService dataDecodingEncodingService; - public AssetProfileUpdateMsg constructAssetProfileUpdatedMsg(UpdateMsgType msgType, AssetProfile assetProfile) { AssetProfileUpdateMsg.Builder builder = AssetProfileUpdateMsg.newBuilder() .setMsgType(msgType) @@ -41,9 +36,9 @@ public class AssetProfileMsgConstructor { .setIdLSB(assetProfile.getId().getId().getLeastSignificantBits()) .setName(assetProfile.getName()) .setDefault(assetProfile.isDefault()); - if (assetProfile.getDefaultRuleChainId() != null) { - builder.setDefaultRuleChainIdMSB(assetProfile.getDefaultRuleChainId().getId().getMostSignificantBits()) - .setDefaultRuleChainIdLSB(assetProfile.getDefaultRuleChainId().getId().getLeastSignificantBits()); + if (assetProfile.getDefaultDashboardId() != null) { + builder.setDefaultDashboardIdMSB(assetProfile.getDefaultDashboardId().getId().getMostSignificantBits()) + .setDefaultDashboardIdLSB(assetProfile.getDefaultDashboardId().getId().getLeastSignificantBits()); } if (assetProfile.getDefaultQueueName() != null) { builder.setDefaultQueueName(assetProfile.getDefaultQueueName()); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java index e240fa142d..c61c5eb260 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DashboardMsgConstructor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.constructor; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; @@ -28,16 +27,15 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class DashboardMsgConstructor { - public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard, CustomerId customerId) { + public DashboardUpdateMsg constructDashboardUpdatedMsg(UpdateMsgType msgType, Dashboard dashboard) { DashboardUpdateMsg.Builder builder = DashboardUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(dashboard.getId().getId().getMostSignificantBits()) .setIdLSB(dashboard.getId().getId().getLeastSignificantBits()) .setTitle(dashboard.getTitle()) .setConfiguration(JacksonUtil.toString(dashboard.getConfiguration())); - if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + if (dashboard.getAssignedCustomers() != null) { + builder.setAssignedCustomers(JacksonUtil.toString(dashboard.getAssignedCustomers())); } return builder.build(); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java index 3c2db96b31..522bcd3a4e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceMsgConstructor.java @@ -21,13 +21,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.RpcRequestMsg; +import org.thingsboard.server.gen.edge.v1.RpcResponseMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -41,7 +41,7 @@ public class DeviceMsgConstructor { @Autowired private DataDecodingEncodingService dataDecodingEncodingService; - public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device, CustomerId customerId, String conflictName) { + public DeviceUpdateMsg constructDeviceUpdatedMsg(UpdateMsgType msgType, Device device, String conflictName) { DeviceUpdateMsg.Builder builder = DeviceUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(device.getId().getId().getMostSignificantBits()) @@ -51,9 +51,9 @@ public class DeviceMsgConstructor { if (device.getLabel() != null) { builder.setLabel(device.getLabel()); } - if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + if (device.getCustomerId() != null) { + builder.setCustomerIdMSB(device.getCustomerId().getId().getMostSignificantBits()); + builder.setCustomerIdLSB(device.getCustomerId().getId().getLeastSignificantBits()); } if (device.getDeviceProfileId() != null) { builder.setDeviceProfileIdMSB(device.getDeviceProfileId().getId().getMostSignificantBits()); @@ -97,25 +97,55 @@ public class DeviceMsgConstructor { } public DeviceRpcCallMsg constructDeviceRpcCallMsg(UUID deviceId, JsonNode body) { - int requestId = body.get("requestId").asInt(); - boolean oneway = body.get("oneway").asBoolean(); - UUID requestUUID = UUID.fromString(body.get("requestUUID").asText()); - long expirationTime = body.get("expirationTime").asLong(); - String method = body.get("method").asText(); - String params = body.get("params").asText(); + DeviceRpcCallMsg.Builder builder = constructDeviceRpcMsg(deviceId, body); + if (body.has("error") || body.has("response")) { + RpcResponseMsg.Builder responseBuilder = RpcResponseMsg.newBuilder(); + if (body.has("error")) { + responseBuilder.setError(body.get("error").asText()); + } else { + responseBuilder.setResponse(body.get("response").asText()); + } + builder.setResponseMsg(responseBuilder.build()); + } else { + RpcRequestMsg.Builder requestBuilder = RpcRequestMsg.newBuilder(); + requestBuilder.setMethod(body.get("method").asText()); + requestBuilder.setParams(body.get("params").asText()); + builder.setRequestMsg(requestBuilder.build()); + } + return builder.build(); + } - RpcRequestMsg.Builder requestBuilder = RpcRequestMsg.newBuilder(); - requestBuilder.setMethod(method); - requestBuilder.setParams(params); + private DeviceRpcCallMsg.Builder constructDeviceRpcMsg(UUID deviceId, JsonNode body) { DeviceRpcCallMsg.Builder builder = DeviceRpcCallMsg.newBuilder() .setDeviceIdMSB(deviceId.getMostSignificantBits()) .setDeviceIdLSB(deviceId.getLeastSignificantBits()) - .setRequestUuidMSB(requestUUID.getMostSignificantBits()) - .setRequestUuidLSB(requestUUID.getLeastSignificantBits()) - .setRequestId(requestId) - .setExpirationTime(expirationTime) - .setOneway(oneway) - .setRequestMsg(requestBuilder.build()); - return builder.build(); + .setRequestId(body.get("requestId").asInt()); + if (body.get("oneway") != null) { + builder.setOneway(body.get("oneway").asBoolean()); + } + if (body.get("requestUUID") != null) { + UUID requestUUID = UUID.fromString(body.get("requestUUID").asText()); + builder.setRequestUuidMSB(requestUUID.getMostSignificantBits()) + .setRequestUuidLSB(requestUUID.getLeastSignificantBits()); + } + if (body.get("expirationTime") != null) { + builder.setExpirationTime(body.get("expirationTime").asLong()); + } + if (body.get("persisted") != null) { + builder.setPersisted(body.get("persisted").asBoolean()); + } + if (body.get("retries") != null) { + builder.setRetries(body.get("retries").asInt()); + } + if (body.get("additionalInfo") != null) { + builder.setAdditionalInfo(JacksonUtil.toString(body.get("additionalInfo"))); + } + if (body.get("serviceId") != null) { + builder.setServiceId(body.get("serviceId").asText()); + } + if (body.get("sessionId") != null) { + builder.setSessionId(body.get("sessionId").asText()); + } + return builder; } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java index cb0f3145a8..7865adb292 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/DeviceProfileMsgConstructor.java @@ -43,11 +43,6 @@ public class DeviceProfileMsgConstructor { .setDefault(deviceProfile.isDefault()) .setType(deviceProfile.getType().name()) .setProfileDataBytes(ByteString.copyFrom(dataDecodingEncodingService.encode(deviceProfile.getProfileData()))); - // TODO: @voba - add possibility to setup edge rule chain as device profile default -// if (deviceProfile.getDefaultRuleChainId() != null) { -// builder.setDefaultRuleChainIdMSB(deviceProfile.getDefaultRuleChainId().getId().getMostSignificantBits()) -// .setDefaultRuleChainIdLSB(deviceProfile.getDefaultRuleChainId().getId().getLeastSignificantBits()); -// } if (deviceProfile.getDefaultQueueName() != null) { builder.setDefaultQueueName(deviceProfile.getDefaultQueueName()); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EdgeMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EdgeMsgConstructor.java new file mode 100644 index 0000000000..5501217578 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EdgeMsgConstructor.java @@ -0,0 +1,46 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.constructor; + +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; +import org.thingsboard.server.queue.util.TbCoreComponent; + +@Component +@TbCoreComponent +public class EdgeMsgConstructor { + + public EdgeConfiguration constructEdgeConfiguration(Edge edge) { + EdgeConfiguration.Builder builder = EdgeConfiguration.newBuilder() + .setEdgeIdMSB(edge.getId().getId().getMostSignificantBits()) + .setEdgeIdLSB(edge.getId().getId().getLeastSignificantBits()) + .setTenantIdMSB(edge.getTenantId().getId().getMostSignificantBits()) + .setTenantIdLSB(edge.getTenantId().getId().getLeastSignificantBits()) + .setName(edge.getName()) + .setType(edge.getType()) + .setRoutingKey(edge.getRoutingKey()) + .setSecret(edge.getSecret()) + .setAdditionalInfo(JacksonUtil.toString(edge.getAdditionalInfo())) + .setCloudType("CE"); + if (edge.getCustomerId() != null) { + builder.setCustomerIdMSB(edge.getCustomerId().getId().getMostSignificantBits()) + .setCustomerIdLSB(edge.getCustomerId().getId().getLeastSignificantBits()); + } + return builder.build(); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java index 855890022a..e2d3735910 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityDataMsgConstructor.java @@ -15,13 +15,16 @@ */ package org.thingsboard.server.service.edge.rpc.constructor; -import com.google.gson.reflect.TypeToken; import com.google.gson.Gson; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.reflect.TypeToken; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.transport.adaptor.JsonConverter; @@ -62,7 +65,7 @@ public class EntityDataMsgConstructor { JsonObject data = entityData.getAsJsonObject(); TransportProtos.PostAttributeMsg attributesUpdatedMsg = JsonConverter.convertToAttributesProto(data.getAsJsonObject("kv")); builder.setAttributesUpdatedMsg(attributesUpdatedMsg); - builder.setPostAttributeScope(data.getAsJsonPrimitive("scope").getAsString()); + builder.setPostAttributeScope(getScopeOfDefault(data)); } catch (Exception e) { log.warn("[{}] Can't convert to AttributesUpdatedMsg proto, entityData [{}]", entityId, entityData, e); } @@ -72,7 +75,7 @@ public class EntityDataMsgConstructor { JsonObject data = entityData.getAsJsonObject(); TransportProtos.PostAttributeMsg postAttributesMsg = JsonConverter.convertToAttributesProto(data.getAsJsonObject("kv")); builder.setPostAttributesMsg(postAttributesMsg); - builder.setPostAttributeScope(data.getAsJsonPrimitive("scope").getAsString()); + builder.setPostAttributeScope(getScopeOfDefault(data)); } catch (Exception e) { log.warn("[{}] Can't convert to PostAttributesMsg, entityData [{}]", entityId, entityData, e); } @@ -94,4 +97,13 @@ public class EntityDataMsgConstructor { return builder.build(); } + private String getScopeOfDefault(JsonObject data) { + JsonPrimitive scope = data.getAsJsonPrimitive("scope"); + String result = DataConstants.SERVER_SCOPE; + if (scope != null && StringUtils.isNotBlank(scope.getAsString())) { + result = scope.getAsString(); + } + return result; + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java index 759ce9e500..d5aa79700d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/EntityViewMsgConstructor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.constructor; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.gen.edge.v1.EdgeEntityType; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; @@ -29,7 +28,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class EntityViewMsgConstructor { - public EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView, CustomerId customerId) { + public EntityViewUpdateMsg constructEntityViewUpdatedMsg(UpdateMsgType msgType, EntityView entityView) { EdgeEntityType entityType; switch (entityView.getEntityId().getEntityType()) { case DEVICE: @@ -50,9 +49,9 @@ public class EntityViewMsgConstructor { .setEntityIdMSB(entityView.getEntityId().getId().getMostSignificantBits()) .setEntityIdLSB(entityView.getEntityId().getId().getLeastSignificantBits()) .setEntityType(entityType); - if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + if (entityView.getCustomerId() != null) { + builder.setCustomerIdMSB(entityView.getCustomerId().getId().getMostSignificantBits()); + builder.setCustomerIdLSB(entityView.getCustomerId().getId().getLeastSignificantBits()); } if (entityView.getAdditionalInfo() != null) { builder.setAdditionalInfo(JacksonUtil.toString(entityView.getAdditionalInfo())); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java index ac6b822bef..625b555e24 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/UserMsgConstructor.java @@ -18,7 +18,6 @@ package org.thingsboard.server.service.edge.rpc.constructor; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; @@ -30,16 +29,16 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class UserMsgConstructor { - public UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user, CustomerId customerId) { + public UserUpdateMsg constructUserUpdatedMsg(UpdateMsgType msgType, User user) { UserUpdateMsg.Builder builder = UserUpdateMsg.newBuilder() .setMsgType(msgType) .setIdMSB(user.getId().getId().getMostSignificantBits()) .setIdLSB(user.getId().getId().getLeastSignificantBits()) .setEmail(user.getEmail()) .setAuthority(user.getAuthority().name()); - if (customerId != null) { - builder.setCustomerIdMSB(customerId.getId().getMostSignificantBits()); - builder.setCustomerIdLSB(customerId.getId().getLeastSignificantBits()); + if (user.getCustomerId() != null) { + builder.setCustomerIdMSB(user.getCustomerId().getId().getMostSignificantBits()); + builder.setCustomerIdLSB(user.getCustomerId().getId().getLeastSignificantBits()); } if (user.getFirstName() != null) { builder.setFirstName(user.getFirstName()); diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java index c86fb98502..b7c30fde64 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/AdminSettingsEdgeEventFetcher.java @@ -17,13 +17,13 @@ package org.thingsboard.server.service.edge.rpc.fetch; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import freemarker.template.Configuration; import freemarker.template.Template; import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.text.WordUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.StringUtils; @@ -49,15 +49,13 @@ import java.util.regex.Pattern; @Slf4j public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { - private static final ObjectMapper mapper = new ObjectMapper(); - private final AdminSettingsService adminSettingsService; private final Configuration freemarkerConfig; - private static Pattern startPattern = Pattern.compile("
"); - private static Pattern endPattern = Pattern.compile("
"); + private static final Pattern startPattern = Pattern.compile("
"); + private static final Pattern endPattern = Pattern.compile("
"); - private static List templatesNames = Arrays.asList( + private static final List templatesNames = Arrays.asList( "account.activated.ftl", "account.lockout.ftl", "activation.ftl", @@ -65,7 +63,7 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { "reset.password.ftl", "test.ftl"); - // TODO: fix format of next templates + // TODO: @voba fix format of next templates // "state.disabled.ftl", // "state.enabled.ftl", // "state.warning.ftl", @@ -81,21 +79,21 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { AdminSettings systemMailSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, "mail"); result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailSettings))); + EdgeEventActionType.UPDATED, null, JacksonUtil.OBJECT_MAPPER.valueToTree(systemMailSettings))); AdminSettings tenantMailSettings = convertToTenantAdminSettings(tenantId, systemMailSettings.getKey(), (ObjectNode) systemMailSettings.getJsonValue()); result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailSettings))); + EdgeEventActionType.UPDATED, null, JacksonUtil.OBJECT_MAPPER.valueToTree(tenantMailSettings))); AdminSettings systemMailTemplates = loadMailTemplates(); result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, mapper.valueToTree(systemMailTemplates))); + EdgeEventActionType.UPDATED, null, JacksonUtil.OBJECT_MAPPER.valueToTree(systemMailTemplates))); AdminSettings tenantMailTemplates = convertToTenantAdminSettings(tenantId, systemMailTemplates.getKey(), (ObjectNode) systemMailTemplates.getJsonValue()); result.add(EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ADMIN_SETTINGS, - EdgeEventActionType.UPDATED, null, mapper.valueToTree(tenantMailTemplates))); + EdgeEventActionType.UPDATED, null, JacksonUtil.OBJECT_MAPPER.valueToTree(tenantMailTemplates))); - // @voba - returns PageData object to be in sync with other fetchers + // return PageData object to be in sync with other fetchers return new PageData<>(result, 1, result.size(), false); } @@ -116,7 +114,7 @@ public class AdminSettingsEdgeEventFetcher implements EdgeEventFetcher { AdminSettings adminSettings = new AdminSettings(); adminSettings.setId(new AdminSettingsId(Uuids.timeBased())); adminSettings.setKey("mailTemplates"); - adminSettings.setJsonValue(mapper.convertValue(mailTemplates, JsonNode.class)); + adminSettings.setJsonValue(JacksonUtil.OBJECT_MAPPER.convertValue(mailTemplates, JsonNode.class)); return adminSettings; } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java index 1d5c618a0a..de88c2f05b 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerEdgeEventFetcher.java @@ -29,8 +29,8 @@ import org.thingsboard.server.common.data.page.PageLink; import java.util.ArrayList; import java.util.List; -@AllArgsConstructor @Slf4j +@AllArgsConstructor public class CustomerEdgeEventFetcher implements EdgeEventFetcher { @Override diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java index cb72ca8bcb..2cc923d08a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/CustomerUsersEdgeEventFetcher.java @@ -16,8 +16,6 @@ package org.thingsboard.server.service.edge.rpc.fetch; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; @@ -37,5 +35,4 @@ public class CustomerUsersEdgeEventFetcher extends BaseUsersEdgeEventFetcher { protected PageData findUsers(TenantId tenantId, PageLink pageLink) { return userService.findCustomerUsers(tenantId, customerId, pageLink); } - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java new file mode 100644 index 0000000000..fe896dbb79 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/DevicesEdgeEventFetcher.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.fetch; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.device.DeviceService; + +@AllArgsConstructor +@Slf4j +public class DevicesEdgeEventFetcher extends BasePageableEdgeEventFetcher { + + private final DeviceService deviceService; + + @Override + PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) { + return deviceService.findDevicesByTenantIdAndEdgeId(tenantId, edge.getId(), pageLink); + } + + @Override + EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, Device device) { + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, + EdgeEventActionType.ADDED, device.getId(), null); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java new file mode 100644 index 0000000000..3bc4befdbe --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/EntityViewsEdgeEventFetcher.java @@ -0,0 +1,47 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.edge.rpc.fetch; + +import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.EdgeUtils; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.dao.entityview.EntityViewService; + +@AllArgsConstructor +@Slf4j +public class EntityViewsEdgeEventFetcher extends BasePageableEdgeEventFetcher { + + private final EntityViewService entityViewService; + + @Override + PageData fetchPageData(TenantId tenantId, Edge edge, PageLink pageLink) { + return entityViewService.findEntityViewsByTenantIdAndEdgeId(tenantId, edge.getId(), pageLink); + } + + @Override + EdgeEvent constructEdgeEvent(TenantId tenantId, Edge edge, EntityView entityView) { + return EdgeUtils.constructEdgeEvent(tenantId, edge.getId(), EdgeEventType.ENTITY_VIEW, + EdgeEventActionType.ADDED, entityView.getId(), null); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java index 2186bdc608..a496231f7a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/fetch/TenantAdminUsersEdgeEventFetcher.java @@ -31,4 +31,4 @@ public class TenantAdminUsersEdgeEventFetcher extends BaseUsersEdgeEventFetcher protected PageData findUsers(TenantId tenantId, PageLink pageLink) { return userService.findTenantAdmins(tenantId, pageLink); } -} +} \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AdminSettingsEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AdminSettingsEdgeProcessor.java index 21063c1ed7..c3d3df4492 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AdminSettingsEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AdminSettingsEdgeProcessor.java @@ -17,6 +17,7 @@ package org.thingsboard.server.service.edge.rpc.processor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.AdminSettings; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -29,8 +30,8 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class AdminSettingsEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processAdminSettingsToEdge(EdgeEvent edgeEvent) { - AdminSettings adminSettings = mapper.convertValue(edgeEvent.getBody(), AdminSettings.class); + public DownlinkMsg convertAdminSettingsEventToDownlink(EdgeEvent edgeEvent) { + AdminSettings adminSettings = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), AdminSettings.class); AdminSettingsUpdateMsg adminSettingsUpdateMsg = adminSettingsMsgConstructor.constructAdminSettingsUpdateMsg(adminSettings); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java index a63e377a5b..b5d256bfa8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AlarmEdgeProcessor.java @@ -20,12 +20,12 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.alarm.Alarm; import org.thingsboard.server.common.data.alarm.AlarmSeverity; import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; @@ -76,7 +76,7 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { existentAlarm.setStatus(AlarmStatus.valueOf(alarmUpdateMsg.getStatus())); existentAlarm.setAckTs(alarmUpdateMsg.getAckTs()); existentAlarm.setEndTs(alarmUpdateMsg.getEndTs()); - existentAlarm.setDetails(mapper.readTree(alarmUpdateMsg.getDetails())); + existentAlarm.setDetails(JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails())); alarmService.createOrUpdateAlarm(existentAlarm); break; case ALARM_ACK_RPC_MESSAGE: @@ -86,7 +86,8 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { break; case ALARM_CLEAR_RPC_MESSAGE: if (existentAlarm != null) { - alarmService.clearAlarm(tenantId, existentAlarm.getId(), mapper.readTree(alarmUpdateMsg.getDetails()), alarmUpdateMsg.getAckTs()); + alarmService.clearAlarm(tenantId, existentAlarm.getId(), + JacksonUtil.OBJECT_MAPPER.readTree(alarmUpdateMsg.getDetails()), alarmUpdateMsg.getAckTs()); } break; case ENTITY_DELETED_RPC_MESSAGE: @@ -115,10 +116,11 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { } } - public DownlinkMsg processAlarmToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertAlarmEventToDownlink(EdgeEvent edgeEvent) { AlarmId alarmId = new AlarmId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: case ALARM_ACK: @@ -128,7 +130,7 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { if (alarm != null) { downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm)) + .addAlarmUpdateMsg(alarmMsgConstructor.constructAlarmUpdatedMsg(edgeEvent.getTenantId(), msgType, alarm)) .build(); } } catch (Exception e) { @@ -136,9 +138,9 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { } break; case DELETED: - Alarm alarm = mapper.convertValue(edgeEvent.getBody(), Alarm.class); + Alarm alarm = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), Alarm.class); AlarmUpdateMsg alarmUpdateMsg = - alarmMsgConstructor.constructAlarmUpdatedMsg(edge.getTenantId(), msgType, alarm); + alarmMsgConstructor.constructAlarmUpdatedMsg(edgeEvent.getTenantId(), msgType, alarm); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addAlarmUpdateMsg(alarmUpdateMsg) @@ -154,8 +156,8 @@ public class AlarmEdgeProcessor extends BaseEdgeProcessor { switch (actionType) { case DELETED: EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); - Alarm deletedAlarm = mapper.readValue(edgeNotificationMsg.getBody(), Alarm.class); - return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, mapper.valueToTree(deletedAlarm)); + Alarm deletedAlarm = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), Alarm.class); + return saveEdgeEvent(tenantId, edgeId, EdgeEventType.ALARM, actionType, alarmId, JacksonUtil.OBJECT_MAPPER.valueToTree(deletedAlarm)); default: ListenableFuture alarmFuture = alarmService.findAlarmByIdAsync(tenantId, alarmId); return Futures.transformAsync(alarmFuture, alarm -> { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetEdgeProcessor.java index 328f67dcaf..8f03523034 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetEdgeProcessor.java @@ -15,18 +15,20 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -34,10 +36,10 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class AssetEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processAssetToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertAssetEventToDownlink(EdgeEvent edgeEvent) { AssetId assetId = new AssetId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: case ASSIGNED_TO_EDGE: @@ -45,13 +47,17 @@ public class AssetEdgeProcessor extends BaseEdgeProcessor { case UNASSIGNED_FROM_CUSTOMER: Asset asset = assetService.findAssetById(edgeEvent.getTenantId(), assetId); if (asset != null) { - CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(asset, edge); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); AssetUpdateMsg assetUpdateMsg = - assetMsgConstructor.constructAssetUpdatedMsg(msgType, asset, customerId); - downlinkMsg = DownlinkMsg.newBuilder() + assetMsgConstructor.constructAssetUpdatedMsg(msgType, asset); + DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addAssetUpdateMsg(assetUpdateMsg) - .build(); + .addAssetUpdateMsg(assetUpdateMsg); + if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { + AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), asset.getAssetProfileId()); + builder.addAssetProfileUpdateMsg(assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile)); + } + downlinkMsg = builder.build(); } break; case DELETED: @@ -66,4 +72,8 @@ public class AssetEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } + + public ListenableFuture processAssetNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotification(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java index ec11764fea..ce17c050b3 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/AssetProfileEdgeProcessor.java @@ -15,16 +15,18 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AssetProfileId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -32,14 +34,15 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class AssetProfileEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processAssetProfileToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertAssetProfileEventToDownlink(EdgeEvent edgeEvent) { AssetProfileId assetProfileId = new AssetProfileId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: AssetProfile assetProfile = assetProfileService.findAssetProfileById(edgeEvent.getTenantId(), assetProfileId); if (assetProfile != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileMsgConstructor.constructAssetProfileUpdatedMsg(msgType, assetProfile); downlinkMsg = DownlinkMsg.newBuilder() @@ -60,4 +63,8 @@ public class AssetProfileEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } + public ListenableFuture processAssetProfileNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java index de71fa32a3..3b39520606 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/BaseEdgeProcessor.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.edge.rpc.processor; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; @@ -25,17 +24,26 @@ import org.springframework.context.annotation.Lazy; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.HasCustomerId; +import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.asset.AssetProfileService; import org.thingsboard.server.dao.asset.AssetService; @@ -57,6 +65,8 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.service.edge.rpc.constructor.AdminSettingsMsgConstructor; @@ -67,6 +77,7 @@ import org.thingsboard.server.service.edge.rpc.constructor.CustomerMsgConstructo import org.thingsboard.server.service.edge.rpc.constructor.DashboardMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.DeviceMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.DeviceProfileMsgConstructor; +import org.thingsboard.server.service.edge.rpc.constructor.EdgeMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.EntityDataMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.EntityViewMsgConstructor; import org.thingsboard.server.service.edge.rpc.constructor.OtaPackageMsgConstructor; @@ -85,13 +96,12 @@ import org.thingsboard.server.service.telemetry.TelemetrySubscriptionService; import java.util.ArrayList; import java.util.List; +import java.util.UUID; @Slf4j public abstract class BaseEdgeProcessor { - protected static final ObjectMapper mapper = new ObjectMapper(); - - protected static final int DEFAULT_PAGE_SIZE = 1000; + protected static final int DEFAULT_PAGE_SIZE = 100; @Autowired protected TelemetrySubscriptionService tsSubService; @@ -181,6 +191,9 @@ public abstract class BaseEdgeProcessor { @Autowired protected DataValidator deviceValidator; + @Autowired + protected EdgeMsgConstructor edgeMsgConstructor; + @Autowired protected EntityDataMsgConstructor entityDataMsgConstructor; @@ -253,14 +266,6 @@ public abstract class BaseEdgeProcessor { }, dbCallbackExecutorService); } - protected CustomerId getCustomerIdIfEdgeAssignedToCustomer(HasCustomerId hasCustomerIdEntity, Edge edge) { - if (!edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(hasCustomerIdEntity.getCustomerId())) { - return edge.getCustomerId(); - } else { - return null; - } - } - protected ListenableFuture processActionForAllEdges(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { List> futures = new ArrayList<>(); if (TenantId.SYS_TENANT_ID.equals(tenantId)) { @@ -269,17 +274,21 @@ public abstract class BaseEdgeProcessor { do { tenantsIds = tenantService.findTenantsIds(pageLink); for (TenantId tenantId1 : tenantsIds.getData()) { - futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId)); + futures.addAll(processActionForAllEdgesByTenantId(tenantId1, type, actionType, entityId, null)); } pageLink = pageLink.nextPageLink(); } while (tenantsIds.hasNext()); } else { - futures = processActionForAllEdgesByTenantId(tenantId, type, actionType, entityId); + futures = processActionForAllEdgesByTenantId(tenantId, type, actionType, entityId, null); } return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } - private List> processActionForAllEdgesByTenantId(TenantId tenantId, EdgeEventType type, EdgeEventActionType actionType, EntityId entityId) { + protected List> processActionForAllEdgesByTenantId(TenantId tenantId, + EdgeEventType type, + EdgeEventActionType actionType, + EntityId entityId, + JsonNode body) { PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); PageData pageData; List> futures = new ArrayList<>(); @@ -287,7 +296,7 @@ public abstract class BaseEdgeProcessor { pageData = edgeService.findEdgesByTenantId(tenantId, pageLink); if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { for (Edge edge : pageData.getData()) { - futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, null)); + futures.add(saveEdgeEvent(tenantId, edge.getId(), type, actionType, entityId, body)); } if (pageData.hasNext()) { pageLink = pageLink.nextPageLink(); @@ -296,4 +305,161 @@ public abstract class BaseEdgeProcessor { } while (pageData != null && pageData.hasNext()); return futures; } + + protected UpdateMsgType getUpdateMsgType(EdgeEventActionType actionType) { + switch (actionType) { + case UPDATED: + case CREDENTIALS_UPDATED: + case ASSIGNED_TO_CUSTOMER: + case UNASSIGNED_FROM_CUSTOMER: + return UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE; + case ADDED: + case ASSIGNED_TO_EDGE: + case RELATION_ADD_OR_UPDATE: + return UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE; + case DELETED: + case UNASSIGNED_FROM_EDGE: + case RELATION_DELETED: + return UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE; + case ALARM_ACK: + return UpdateMsgType.ALARM_ACK_RPC_MESSAGE; + case ALARM_CLEAR: + return UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE; + default: + throw new RuntimeException("Unsupported actionType [" + actionType + "]"); + } + } + + protected ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); + EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); + EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, + new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); + EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg); + switch (actionType) { + case ADDED: + case UPDATED: + case CREDENTIALS_UPDATED: + case ASSIGNED_TO_CUSTOMER: + case UNASSIGNED_FROM_CUSTOMER: + case DELETED: + if (edgeId != null) { + return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); + } else { + return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); + } + case ASSIGNED_TO_EDGE: + case UNASSIGNED_FROM_EDGE: + ListenableFuture future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); + return Futures.transformAsync(future, unused -> { + if (type.equals(EdgeEventType.RULE_CHAIN)) { + return updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId); + } else { + return Futures.immediateFuture(null); + } + }, dbCallbackExecutorService); + default: + return Futures.immediateFuture(null); + } + } + + private EdgeId safeGetEdgeId(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) { + return new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); + } else { + return null; + } + } + + private ListenableFuture pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (EdgeId relatedEdgeId : pageData.getData()) { + futures.add(saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + } + + private ListenableFuture updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + List> futures = new ArrayList<>(); + do { + pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + for (RuleChain ruleChain : pageData.getData()) { + if (!ruleChain.getId().equals(processingRuleChainId)) { + List connectionInfos = + ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections(); + if (connectionInfos != null && !connectionInfos.isEmpty()) { + for (RuleChainConnectionInfo connectionInfo : connectionInfos) { + if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) { + futures.add(saveEdgeEvent(tenantId, + edgeId, + EdgeEventType.RULE_CHAIN_METADATA, + EdgeEventActionType.UPDATED, + ruleChain.getId(), + null)); + } + } + } + } + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); + } + } + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + } + + protected ListenableFuture processEntityNotificationForAllEdges(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); + EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); + EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); + switch (actionType) { + case ADDED: + case UPDATED: + case DELETED: + case CREDENTIALS_UPDATED: // used by USER entity + return processActionForAllEdges(tenantId, type, actionType, entityId); + default: + return Futures.immediateFuture(null); + } + } + + protected EntityId constructEntityId(String entityTypeStr, long entityIdMSB, long entityIdLSB) { + EntityType entityType = EntityType.valueOf(entityTypeStr); + switch (entityType) { + case DEVICE: + return new DeviceId(new UUID(entityIdMSB, entityIdLSB)); + case ASSET: + return new AssetId(new UUID(entityIdMSB, entityIdLSB)); + case ENTITY_VIEW: + return new EntityViewId(new UUID(entityIdMSB, entityIdLSB)); + case DASHBOARD: + return new DashboardId(new UUID(entityIdMSB, entityIdLSB)); + case TENANT: + return TenantId.fromUUID(new UUID(entityIdMSB, entityIdLSB)); + case CUSTOMER: + return new CustomerId(new UUID(entityIdMSB, entityIdLSB)); + case USER: + return new UserId(new UUID(entityIdMSB, entityIdLSB)); + case EDGE: + return new EdgeId(new UUID(entityIdMSB, entityIdLSB)); + default: + log.warn("Unsupported entity type [{}] during construct of entity id. entityIdMSB [{}], entityIdLSB [{}]", + entityTypeStr, entityIdMSB, entityIdLSB); + return null; + } + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java index 3554057482..7d720fa671 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/CustomerEdgeProcessor.java @@ -46,14 +46,15 @@ import java.util.UUID; @TbCoreComponent public class CustomerEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processCustomerToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertCustomerEventToDownlink(EdgeEvent edgeEvent) { CustomerId customerId = new CustomerId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: Customer customer = customerService.findCustomerById(edgeEvent.getTenantId(), customerId); if (customer != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); CustomerUpdateMsg customerUpdateMsg = customerMsgConstructor.constructCustomerUpdatedMsg(msgType, customer); downlinkMsg = DownlinkMsg.newBuilder() @@ -103,5 +104,4 @@ public class CustomerEdgeProcessor extends BaseEdgeProcessor { return Futures.immediateFuture(null); } } - } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DashboardEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DashboardEdgeProcessor.java index f07ed80817..283df6954e 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DashboardEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DashboardEdgeProcessor.java @@ -15,31 +15,29 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Dashboard; import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; -import java.util.Collections; - @Component @Slf4j @TbCoreComponent public class DashboardEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processDashboardToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertDashboardEventToDownlink(EdgeEvent edgeEvent) { DashboardId dashboardId = new DashboardId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: case ASSIGNED_TO_EDGE: @@ -47,12 +45,9 @@ public class DashboardEdgeProcessor extends BaseEdgeProcessor { case UNASSIGNED_FROM_CUSTOMER: Dashboard dashboard = dashboardService.findDashboardById(edgeEvent.getTenantId(), dashboardId); if (dashboard != null) { - CustomerId customerId = null; - if (!edge.getCustomerId().isNullUid() && dashboard.isAssignedToCustomer(edge.getCustomerId())) { - customerId = edge.getCustomerId(); - } + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DashboardUpdateMsg dashboardUpdateMsg = - dashboardMsgConstructor.constructDashboardUpdatedMsg(msgType, dashboard, customerId); + dashboardMsgConstructor.constructDashboardUpdatedMsg(msgType, dashboard); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addDashboardUpdateMsg(dashboardUpdateMsg) @@ -71,4 +66,8 @@ public class DashboardEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } + + public ListenableFuture processDashboardNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotification(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java index 6d0f66a884..81e7970611 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java @@ -25,9 +25,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.data.DeviceData; @@ -52,13 +52,15 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; +import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.dao.exception.DataValidationException; -import org.thingsboard.server.dao.model.ModelConstants; +import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.TbQueueCallback; import org.thingsboard.server.queue.TbQueueMsgMetadata; import org.thingsboard.server.queue.util.DataDecodingEncodingService; @@ -99,11 +101,10 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { try { newDevice = createDevice(tenantId, edge, deviceUpdateMsg, newDeviceName); } catch (DataValidationException e) { - String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg); - log.error(errMsg, e); + log.error("[{}] Device update msg can't be processed due to data validation [{}]", tenantId, deviceUpdateMsg, e); return Futures.immediateFuture(null); } - ObjectNode body = mapper.createObjectNode(); + ObjectNode body = JacksonUtil.OBJECT_MAPPER.createObjectNode(); body.put("conflictName", deviceName); ListenableFuture input = saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ENTITY_MERGE_REQUEST, newDevice.getId(), body); return Futures.transformAsync(input, unused -> @@ -111,12 +112,11 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { dbCallbackExecutorService); } } else { - log.info("[{}] Creating new device and replacing device entity on the edge [{}]", tenantId, deviceUpdateMsg); + log.info("[{}] Creating new device on the cloud [{}]", tenantId, deviceUpdateMsg); try { device = createDevice(tenantId, edge, deviceUpdateMsg, deviceUpdateMsg.getName()); } catch (DataValidationException e) { - String errMsg = String.format("[%s] Device update msg can't be processed due to data validation [%s]", tenantId, deviceUpdateMsg); - log.error(errMsg, e); + log.error("[{}] Device update msg can't be processed due to data validation [{}]", tenantId, deviceUpdateMsg, e); return Futures.immediateFuture(null); } return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, device.getId(), null); @@ -198,13 +198,14 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { deviceUpdateMsg.getDeviceProfileIdLSB())); device.setDeviceProfileId(deviceProfileId); } + device.setCustomerId(getCustomerId(deviceUpdateMsg)); Optional deviceDataOpt = dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); if (deviceDataOpt.isPresent()) { device.setDeviceData(deviceDataOpt.get()); } Device savedDevice = deviceService.saveDevice(device); - tbClusterService.onDeviceUpdated(savedDevice, device); + tbClusterService.onDeviceUpdated(savedDevice, device, false); return saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.CREDENTIALS_REQUEST, deviceId, null); } else { String errMsg = String.format("[%s] can't find device [%s], edge [%s]", tenantId, deviceUpdateMsg, edge.getId()); @@ -227,8 +228,6 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { device.setCreatedTime(Uuids.unixTimestamp(deviceId.getId())); created = true; } - // make device private, if edge is public - device.setCustomerId(getCustomerId(edge)); device.setName(deviceName); device.setType(deviceUpdateMsg.getType()); if (deviceUpdateMsg.hasLabel()) { @@ -243,6 +242,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { deviceUpdateMsg.getDeviceProfileIdLSB())); device.setDeviceProfileId(deviceProfileId); } + device.setCustomerId(getCustomerId(deviceUpdateMsg)); Optional deviceDataOpt = dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); if (deviceDataOpt.isPresent()) { @@ -272,15 +272,11 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { return device; } - private CustomerId getCustomerId(Edge edge) { - if (edge.getCustomerId() == null || edge.getCustomerId().getId().equals(ModelConstants.NULL_UUID)) { - return edge.getCustomerId(); - } - Customer publicCustomer = customerService.findOrCreatePublicCustomer(edge.getTenantId()); - if (publicCustomer.getId().equals(edge.getCustomerId())) { - return null; + private CustomerId getCustomerId(DeviceUpdateMsg deviceUpdateMsg) { + if (deviceUpdateMsg.hasCustomerIdMSB() && deviceUpdateMsg.hasCustomerIdLSB()) { + return new CustomerId(new UUID(deviceUpdateMsg.getCustomerIdMSB(), deviceUpdateMsg.getCustomerIdLSB())); } else { - return edge.getCustomerId(); + return null; } } @@ -296,9 +292,9 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { private void pushDeviceCreatedEventToRuleEngine(TenantId tenantId, Edge edge, Device device) { try { DeviceId deviceId = device.getId(); - ObjectNode entityNode = mapper.valueToTree(device); + ObjectNode entityNode = JacksonUtil.OBJECT_MAPPER.valueToTree(device); TbMsg tbMsg = TbMsg.newMsg(DataConstants.ENTITY_CREATED, deviceId, device.getCustomerId(), - getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, mapper.writeValueAsString(entityNode)); + getActionTbMsgMetaData(edge, device.getCustomerId()), TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(entityNode)); tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { @@ -330,8 +326,17 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { return metaData; } - public ListenableFuture processDeviceRpcCallResponseFromEdge(TenantId tenantId, DeviceRpcCallMsg deviceRpcCallMsg) { - log.trace("[{}] processDeviceRpcCallResponseMsg [{}]", tenantId, deviceRpcCallMsg); + public ListenableFuture processDeviceRpcCallFromEdge(TenantId tenantId, Edge edge, DeviceRpcCallMsg deviceRpcCallMsg) { + log.trace("[{}] processDeviceRpcCallFromEdge [{}]", tenantId, deviceRpcCallMsg); + if (deviceRpcCallMsg.hasResponseMsg()) { + return processDeviceRpcResponseFromEdge(tenantId, deviceRpcCallMsg); + } else if (deviceRpcCallMsg.hasRequestMsg()) { + return processDeviceRpcRequestFromEdge(tenantId, edge, deviceRpcCallMsg); + } + return Futures.immediateFuture(null); + } + + private ListenableFuture processDeviceRpcResponseFromEdge(TenantId tenantId, DeviceRpcCallMsg deviceRpcCallMsg) { SettableFuture futureToSet = SettableFuture.create(); UUID requestUuid = new UUID(deviceRpcCallMsg.getRequestUuidMSB(), deviceRpcCallMsg.getRequestUuidLSB()); DeviceId deviceId = new DeviceId(new UUID(deviceRpcCallMsg.getDeviceIdMSB(), deviceRpcCallMsg.getDeviceIdLSB())); @@ -362,11 +367,50 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { return futureToSet; } - public DownlinkMsg processDeviceToEdge(Edge edge, EdgeEvent edgeEvent, - UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) { + private ListenableFuture processDeviceRpcRequestFromEdge(TenantId tenantId, Edge edge, DeviceRpcCallMsg deviceRpcCallMsg) { + DeviceId deviceId = new DeviceId(new UUID(deviceRpcCallMsg.getDeviceIdMSB(), deviceRpcCallMsg.getDeviceIdLSB())); + try { + TbMsgMetaData metaData = new TbMsgMetaData(); + String requestId = Integer.toString(deviceRpcCallMsg.getRequestId()); + metaData.putValue("requestId", requestId); + metaData.putValue("serviceId", deviceRpcCallMsg.getServiceId()); + metaData.putValue("sessionId", deviceRpcCallMsg.getSessionId()); + metaData.putValue(DataConstants.EDGE_ID, edge.getId().toString()); + Device device = deviceService.findDeviceById(tenantId, deviceId); + if (device != null) { + metaData.putValue("deviceName", device.getName()); + metaData.putValue("deviceType", device.getType()); + metaData.putValue(DataConstants.DEVICE_ID, deviceId.getId().toString()); + } + ObjectNode data = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + data.put("method", deviceRpcCallMsg.getRequestMsg().getMethod()); + data.put("params", deviceRpcCallMsg.getRequestMsg().getParams()); + TbMsg tbMsg = TbMsg.newMsg(SessionMsgType.TO_SERVER_RPC_REQUEST.name(), deviceId, null, metaData, + TbMsgDataType.JSON, JacksonUtil.OBJECT_MAPPER.writeValueAsString(data)); + tbClusterService.pushMsgToRuleEngine(tenantId, deviceId, tbMsg, new TbQueueCallback() { + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + log.debug("Successfully send TO_SERVER_RPC_REQUEST to rule engine [{}], deviceRpcCallMsg {}", + device, deviceRpcCallMsg); + } + + @Override + public void onFailure(Throwable t) { + log.debug("Failed to send TO_SERVER_RPC_REQUEST to rule engine [{}], deviceRpcCallMsg {}", + device, deviceRpcCallMsg, t); + } + }); + } catch (JsonProcessingException | IllegalArgumentException e) { + log.warn("[{}] Failed to push TO_SERVER_RPC_REQUEST to rule engine. deviceRpcCallMsg {}", deviceId, deviceRpcCallMsg, e); + } + + return Futures.immediateFuture(null); + } + + public DownlinkMsg convertDeviceEventToDownlink(EdgeEvent edgeEvent) { DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (edgeEdgeEventActionType) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: case ASSIGNED_TO_EDGE: @@ -374,13 +418,17 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { case UNASSIGNED_FROM_CUSTOMER: Device device = deviceService.findDeviceById(edgeEvent.getTenantId(), deviceId); if (device != null) { - CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(device, edge); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DeviceUpdateMsg deviceUpdateMsg = - deviceMsgConstructor.constructDeviceUpdatedMsg(msgType, device, customerId, null); - downlinkMsg = DownlinkMsg.newBuilder() + deviceMsgConstructor.constructDeviceUpdatedMsg(msgType, device, null); + DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceUpdateMsg(deviceUpdateMsg) - .build(); + .addDeviceUpdateMsg(deviceUpdateMsg); + if (UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(msgType)) { + DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), device.getDeviceProfileId()); + builder.addDeviceProfileUpdateMsg(deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile)); + } + downlinkMsg = builder.build(); } break; case DELETED: @@ -393,7 +441,7 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { .build(); break; case CREDENTIALS_UPDATED: - DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(edge.getTenantId(), deviceId); + DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(edgeEvent.getTenantId(), deviceId); if (deviceCredentials != null) { DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = deviceMsgConstructor.constructDeviceCredentialsUpdatedMsg(deviceCredentials); @@ -403,17 +451,52 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { .build(); } break; + case RPC_CALL: + return convertRpcCallEventToDownlink(edgeEvent); + case CREDENTIALS_REQUEST: + return convertCredentialsRequestEventToDownlink(edgeEvent); + case ENTITY_MERGE_REQUEST: + return convertEntityMergeRequestEventToDownlink(edgeEvent); } return downlinkMsg; } - public DownlinkMsg processRpcCallMsgToEdge(EdgeEvent edgeEvent) { - log.trace("Executing processRpcCall, edgeEvent [{}]", edgeEvent); - DeviceRpcCallMsg deviceRpcCallMsg = - deviceMsgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody()); + private DownlinkMsg convertRpcCallEventToDownlink(EdgeEvent edgeEvent) { + log.trace("Executing convertRpcCallEventToDownlink, edgeEvent [{}]", edgeEvent); + return DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addDeviceRpcCallMsg(deviceMsgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody())) + .build(); + } + + private DownlinkMsg convertCredentialsRequestEventToDownlink(EdgeEvent edgeEvent) { + DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); + DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = DeviceCredentialsRequestMsg.newBuilder() + .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) + .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) + .build(); + DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .addDeviceCredentialsRequestMsg(deviceCredentialsRequestMsg); + return builder.build(); + } + + public DownlinkMsg convertEntityMergeRequestEventToDownlink(EdgeEvent edgeEvent) { + DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); + Device device = deviceService.findDeviceById(edgeEvent.getTenantId(), deviceId); + String conflictName = null; + if(edgeEvent.getBody() != null) { + conflictName = edgeEvent.getBody().get("conflictName").asText(); + } + DeviceUpdateMsg deviceUpdateMsg = deviceMsgConstructor + .constructDeviceUpdatedMsg(UpdateMsgType.ENTITY_MERGE_RPC_MESSAGE, device, conflictName); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceRpcCallMsg(deviceRpcCallMsg) + .addDeviceUpdateMsg(deviceUpdateMsg) .build(); } + + public ListenableFuture processDeviceNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotification(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProfileEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProfileEdgeProcessor.java index 1e01ecc208..d5d36ee640 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProfileEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceProfileEdgeProcessor.java @@ -15,16 +15,18 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -32,14 +34,15 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class DeviceProfileEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processDeviceProfileToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertDeviceProfileEventToDownlink(EdgeEvent edgeEvent) { DeviceProfileId deviceProfileId = new DeviceProfileId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: DeviceProfile deviceProfile = deviceProfileService.findDeviceProfileById(edgeEvent.getTenantId(), deviceProfileId); if (deviceProfile != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileMsgConstructor.constructDeviceProfileUpdatedMsg(msgType, deviceProfile); downlinkMsg = DownlinkMsg.newBuilder() @@ -60,4 +63,7 @@ public class DeviceProfileEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } + public ListenableFuture processDeviceProfileNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java index fdbebafc98..d4f6998926 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EdgeProcessor.java @@ -19,8 +19,11 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.CustomerId; @@ -28,6 +31,8 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @@ -40,46 +45,64 @@ import java.util.UUID; @TbCoreComponent public class EdgeProcessor extends BaseEdgeProcessor { + public DownlinkMsg convertEdgeEventToDownlink(EdgeEvent edgeEvent) { + EdgeId edgeId = new EdgeId(edgeEvent.getEntityId()); + DownlinkMsg downlinkMsg = null; + switch (edgeEvent.getAction()) { + case ASSIGNED_TO_CUSTOMER: + case UNASSIGNED_FROM_CUSTOMER: + Edge edge = edgeService.findEdgeById(edgeEvent.getTenantId(), edgeId); + if (edge != null) { + EdgeConfiguration edgeConfigMsg = + edgeMsgConstructor.constructEdgeConfiguration(edge); + downlinkMsg = DownlinkMsg.newBuilder() + .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) + .setEdgeConfiguration(edgeConfigMsg) + .build(); + } + break; + } + return downlinkMsg; + } + public ListenableFuture processEdgeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { try { EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); EdgeId edgeId = new EdgeId(new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - ListenableFuture edgeFuture; switch (actionType) { case ASSIGNED_TO_CUSTOMER: - CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); - edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId); - return Futures.transformAsync(edgeFuture, edge -> { - if (edge == null || customerId.isNullUid()) { - return Futures.immediateFuture(null); - } - List> futures = new ArrayList<>(); - futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null)); - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size()); - for (User user : pageData.getData()) { - futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null)); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } + CustomerId customerId = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), CustomerId.class); + Edge edge = edgeService.findEdgeById(tenantId, edgeId); + if (edge == null || customerId.isNullUid()) { + return Futures.immediateFuture(null); + } + List> futures = new ArrayList<>(); + futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.ADDED, customerId, null)); + futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.EDGE, EdgeEventActionType.ASSIGNED_TO_CUSTOMER, edgeId, null)); + PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); + PageData pageData; + do { + pageData = userService.findCustomerUsers(tenantId, customerId, pageLink); + if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { + log.trace("[{}] [{}] user(s) are going to be added to edge.", edge.getId(), pageData.getData().size()); + for (User user : pageData.getData()) { + futures.add(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.USER, EdgeEventActionType.ADDED, user.getId(), null)); + } + if (pageData.hasNext()) { + pageLink = pageLink.nextPageLink(); } - } while (pageData != null && pageData.hasNext()); - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - }, dbCallbackExecutorService); - case UNASSIGNED_FROM_CUSTOMER: - CustomerId customerIdToDelete = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); - edgeFuture = edgeService.findEdgeByIdAsync(tenantId, edgeId); - return Futures.transformAsync(edgeFuture, edge -> { - if (edge == null || customerIdToDelete.isNullUid()) { - return Futures.immediateFuture(null); } - return saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null); - }, dbCallbackExecutorService); + } while (pageData != null && pageData.hasNext()); + return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); + case UNASSIGNED_FROM_CUSTOMER: + CustomerId customerIdToDelete = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), CustomerId.class); + edge = edgeService.findEdgeById(tenantId, edgeId); + if (edge == null || customerIdToDelete.isNullUid()) { + return Futures.immediateFuture(null); + } + return Futures.transformAsync(saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.EDGE, EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER, edgeId, null), + voids -> saveEdgeEvent(edge.getTenantId(), edge.getId(), EdgeEventType.CUSTOMER, EdgeEventActionType.DELETED, customerIdToDelete, null), + dbCallbackExecutorService); default: return Futures.immediateFuture(null); } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java deleted file mode 100644 index 195f932b93..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityEdgeProcessor.java +++ /dev/null @@ -1,235 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.edge.rpc.processor; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import lombok.extern.slf4j.Slf4j; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.EdgeUtils; -import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EdgeId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainConnectionInfo; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DownlinkMsg; -import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.TbCoreComponent; - -import java.util.ArrayList; -import java.util.List; -import java.util.UUID; - -@Component -@Slf4j -@TbCoreComponent -public class EntityEdgeProcessor extends BaseEdgeProcessor { - - public DownlinkMsg processEntityMergeRequestMessageToEdge(Edge edge, EdgeEvent edgeEvent) { - DownlinkMsg downlinkMsg = null; - if (EdgeEventType.DEVICE.equals(edgeEvent.getType())) { - DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); - Device device = deviceService.findDeviceById(edge.getTenantId(), deviceId); - CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(device, edge); - String conflictName = null; - if(edgeEvent.getBody() != null) { - conflictName = edgeEvent.getBody().get("conflictName").asText(); - } - DeviceUpdateMsg deviceUpdateMsg = deviceMsgConstructor - .constructDeviceUpdatedMsg(UpdateMsgType.ENTITY_MERGE_RPC_MESSAGE, device, customerId, conflictName); - downlinkMsg = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceUpdateMsg(deviceUpdateMsg) - .build(); - } - return downlinkMsg; - } - - public DownlinkMsg processCredentialsRequestMessageToEdge(EdgeEvent edgeEvent) { - DownlinkMsg downlinkMsg = null; - if (EdgeEventType.DEVICE.equals(edgeEvent.getType())) { - DeviceId deviceId = new DeviceId(edgeEvent.getEntityId()); - DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = DeviceCredentialsRequestMsg.newBuilder() - .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) - .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) - .build(); - DownlinkMsg.Builder builder = DownlinkMsg.newBuilder() - .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceCredentialsRequestMsg(deviceCredentialsRequestMsg); - downlinkMsg = builder.build(); - } - return downlinkMsg; - } - - public ListenableFuture processEntityNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); - EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); - EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, - new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - EdgeId edgeId = safeGetEdgeId(edgeNotificationMsg); - switch (actionType) { - case ADDED: // used only for USER entity - case UPDATED: - case CREDENTIALS_UPDATED: - return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); - case ASSIGNED_TO_CUSTOMER: - case UNASSIGNED_FROM_CUSTOMER: - return pushNotificationToAllRelatedCustomerEdges(tenantId, edgeNotificationMsg, entityId, actionType, type); - case DELETED: - if (edgeId != null) { - return saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); - } else { - return pushNotificationToAllRelatedEdges(tenantId, entityId, type, actionType); - } - case ASSIGNED_TO_EDGE: - case UNASSIGNED_FROM_EDGE: - ListenableFuture future = saveEdgeEvent(tenantId, edgeId, type, actionType, entityId, null); - return Futures.transformAsync(future, unused -> { - if (type.equals(EdgeEventType.RULE_CHAIN)) { - return updateDependentRuleChains(tenantId, new RuleChainId(entityId.getId()), edgeId); - } else { - return Futures.immediateFuture(null); - } - }, dbCallbackExecutorService); - default: - return Futures.immediateFuture(null); - } - } - - private ListenableFuture pushNotificationToAllRelatedCustomerEdges(TenantId tenantId, - TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg, - EntityId entityId, - EdgeEventActionType actionType, - EdgeEventType type) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - List> futures = new ArrayList<>(); - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId relatedEdgeId : pageData.getData()) { - try { - CustomerId customerId = mapper.readValue(edgeNotificationMsg.getBody(), CustomerId.class); - ListenableFuture future = edgeService.findEdgeByIdAsync(tenantId, relatedEdgeId); - futures.add(Futures.transformAsync(future, edge -> { - if (edge != null && edge.getCustomerId() != null && - !edge.getCustomerId().isNullUid() && edge.getCustomerId().equals(customerId)) { - return saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null); - } else { - return Futures.immediateFuture(null); - } - }, dbCallbackExecutorService)); - } catch (Exception e) { - log.error("Can't parse customer id from entity body [{}]", edgeNotificationMsg, e); - return Futures.immediateFailedFuture(e); - } - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - } - - private EdgeId safeGetEdgeId(TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - if (edgeNotificationMsg.getEdgeIdMSB() != 0 && edgeNotificationMsg.getEdgeIdLSB() != 0) { - return new EdgeId(new UUID(edgeNotificationMsg.getEdgeIdMSB(), edgeNotificationMsg.getEdgeIdLSB())); - } else { - return null; - } - } - - private ListenableFuture pushNotificationToAllRelatedEdges(TenantId tenantId, EntityId entityId, EdgeEventType type, EdgeEventActionType actionType) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - List> futures = new ArrayList<>(); - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (EdgeId relatedEdgeId : pageData.getData()) { - futures.add(saveEdgeEvent(tenantId, relatedEdgeId, type, actionType, entityId, null)); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - } - - private ListenableFuture updateDependentRuleChains(TenantId tenantId, RuleChainId processingRuleChainId, EdgeId edgeId) { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - List> futures = new ArrayList<>(); - do { - pageData = ruleChainService.findRuleChainsByTenantIdAndEdgeId(tenantId, edgeId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - for (RuleChain ruleChain : pageData.getData()) { - if (!ruleChain.getId().equals(processingRuleChainId)) { - List connectionInfos = - ruleChainService.loadRuleChainMetaData(ruleChain.getTenantId(), ruleChain.getId()).getRuleChainConnections(); - if (connectionInfos != null && !connectionInfos.isEmpty()) { - for (RuleChainConnectionInfo connectionInfo : connectionInfos) { - if (connectionInfo.getTargetRuleChainId().equals(processingRuleChainId)) { - futures.add(saveEdgeEvent(tenantId, - edgeId, - EdgeEventType.RULE_CHAIN_METADATA, - EdgeEventActionType.UPDATED, - ruleChain.getId(), - null)); - } - } - } - } - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - } - - public ListenableFuture processEntityNotificationForAllEdges(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { - EdgeEventActionType actionType = EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()); - EdgeEventType type = EdgeEventType.valueOf(edgeNotificationMsg.getType()); - EntityId entityId = EntityIdFactory.getByEdgeEventTypeAndUuid(type, new UUID(edgeNotificationMsg.getEntityIdMSB(), edgeNotificationMsg.getEntityIdLSB())); - switch (actionType) { - case ADDED: - case UPDATED: - case DELETED: - return processActionForAllEdges(tenantId, type, actionType, entityId); - default: - return Futures.immediateFuture(null); - } - } -} - diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityViewEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityViewEdgeProcessor.java index e5d69e36cf..b89ce15a5d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityViewEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/EntityViewEdgeProcessor.java @@ -15,18 +15,18 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -34,10 +34,10 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class EntityViewEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processEntityViewToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertEntityViewEventToDownlink(EdgeEvent edgeEvent) { EntityViewId entityViewId = new EntityViewId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: case ASSIGNED_TO_EDGE: @@ -45,9 +45,9 @@ public class EntityViewEdgeProcessor extends BaseEdgeProcessor { case UNASSIGNED_FROM_CUSTOMER: EntityView entityView = entityViewService.findEntityViewById(edgeEvent.getTenantId(), entityViewId); if (entityView != null) { - CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(entityView, edge); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); EntityViewUpdateMsg entityViewUpdateMsg = - entityViewMsgConstructor.constructEntityViewUpdatedMsg(msgType, entityView, customerId); + entityViewMsgConstructor.constructEntityViewUpdatedMsg(msgType, entityView); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) .addEntityViewUpdateMsg(entityViewUpdateMsg) @@ -66,4 +66,8 @@ public class EntityViewEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } + + public ListenableFuture processEntityViewNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotification(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java index 0ccce4c346..37ae5142af 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/OtaPackageEdgeProcessor.java @@ -15,16 +15,18 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.OtaPackage; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -32,14 +34,15 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processOtaPackageToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertOtaPackageEventToDownlink(EdgeEvent edgeEvent) { OtaPackageId otaPackageId = new OtaPackageId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: OtaPackage otaPackage = otaPackageService.findOtaPackageById(edgeEvent.getTenantId(), otaPackageId); if (otaPackage != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); OtaPackageUpdateMsg otaPackageUpdateMsg = otaPackageMsgConstructor.constructOtaPackageUpdatedMsg(msgType, otaPackage); downlinkMsg = DownlinkMsg.newBuilder() @@ -60,4 +63,7 @@ public class OtaPackageEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } + public ListenableFuture processOtaPackageNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java index 2993c651da..6a7e66a58a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/QueueEdgeProcessor.java @@ -15,16 +15,18 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.QueueId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -32,14 +34,15 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class QueueEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processQueueToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertQueueEventToDownlink(EdgeEvent edgeEvent) { QueueId queueId = new QueueId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: Queue queue = queueService.findQueueById(edgeEvent.getTenantId(), queueId); if (queue != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); QueueUpdateMsg queueUpdateMsg = queueMsgConstructor.constructQueueUpdatedMsg(msgType, queue); downlinkMsg = DownlinkMsg.newBuilder() @@ -60,4 +63,7 @@ public class QueueEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } + public ListenableFuture processQueueNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java index f86ea8aad2..2998001a0a 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RelationEdgeProcessor.java @@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -37,8 +38,6 @@ import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -75,7 +74,7 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor { if (relationUpdateMsg.hasTypeGroup()) { entityRelation.setTypeGroup(RelationTypeGroup.valueOf(relationUpdateMsg.getTypeGroup())); } - entityRelation.setAdditionalInfo(mapper.readTree(relationUpdateMsg.getAdditionalInfo())); + entityRelation.setAdditionalInfo(JacksonUtil.OBJECT_MAPPER.readTree(relationUpdateMsg.getAdditionalInfo())); switch (relationUpdateMsg.getMsgType()) { case ENTITY_CREATED_RPC_MESSAGE: case ENTITY_UPDATED_RPC_MESSAGE: @@ -117,8 +116,9 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor { } } - public DownlinkMsg processRelationToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType) { - EntityRelation entityRelation = mapper.convertValue(edgeEvent.getBody(), EntityRelation.class); + public DownlinkMsg convertRelationEventToDownlink(EdgeEvent edgeEvent) { + EntityRelation entityRelation = JacksonUtil.OBJECT_MAPPER.convertValue(edgeEvent.getBody(), EntityRelation.class); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); RelationUpdateMsg relationUpdateMsg = relationMsgConstructor.constructRelationUpdatedMsg(msgType, entityRelation); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) @@ -127,15 +127,15 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor { } public ListenableFuture processRelationNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) throws JsonProcessingException { - EntityRelation relation = mapper.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); + EntityRelation relation = JacksonUtil.OBJECT_MAPPER.readValue(edgeNotificationMsg.getBody(), EntityRelation.class); if (relation.getFrom().getEntityType().equals(EntityType.EDGE) || relation.getTo().getEntityType().equals(EntityType.EDGE)) { return Futures.immediateFuture(null); } Set uniqueEdgeIds = new HashSet<>(); - uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getTo())); - uniqueEdgeIds.addAll(findRelatedEdgeIds(tenantId, relation.getFrom())); + uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getTo())); + uniqueEdgeIds.addAll(edgeService.findAllRelatedEdgeIds(tenantId, relation.getFrom())); if (uniqueEdgeIds.isEmpty()) { return Futures.immediateFuture(null); } @@ -146,24 +146,8 @@ public class RelationEdgeProcessor extends BaseEdgeProcessor { EdgeEventType.RELATION, EdgeEventActionType.valueOf(edgeNotificationMsg.getAction()), null, - mapper.valueToTree(relation))); + JacksonUtil.OBJECT_MAPPER.valueToTree(relation))); } return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); } - - private List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { - List result = new ArrayList<>(); - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = edgeService.findRelatedEdgeIdsByEntityId(tenantId, entityId, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - result.addAll(pageData.getData()); - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - return result; - } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java index 5620dc9a7c..5704e7d30d 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/RuleChainEdgeProcessor.java @@ -15,13 +15,14 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; @@ -29,6 +30,7 @@ import org.thingsboard.server.gen.edge.v1.EdgeVersion; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -36,15 +38,16 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class RuleChainEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processRuleChainToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType action) { + public DownlinkMsg convertRuleChainEventToDownlink(Edge edge, EdgeEvent edgeEvent) { RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (action) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: case ASSIGNED_TO_EDGE: RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId); if (ruleChain != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainMsgConstructor.constructRuleChainUpdatedMsg(edge.getRootRuleChainId(), msgType, ruleChain); downlinkMsg = DownlinkMsg.newBuilder() @@ -64,12 +67,13 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } - public DownlinkMsg processRuleChainMetadataToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeVersion edgeVersion) { + public DownlinkMsg convertRuleChainMetadataEventToDownlink(EdgeEvent edgeEvent, EdgeVersion edgeVersion) { RuleChainId ruleChainId = new RuleChainId(edgeEvent.getEntityId()); RuleChain ruleChain = ruleChainService.findRuleChainById(edgeEvent.getTenantId(), ruleChainId); DownlinkMsg downlinkMsg = null; if (ruleChain != null) { RuleChainMetaData ruleChainMetaData = ruleChainService.loadRuleChainMetaData(edgeEvent.getTenantId(), ruleChainId); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ruleChainMsgConstructor.constructRuleChainMetadataUpdatedMsg(edgeEvent.getTenantId(), msgType, ruleChainMetaData, edgeVersion); if (ruleChainMetadataUpdateMsg != null) { @@ -81,4 +85,8 @@ public class RuleChainEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } + + public ListenableFuture processRuleChainNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotification(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java index 171d5518fc..722b33b426 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/TelemetryEdgeProcessor.java @@ -17,7 +17,6 @@ package org.thingsboard.server.service.edge.rpc.processor; import com.fasterxml.jackson.core.JsonProcessingException; import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.gson.Gson; @@ -27,6 +26,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; import org.springframework.stereotype.Component; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.msg.DeviceAttributesEventNotificationMsg; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -35,8 +35,8 @@ import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.id.AssetId; @@ -48,7 +48,6 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.kv.AttributeKey; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.msg.TbMsg; @@ -58,7 +57,7 @@ import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.session.SessionMsgType; import org.thingsboard.server.common.transport.adaptor.JsonConverter; import org.thingsboard.server.common.transport.util.JsonUtils; -import org.thingsboard.server.controller.BaseController; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.EntityDataProto; @@ -75,7 +74,6 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.UUID; @Component @Slf4j @@ -91,19 +89,21 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { tbCoreMsgProducer = producerProvider.getTbCoreMsgProducer(); } - public List> processTelemetryFromEdge(TenantId tenantId, CustomerId customerId, EntityDataProto entityData) { - log.trace("[{}] onTelemetryUpdate [{}]", tenantId, entityData); + public List> processTelemetryFromEdge(TenantId tenantId, EntityDataProto entityData) { + log.trace("[{}] processTelemetryFromEdge [{}]", tenantId, entityData); List> result = new ArrayList<>(); - EntityId entityId = constructEntityId(entityData); + EntityId entityId = constructEntityId(entityData.getEntityType(), entityData.getEntityIdMSB(), entityData.getEntityIdLSB()); if ((entityData.hasPostAttributesMsg() || entityData.hasPostTelemetryMsg() || entityData.hasAttributesUpdatedMsg()) && entityId != null) { - TbMsgMetaData metaData = constructBaseMsgMetadata(tenantId, entityId); + Pair pair = getBaseMsgMetadataAndCustomerId(tenantId, entityId); + TbMsgMetaData metaData = pair.getKey(); + CustomerId customerId = pair.getValue(); metaData.putValue(DataConstants.MSG_SOURCE_KEY, DataConstants.EDGE_MSG_SOURCE); if (entityData.hasPostAttributesMsg()) { result.add(processPostAttributes(tenantId, customerId, entityId, entityData.getPostAttributesMsg(), metaData)); } if (entityData.hasAttributesUpdatedMsg()) { metaData.putValue("scope", entityData.getPostAttributeScope()); - result.add(processAttributesUpdate(tenantId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); + result.add(processAttributesUpdate(tenantId, customerId, entityId, entityData.getAttributesUpdatedMsg(), metaData)); } if (entityData.hasPostTelemetryMsg()) { result.add(processPostTelemetry(tenantId, customerId, entityId, entityData.getPostTelemetryMsg(), metaData)); @@ -111,12 +111,16 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { if (EntityType.DEVICE.equals(entityId.getEntityType())) { DeviceId deviceId = new DeviceId(entityId.getId()); + long currentTs = System.currentTimeMillis(); + TransportProtos.DeviceActivityProto deviceActivityMsg = TransportProtos.DeviceActivityProto.newBuilder() .setTenantIdMSB(tenantId.getId().getMostSignificantBits()) .setTenantIdLSB(tenantId.getId().getLeastSignificantBits()) .setDeviceIdMSB(deviceId.getId().getMostSignificantBits()) .setDeviceIdLSB(deviceId.getId().getLeastSignificantBits()) - .setLastActivityTime(System.currentTimeMillis()).build(); + .setLastActivityTime(currentTs).build(); + + log.trace("[{}][{}] device activity time is going to be updated, ts {}", tenantId, deviceId, currentTs); TopicPartitionInfo tpi = partitionService.resolve(ServiceType.TB_CORE, tenantId, deviceId); tbCoreMsgProducer.send(tpi, new TbProtoQueueMsg<>(deviceId.getId(), @@ -129,12 +133,14 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { return result; } - private TbMsgMetaData constructBaseMsgMetadata(TenantId tenantId, EntityId entityId) { + private Pair getBaseMsgMetadataAndCustomerId(TenantId tenantId, EntityId entityId) { TbMsgMetaData metaData = new TbMsgMetaData(); + CustomerId customerId = null; switch (entityId.getEntityType()) { case DEVICE: Device device = deviceService.findDeviceById(tenantId, new DeviceId(entityId.getId())); if (device != null) { + customerId = device.getCustomerId(); metaData.putValue("deviceName", device.getName()); metaData.putValue("deviceType", device.getType()); } @@ -142,6 +148,7 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { case ASSET: Asset asset = assetService.findAssetById(tenantId, new AssetId(entityId.getId())); if (asset != null) { + customerId = asset.getCustomerId(); metaData.putValue("assetName", asset.getName()); metaData.putValue("assetType", asset.getType()); } @@ -149,38 +156,24 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { case ENTITY_VIEW: EntityView entityView = entityViewService.findEntityViewById(tenantId, new EntityViewId(entityId.getId())); if (entityView != null) { + customerId = entityView.getCustomerId(); metaData.putValue("entityViewName", entityView.getName()); metaData.putValue("entityViewType", entityView.getType()); } break; + case EDGE: + Edge edge = edgeService.findEdgeById(tenantId, new EdgeId(entityId.getId())); + if (edge != null) { + customerId = edge.getCustomerId(); + metaData.putValue("edgeName", edge.getName()); + metaData.putValue("edgeType", edge.getType()); + } + break; default: log.debug("Using empty metadata for entityId [{}]", entityId); break; } - return metaData; - } - - private Pair getDefaultQueueNameAndRuleChainId(TenantId tenantId, EntityId entityId) { - RuleChainId ruleChainId = null; - String queueName = null; - if (EntityType.DEVICE.equals(entityId.getEntityType())) { - DeviceProfile deviceProfile = deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())); - if (deviceProfile == null) { - log.warn("[{}] Device profile is null!", entityId); - } else { - ruleChainId = deviceProfile.getDefaultRuleChainId(); - queueName = deviceProfile.getDefaultQueueName(); - } - } else if (EntityType.ASSET.equals(entityId.getEntityType())) { - AssetProfile assetProfile = assetProfileCache.get(tenantId, new AssetId(entityId.getId())); - if (assetProfile == null) { - log.warn("[{}] Asset profile is null!", entityId); - } else { - ruleChainId = assetProfile.getDefaultRuleChainId(); - queueName = assetProfile.getDefaultQueueName(); - } - } - return new ImmutablePair<>(queueName, ruleChainId); + return new ImmutablePair<>(metaData, customerId != null ? customerId : new CustomerId(ModelConstants.NULL_UUID)); } private ListenableFuture processPostTelemetry(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostTelemetryMsg msg, TbMsgMetaData metaData) { @@ -206,6 +199,29 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { return futureToSet; } + private Pair getDefaultQueueNameAndRuleChainId(TenantId tenantId, EntityId entityId) { + RuleChainId ruleChainId = null; + String queueName = null; + if (EntityType.DEVICE.equals(entityId.getEntityType())) { + DeviceProfile deviceProfile = deviceProfileCache.get(tenantId, new DeviceId(entityId.getId())); + if (deviceProfile == null) { + log.warn("[{}] Device profile is null!", entityId); + } else { + ruleChainId = deviceProfile.getDefaultRuleChainId(); + queueName = deviceProfile.getDefaultQueueName(); + } + } else if (EntityType.ASSET.equals(entityId.getEntityType())) { + AssetProfile assetProfile = assetProfileCache.get(tenantId, new AssetId(entityId.getId())); + if (assetProfile == null) { + log.warn("[{}] Asset profile is null!", entityId); + } else { + ruleChainId = assetProfile.getDefaultRuleChainId(); + queueName = assetProfile.getDefaultQueueName(); + } + } + return new ImmutablePair<>(queueName, ruleChainId); + } + private ListenableFuture processPostAttributes(TenantId tenantId, CustomerId customerId, EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) { SettableFuture futureToSet = SettableFuture.create(); JsonObject json = JsonUtils.getJsonObject(msg.getKvList()); @@ -227,6 +243,7 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { } private ListenableFuture processAttributesUpdate(TenantId tenantId, + CustomerId customerId, EntityId entityId, TransportProtos.PostAttributeMsg msg, TbMsgMetaData metaData) { @@ -237,26 +254,34 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { tsSubService.saveAndNotify(tenantId, entityId, scope, attributes, new FutureCallback() { @Override public void onSuccess(@Nullable Void tmp) { - logAttributesUpdated(tenantId, entityId, scope, attributes, null); - futureToSet.set(null); + var defaultQueueAndRuleChain = getDefaultQueueNameAndRuleChainId(tenantId, entityId); + TbMsg tbMsg = TbMsg.newMsg(defaultQueueAndRuleChain.getKey(), DataConstants.ATTRIBUTES_UPDATED, entityId, + customerId, metaData, gson.toJson(json), defaultQueueAndRuleChain.getValue(), null); + tbClusterService.pushMsgToRuleEngine(tenantId, tbMsg.getOriginator(), tbMsg, new TbQueueCallback() { + @Override + public void onSuccess(TbQueueMsgMetadata metadata) { + futureToSet.set(null); + } + + @Override + public void onFailure(Throwable t) { + log.error("Can't process attributes update [{}]", msg, t); + futureToSet.setException(t); + } + }); } @Override public void onFailure(Throwable t) { log.error("Can't process attributes update [{}]", msg, t); - logAttributesUpdated(tenantId, entityId, scope, attributes, t); futureToSet.setException(t); } }); return futureToSet; } - private void logAttributesUpdated(TenantId tenantId, EntityId entityId, String scope, List attributes, Throwable e) { - notificationEntityService.logEntityAction(tenantId, entityId, ActionType.ATTRIBUTES_UPDATED, null, - BaseController.toException(e), scope, attributes); - } - - private ListenableFuture processAttributeDeleteMsg(TenantId tenantId, EntityId entityId, AttributeDeleteMsg attributeDeleteMsg, String entityType) { + private ListenableFuture processAttributeDeleteMsg(TenantId tenantId, EntityId entityId, AttributeDeleteMsg attributeDeleteMsg, + String entityType) { SettableFuture futureToSet = SettableFuture.create(); String scope = attributeDeleteMsg.getScope(); List attributeNames = attributeDeleteMsg.getAttributeNamesList(); @@ -283,30 +308,7 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { return futureToSet; } - private EntityId constructEntityId(EntityDataProto entityData) { - EntityType entityType = EntityType.valueOf(entityData.getEntityType()); - switch (entityType) { - case DEVICE: - return new DeviceId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); - case ASSET: - return new AssetId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); - case ENTITY_VIEW: - return new EntityViewId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); - case DASHBOARD: - return new DashboardId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); - case TENANT: - return TenantId.fromUUID(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); - case CUSTOMER: - return new CustomerId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); - case USER: - return new UserId(new UUID(entityData.getEntityIdMSB(), entityData.getEntityIdLSB())); - default: - log.warn("Unsupported entity type [{}] during construct of entity id. EntityDataProto [{}]", entityData.getEntityType(), entityData); - return null; - } - } - - public DownlinkMsg processTelemetryMessageToEdge(EdgeEvent edgeEvent) throws JsonProcessingException { + public DownlinkMsg convertTelemetryEventToDownlink(EdgeEvent edgeEvent) throws JsonProcessingException { EntityId entityId; switch (edgeEvent.getType()) { case DEVICE: @@ -334,7 +336,8 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { log.warn("Unsupported edge event type [{}]", edgeEvent); return null; } - return constructEntityDataProtoMsg(entityId, edgeEvent.getAction(), JsonUtils.parse(mapper.writeValueAsString(edgeEvent.getBody()))); + return constructEntityDataProtoMsg(entityId, edgeEvent.getAction(), + JsonUtils.parse(JacksonUtil.OBJECT_MAPPER.writeValueAsString(edgeEvent.getBody()))); } private DownlinkMsg constructEntityDataProtoMsg(EntityId entityId, EdgeEventActionType actionType, JsonElement entityData) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/UserEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/UserEdgeProcessor.java index a64fb133f1..390fafe464 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/UserEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/UserEdgeProcessor.java @@ -15,19 +15,19 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -35,18 +35,18 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class UserEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processUserToEdge(Edge edge, EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) { + public DownlinkMsg convertUserEventToDownlink(EdgeEvent edgeEvent) { UserId userId = new UserId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (edgeEdgeEventActionType) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: User user = userService.findUserById(edgeEvent.getTenantId(), userId); if (user != null) { - CustomerId customerId = getCustomerIdIfEdgeAssignedToCustomer(user, edge); + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); downlinkMsg = DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addUserUpdateMsg(userMsgConstructor.constructUserUpdatedMsg(msgType, user, customerId)) + .addUserUpdateMsg(userMsgConstructor.constructUserUpdatedMsg(msgType, user)) .build(); } break; @@ -57,7 +57,7 @@ public class UserEdgeProcessor extends BaseEdgeProcessor { .build(); break; case CREDENTIALS_UPDATED: - UserCredentials userCredentialsByUserId = userService.findUserCredentialsByUserId(edge.getTenantId(), userId); + UserCredentials userCredentialsByUserId = userService.findUserCredentialsByUserId(edgeEvent.getTenantId(), userId); if (userCredentialsByUserId != null && userCredentialsByUserId.isEnabled()) { UserCredentialsUpdateMsg userCredentialsUpdateMsg = userMsgConstructor.constructUserCredentialsUpdatedMsg(userCredentialsByUserId); @@ -70,4 +70,7 @@ public class UserEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } + public ListenableFuture processUserNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetBundleEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetBundleEdgeProcessor.java index ff43e47c62..b74e3c1cec 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetBundleEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetBundleEdgeProcessor.java @@ -15,16 +15,18 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.widget.WidgetsBundle; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -32,14 +34,15 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processWidgetsBundleToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) { + public DownlinkMsg convertWidgetsBundleEventToDownlink(EdgeEvent edgeEvent) { WidgetsBundleId widgetsBundleId = new WidgetsBundleId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (edgeEdgeEventActionType) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: WidgetsBundle widgetsBundle = widgetsBundleService.findWidgetsBundleById(edgeEvent.getTenantId(), widgetsBundleId); if (widgetsBundle != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = widgetsBundleMsgConstructor.constructWidgetsBundleUpdateMsg(msgType, widgetsBundle); downlinkMsg = DownlinkMsg.newBuilder() @@ -59,4 +62,8 @@ public class WidgetBundleEdgeProcessor extends BaseEdgeProcessor { } return downlinkMsg; } + + public ListenableFuture processWidgetsBundleNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java index dc5acb726a..10807b7b5c 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/WidgetTypeEdgeProcessor.java @@ -15,16 +15,18 @@ */ package org.thingsboard.server.service.edge.rpc.processor; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.WidgetTypeId; import org.thingsboard.server.common.data.widget.WidgetTypeDetails; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; +import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.util.TbCoreComponent; @Component @@ -32,14 +34,15 @@ import org.thingsboard.server.queue.util.TbCoreComponent; @TbCoreComponent public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { - public DownlinkMsg processWidgetTypeToEdge(EdgeEvent edgeEvent, UpdateMsgType msgType, EdgeEventActionType edgeEdgeEventActionType) { + public DownlinkMsg convertWidgetTypeEventToDownlink(EdgeEvent edgeEvent) { WidgetTypeId widgetTypeId = new WidgetTypeId(edgeEvent.getEntityId()); DownlinkMsg downlinkMsg = null; - switch (edgeEdgeEventActionType) { + switch (edgeEvent.getAction()) { case ADDED: case UPDATED: WidgetTypeDetails widgetTypeDetails = widgetTypeService.findWidgetTypeDetailsById(edgeEvent.getTenantId(), widgetTypeId); if (widgetTypeDetails != null) { + UpdateMsgType msgType = getUpdateMsgType(edgeEvent.getAction()); WidgetTypeUpdateMsg widgetTypeUpdateMsg = widgetTypeMsgConstructor.constructWidgetTypeUpdateMsg(msgType, widgetTypeDetails); downlinkMsg = DownlinkMsg.newBuilder() @@ -60,4 +63,7 @@ public class WidgetTypeEdgeProcessor extends BaseEdgeProcessor { return downlinkMsg; } + public ListenableFuture processWidgetTypeNotification(TenantId tenantId, TransportProtos.EdgeNotificationMsgProto edgeNotificationMsg) { + return processEntityNotificationForAllEdges(tenantId, edgeNotificationMsg); + } } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java index 8db5c4f4c7..9210d3ceec 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/DefaultEdgeRequestsService.java @@ -16,7 +16,6 @@ package org.thingsboard.server.service.edge.rpc.sync; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; @@ -27,21 +26,16 @@ import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.cluster.TbClusterService; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.AssetProfileId; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; @@ -51,8 +45,6 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.id.WidgetsBundleId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntityRelationsQuery; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -69,10 +61,8 @@ import org.thingsboard.server.dao.edge.EdgeEventService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.widget.WidgetTypeService; import org.thingsboard.server.dao.widget.WidgetsBundleService; -import org.thingsboard.server.gen.edge.v1.AssetProfileAssetsRequestMsg; import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; -import org.thingsboard.server.gen.edge.v1.DeviceProfileDevicesRequestMsg; import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg; import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; @@ -94,8 +84,6 @@ import java.util.UUID; @Slf4j public class DefaultEdgeRequestsService implements EdgeRequestsService { - private static final ObjectMapper mapper = new ObjectMapper(); - private static final int DEFAULT_PAGE_SIZE = 1000; @Autowired @@ -175,7 +163,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { try { Map entityData = new HashMap<>(); - ObjectNode attributes = mapper.createObjectNode(); + ObjectNode attributes = JacksonUtil.OBJECT_MAPPER.createObjectNode(); for (AttributeKvEntry attr : ssAttributes) { if (DefaultDeviceStateService.PERSISTENT_ATTRIBUTES.contains(attr.getKey())) { continue; @@ -192,7 +180,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { } entityData.put("kv", attributes); entityData.put("scope", scope); - JsonNode body = mapper.valueToTree(entityData); + JsonNode body = JacksonUtil.OBJECT_MAPPER.valueToTree(entityData); log.debug("Sending attributes data msg, entityId [{}], attributes [{}]", entityId, body); ListenableFuture future = saveEdgeEvent(tenantId, edge.getId(), type, EdgeEventActionType.ATTRIBUTES_UPDATED, entityId, body); Futures.addCallback(future, new FutureCallback<>() { @@ -254,7 +242,7 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { EdgeEventType.RELATION, EdgeEventActionType.ADDED, null, - mapper.valueToTree(relation))); + JacksonUtil.OBJECT_MAPPER.valueToTree(relation))); } } catch (Exception e) { String errMsg = String.format("[%s] Exception during loading relation [%s] to edge on sync!", edge.getId(), relation); @@ -325,82 +313,6 @@ public class DefaultEdgeRequestsService implements EdgeRequestsService { EdgeEventActionType.CREDENTIALS_UPDATED, userId, null); } - @Override - public ListenableFuture processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg) { - log.trace("[{}] processDeviceProfileDevicesRequestMsg [{}][{}]", tenantId, edge.getName(), deviceProfileDevicesRequestMsg); - if (deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB() == 0 || deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB() == 0) { - return Futures.immediateFuture(null); - } - DeviceProfileId deviceProfileId = new DeviceProfileId(new UUID(deviceProfileDevicesRequestMsg.getDeviceProfileIdMSB(), deviceProfileDevicesRequestMsg.getDeviceProfileIdLSB())); - DeviceProfile deviceProfileById = deviceProfileService.findDeviceProfileById(tenantId, deviceProfileId); - if (deviceProfileById == null) { - return Futures.immediateFuture(null); - } - return syncDevices(tenantId, edge, deviceProfileById.getName()); - } - - private ListenableFuture syncDevices(TenantId tenantId, Edge edge, String deviceType) { - log.trace("[{}] syncDevices [{}][{}]", tenantId, edge.getName(), deviceType); - List> futures = new ArrayList<>(); - try { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = deviceService.findDevicesByTenantIdAndEdgeIdAndType(tenantId, edge.getId(), deviceType, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - log.trace("[{}] [{}] device(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); - for (Device device : pageData.getData()) { - futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.DEVICE, EdgeEventActionType.ADDED, device.getId(), null)); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - } catch (Exception e) { - log.error("Exception during loading edge device(s) on sync!", e); - } - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - } - - @Override - public ListenableFuture processAssetProfileAssetsRequestMsg(TenantId tenantId, Edge edge, AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg) { - log.trace("[{}] processAssetProfileAssetsRequestMsg [{}][{}]", tenantId, edge.getName(), assetProfileAssetsRequestMsg); - if (assetProfileAssetsRequestMsg.getAssetProfileIdMSB() == 0 || assetProfileAssetsRequestMsg.getAssetProfileIdLSB() == 0) { - return Futures.immediateFuture(null); - } - AssetProfileId assetProfileId = new AssetProfileId(new UUID(assetProfileAssetsRequestMsg.getAssetProfileIdMSB(), assetProfileAssetsRequestMsg.getAssetProfileIdLSB())); - AssetProfile assetProfileById = assetProfileService.findAssetProfileById(tenantId, assetProfileId); - if (assetProfileById == null) { - return Futures.immediateFuture(null); - } - return syncAssets(tenantId, edge, assetProfileById.getName()); - } - - private ListenableFuture syncAssets(TenantId tenantId, Edge edge, String assetType) { - log.trace("[{}] syncAssets [{}][{}]", tenantId, edge.getName(), assetType); - List> futures = new ArrayList<>(); - try { - PageLink pageLink = new PageLink(DEFAULT_PAGE_SIZE); - PageData pageData; - do { - pageData = assetService.findAssetsByTenantIdAndEdgeIdAndType(tenantId, edge.getId(), assetType, pageLink); - if (pageData != null && pageData.getData() != null && !pageData.getData().isEmpty()) { - log.trace("[{}] [{}] asset(s) are going to be pushed to edge.", edge.getId(), pageData.getData().size()); - for (Asset asset : pageData.getData()) { - futures.add(saveEdgeEvent(tenantId, edge.getId(), EdgeEventType.ASSET, EdgeEventActionType.ADDED, asset.getId(), null)); - } - if (pageData.hasNext()) { - pageLink = pageLink.nextPageLink(); - } - } - } while (pageData != null && pageData.hasNext()); - } catch (Exception e) { - log.error("Exception during loading edge asset(s) on sync!", e); - } - return Futures.transform(Futures.allAsList(futures), voids -> null, dbCallbackExecutorService); - } - @Override public ListenableFuture processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg) { diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java index 7140ac52a5..4d512234bd 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/sync/EdgeRequestsService.java @@ -18,10 +18,8 @@ package org.thingsboard.server.service.edge.rpc.sync; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.gen.edge.v1.AssetProfileAssetsRequestMsg; import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; -import org.thingsboard.server.gen.edge.v1.DeviceProfileDevicesRequestMsg; import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg; import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; @@ -40,10 +38,6 @@ public interface EdgeRequestsService { ListenableFuture processUserCredentialsRequestMsg(TenantId tenantId, Edge edge, UserCredentialsRequestMsg userCredentialsRequestMsg); - ListenableFuture processDeviceProfileDevicesRequestMsg(TenantId tenantId, Edge edge, DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg); - - ListenableFuture processAssetProfileAssetsRequestMsg(TenantId tenantId, Edge edge, AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg); - ListenableFuture processWidgetBundleTypesRequestMsg(TenantId tenantId, Edge edge, WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg); ListenableFuture processEntityViewsRequestMsg(TenantId tenantId, Edge edge, EntityViewsRequestMsg entityViewsRequestMsg); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java index 0e66e365da..9f34c061fe 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/AbstractTbEntityService.java @@ -29,12 +29,10 @@ import org.thingsboard.server.common.data.alarm.AlarmQuery; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.dao.customer.CustomerService; @@ -42,9 +40,8 @@ import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.service.executors.DbCallbackExecutorService; import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; +import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; -import java.util.ArrayList; -import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.UUID; @@ -53,14 +50,9 @@ import java.util.stream.Collectors; @Slf4j public abstract class AbstractTbEntityService { - protected static final int DEFAULT_PAGE_SIZE = 1000; - @Value("${server.log_controller_error_stack_trace}") @Getter private boolean logControllerErrorStackTrace; - @Value("${edges.enabled}") - @Getter - protected boolean edgesEnabled; @Autowired protected DbCallbackExecutorService dbExecutor; @@ -71,6 +63,8 @@ public abstract class AbstractTbEntityService { @Autowired protected AlarmService alarmService; @Autowired + protected AlarmSubscriptionService alarmSubscriptionService; + @Autowired protected CustomerService customerService; @Autowired protected TbClusterService tbClusterService; @@ -113,22 +107,6 @@ public abstract class AbstractTbEntityService { } } - protected List findRelatedEdgeIds(TenantId tenantId, EntityId entityId) { - if (!edgesEnabled) { - return null; - } - if (EntityType.EDGE.equals(entityId.getEntityType())) { - return Collections.singletonList(new EdgeId(entityId.getId())); - } - PageDataIterableByTenantIdEntityId relatedEdgeIdsIterator = - new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE); - List result = new ArrayList<>(); - for (EdgeId edgeId : relatedEdgeIdsIterator) { - result.add(edgeId); - } - return result; - } - protected I emptyId(EntityType entityType) { return (I) EntityIdFactory.getByTypeAndUuid(entityType, ModelConstants.NULL_UUID); } diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java index 187d8deabd..8f8531a5b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/DefaultTbNotificationEntityService.java @@ -102,7 +102,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS List relatedEdgeIds, User user, Object... additionalInfo) { logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); - sendDeleteNotificationMsg(tenantId, entityId, entity, relatedEdgeIds); + sendDeleteNotificationMsg(tenantId, entityId, relatedEdgeIds, null); } @Override @@ -135,7 +135,7 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS logEntityAction(tenantId, entityId, entity, customerId, actionType, user, additionalInfo); if (sendToEdge) { - sendEntityAssignToCustomerNotificationMsg(tenantId, entityId, customerId, edgeTypeByActionType(actionType)); + sendEntityNotificationMsg(tenantId, entityId, edgeTypeByActionType(actionType), JacksonUtil.toString(customerId)); } } @@ -203,10 +203,9 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS } @Override - public void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, - ActionType actionType, User user, Object... additionalInfo) { + public void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, + ActionType actionType, User user, Object... additionalInfo) { ComponentLifecycleEvent lifecycleEvent; - EdgeEventActionType edgeEventActionType = null; switch (actionType) { case ADDED: lifecycleEvent = ComponentLifecycleEvent.CREATED; @@ -214,28 +213,14 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS case UPDATED: lifecycleEvent = ComponentLifecycleEvent.UPDATED; break; - case ASSIGNED_TO_CUSTOMER: - lifecycleEvent = ComponentLifecycleEvent.UPDATED; - edgeEventActionType = EdgeEventActionType.ASSIGNED_TO_CUSTOMER; - break; - case UNASSIGNED_FROM_CUSTOMER: - lifecycleEvent = ComponentLifecycleEvent.UPDATED; - edgeEventActionType = EdgeEventActionType.UNASSIGNED_FROM_CUSTOMER; - break; case DELETED: lifecycleEvent = ComponentLifecycleEvent.DELETED; break; default: throw new IllegalArgumentException("Unknown actionType: " + actionType); } - tbClusterService.broadcastEntityStateChangeEvent(tenantId, edgeId, lifecycleEvent); logEntityAction(tenantId, edgeId, edge, customerId, actionType, user, additionalInfo); - - //Send notification to edge - if (edgeEventActionType != null) { - sendEntityAssignToCustomerNotificationMsg(tenantId, edgeId, customerId, edgeEventActionType); - } } @Override @@ -260,42 +245,22 @@ public class DefaultTbNotificationEntityService implements TbNotificationEntityS ActionType actionType, Object... additionalInfo) { logEntityAction(tenantId, relation.getFrom(), null, customerId, actionType, user, additionalInfo); logEntityAction(tenantId, relation.getTo(), null, customerId, actionType, user, additionalInfo); - try { - if (!relation.getFrom().getEntityType().equals(EntityType.EDGE) && !relation.getTo().getEntityType().equals(EntityType.EDGE)) { - sendNotificationMsgToEdge(tenantId, null, null, JacksonUtil.toString(relation), - EdgeEventType.RELATION, edgeTypeByActionType(actionType)); - } - } catch (Exception e) { - log.warn("Failed to push relation to core: {}", relation, e); + if (!EntityType.EDGE.equals(relation.getFrom().getEntityType()) && !EntityType.EDGE.equals(relation.getTo().getEntityType())) { + sendNotificationMsgToEdge(tenantId, null, null, JacksonUtil.toString(relation), + EdgeEventType.RELATION, edgeTypeByActionType(actionType)); } } private void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action) { - sendNotificationMsgToEdge(tenantId, null, entityId, null, null, action); + sendEntityNotificationMsg(tenantId, entityId, action, null); } - private void sendEntityAssignToCustomerNotificationMsg(TenantId tenantId, EntityId entityId, CustomerId customerId, EdgeEventActionType action) { - try { - sendNotificationMsgToEdge(tenantId, null, entityId, JacksonUtil.toString(customerId), null, action); - } catch (Exception e) { - log.warn("Failed to push assign/unassign to/from customer to core: {}", customerId, e); - } + private void sendEntityNotificationMsg(TenantId tenantId, EntityId entityId, EdgeEventActionType action, String body) { + sendNotificationMsgToEdge(tenantId, null, entityId, body, null, action); } private void sendAlarmDeleteNotificationMsg(TenantId tenantId, Alarm alarm, List edgeIds, String body) { - try { - sendDeleteNotificationMsg(tenantId, alarm.getId(), edgeIds, body); - } catch (Exception e) { - log.warn("Failed to push delete msg to core: {}", alarm, e); - } - } - - private void sendDeleteNotificationMsg(TenantId tenantId, I entityId, E entity, List edgeIds) { - try { - sendDeleteNotificationMsg(tenantId, entityId, edgeIds, null); - } catch (Exception e) { - log.warn("Failed to push delete msg to core: {}", entity, e); - } + sendDeleteNotificationMsg(tenantId, alarm.getId(), edgeIds, body); } private void sendDeleteNotificationMsg(TenantId tenantId, EntityId entityId, List edgeIds, String body) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java index 7e929254a6..dc6e148cab 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/TbNotificationEntityService.java @@ -97,8 +97,8 @@ public interface TbNotificationEntityService { void notifyAssignDeviceToTenant(TenantId tenantId, TenantId newTenantId, DeviceId deviceId, CustomerId customerId, Device device, Tenant tenant, User user, Object... additionalInfo); - void notifyEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, - User user, Object... additionalInfo); + void notifyCreateOrUpdateOrDeleteEdge(TenantId tenantId, EdgeId edgeId, CustomerId customerId, Edge edge, ActionType actionType, + User user, Object... additionalInfo); void notifyCreateOrUpdateAlarm(Alarm alarm, ActionType actionType, User user, Object... additionalInfo); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java index 4f2b7fd5bc..8157eff3e5 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmService.java @@ -29,7 +29,6 @@ import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import java.util.List; @@ -43,7 +42,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb ActionType actionType = alarm.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = alarm.getTenantId(); try { - Alarm savedAlarm = checkNotNull(alarmService.createOrUpdateAlarm(alarm).getAlarm()); + Alarm savedAlarm = checkNotNull(alarmSubscriptionService.createOrUpdateAlarm(alarm)); notificationEntityService.notifyCreateOrUpdateAlarm(savedAlarm, actionType, user); return savedAlarm; } catch (Exception e) { @@ -55,7 +54,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public ListenableFuture ack(Alarm alarm, User user) { long ackTs = System.currentTimeMillis(); - ListenableFuture future = alarmService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs); + ListenableFuture future = alarmSubscriptionService.ackAlarm(alarm.getTenantId(), alarm.getId(), ackTs); return Futures.transform(future, result -> { alarm.setAckTs(ackTs); alarm.setStatus(alarm.getStatus().isCleared() ? AlarmStatus.CLEARED_ACK : AlarmStatus.ACTIVE_ACK); @@ -67,7 +66,7 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public ListenableFuture clear(Alarm alarm, User user) { long clearTs = System.currentTimeMillis(); - ListenableFuture future = alarmService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs); + ListenableFuture future = alarmSubscriptionService.clearAlarm(alarm.getTenantId(), alarm.getId(), null, clearTs); return Futures.transform(future, result -> { alarm.setClearTs(clearTs); alarm.setStatus(alarm.getStatus().isAck() ? AlarmStatus.CLEARED_ACK : AlarmStatus.CLEARED_UNACK); @@ -79,9 +78,9 @@ public class DefaultTbAlarmService extends AbstractTbEntityService implements Tb @Override public Boolean delete(Alarm alarm, User user) { TenantId tenantId = alarm.getTenantId(); - List relatedEdgeIds = findRelatedEdgeIds(tenantId, alarm.getOriginator()); + List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, alarm.getOriginator()); notificationEntityService.notifyDeleteAlarm(tenantId, alarm, alarm.getOriginator(), alarm.getCustomerId(), relatedEdgeIds, user, JacksonUtil.toString(alarm)); - return alarmService.deleteAlarm(tenantId, alarm.getId()).isSuccessful(); + return alarmSubscriptionService.deleteAlarm(tenantId, alarm.getId()); } } \ No newline at end of file diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java index 724c3bb5c1..abaef626b5 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/asset/DefaultTbAssetService.java @@ -22,8 +22,10 @@ import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetProfile; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; @@ -31,20 +33,32 @@ import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.service.entitiy.AbstractTbEntityService; +import org.thingsboard.server.service.profile.TbAssetProfileCache; import java.util.List; +import static org.thingsboard.server.dao.asset.BaseAssetService.TB_SERVICE_QUEUE; + @Service @AllArgsConstructor public class DefaultTbAssetService extends AbstractTbEntityService implements TbAssetService { private final AssetService assetService; + private final TbAssetProfileCache assetProfileCache; @Override public Asset save(Asset asset, User user) throws Exception { ActionType actionType = asset.getId() == null ? ActionType.ADDED : ActionType.UPDATED; TenantId tenantId = asset.getTenantId(); try { + if (TB_SERVICE_QUEUE.equals(asset.getType())) { + throw new ThingsboardException("Unable to save asset with type " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } else if (asset.getAssetProfileId() != null) { + AssetProfile assetProfile = assetProfileCache.get(tenantId, asset.getAssetProfileId()); + if (assetProfile != null && TB_SERVICE_QUEUE.equals(assetProfile.getName())) { + throw new ThingsboardException("Unable to save asset with profile " + TB_SERVICE_QUEUE, ThingsboardErrorCode.BAD_REQUEST_PARAMS); + } + } Asset savedAsset = checkNotNull(assetService.saveAsset(asset)); autoCommit(user, savedAsset.getId()); notificationEntityService.notifyCreateOrUpdateEntity(tenantId, savedAsset.getId(), savedAsset, @@ -61,7 +75,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb TenantId tenantId = asset.getTenantId(); AssetId assetId = asset.getId(); try { - List relatedEdgeIds = findRelatedEdgeIds(tenantId, assetId); + List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, assetId); assetService.deleteAsset(tenantId, assetId); notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), ActionType.DELETED, relatedEdgeIds, user, assetId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java index 07ef6a2eef..986d67dde7 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/customer/DefaultTbCustomerService.java @@ -53,7 +53,7 @@ public class DefaultTbCustomerService extends AbstractTbEntityService implements TenantId tenantId = customer.getTenantId(); CustomerId customerId = customer.getId(); try { - List relatedEdgeIds = findRelatedEdgeIds(tenantId, customer.getId()); + List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, customer.getId()); customerService.deleteCustomer(tenantId, customerId); notificationEntityService.notifyDeleteEntity(tenantId, customer.getId(), customer, customerId, ActionType.DELETED, relatedEdgeIds, user, customerId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java index 5d9b4290f4..7fadab17ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/dashboard/DefaultTbDashboardService.java @@ -65,7 +65,7 @@ public class DefaultTbDashboardService extends AbstractTbEntityService implement DashboardId dashboardId = dashboard.getId(); TenantId tenantId = dashboard.getTenantId(); try { - List relatedEdgeIds = findRelatedEdgeIds(tenantId, dashboardId); + List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, dashboardId); dashboardService.deleteDashboard(tenantId, dashboardId); notificationEntityService.notifyDeleteEntity(tenantId, dashboardId, dashboard, null, ActionType.DELETED, relatedEdgeIds, user, dashboardId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java index 959e6520b1..f72092bedf 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/device/DefaultTbDeviceService.java @@ -96,7 +96,7 @@ public class DefaultTbDeviceService extends AbstractTbEntityService implements T TenantId tenantId = device.getTenantId(); DeviceId deviceId = device.getId(); try { - List relatedEdgeIds = findRelatedEdgeIds(tenantId, deviceId); + List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, deviceId); deviceService.deleteDevice(tenantId, deviceId); notificationEntityService.notifyDeleteDevice(tenantId, deviceId, device.getCustomerId(), device, relatedEdgeIds, user, deviceId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java index b06466b01f..697fe35a42 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/edge/DefaultTbEdgeService.java @@ -57,7 +57,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE edgeService.assignDefaultRuleChainsToEdge(tenantId, edgeId); } - notificationEntityService.notifyEdge(tenantId, edgeId, savedEdge.getCustomerId(), savedEdge, actionType, user); + notificationEntityService.notifyCreateOrUpdateOrDeleteEdge(tenantId, edgeId, savedEdge.getCustomerId(), savedEdge, actionType, user); return savedEdge; } catch (Exception e) { @@ -72,7 +72,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE TenantId tenantId = edge.getTenantId(); try { edgeService.deleteEdge(tenantId, edgeId); - notificationEntityService.notifyEdge(tenantId, edgeId, edge.getCustomerId(), edge, ActionType.DELETED, user, edgeId.toString()); + notificationEntityService.notifyCreateOrUpdateOrDeleteEdge(tenantId, edgeId, edge.getCustomerId(), edge, ActionType.DELETED, user, edgeId.toString()); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), ActionType.DELETED, user, e, edgeId.toString()); @@ -82,11 +82,12 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE @Override public Edge assignEdgeToCustomer(TenantId tenantId, EdgeId edgeId, Customer customer, User user) throws ThingsboardException { + ActionType actionType = ActionType.ASSIGNED_TO_CUSTOMER; CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); - notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, ActionType.ASSIGNED_TO_CUSTOMER, user, - edgeId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, edgeId, customerId, savedEdge, + actionType, user, true, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { @@ -98,14 +99,14 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE @Override public Edge unassignEdgeFromCustomer(Edge edge, Customer customer, User user) throws ThingsboardException { + ActionType actionType = ActionType.UNASSIGNED_FROM_CUSTOMER; TenantId tenantId = edge.getTenantId(); EdgeId edgeId = edge.getId(); CustomerId customerId = customer.getId(); try { Edge savedEdge = checkNotNull(edgeService.unassignEdgeFromCustomer(tenantId, edgeId)); - - notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, ActionType.UNASSIGNED_FROM_CUSTOMER, user, - edgeId.toString(), customerId.toString(), customer.getName()); + notificationEntityService.notifyAssignOrUnassignEntityToCustomer(tenantId, edgeId, customerId, savedEdge, + actionType, user, true, edgeId.toString(), customerId.toString(), customer.getName()); return savedEdge; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), @@ -121,7 +122,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE try { Edge savedEdge = checkNotNull(edgeService.assignEdgeToCustomer(tenantId, edgeId, customerId)); - notificationEntityService.notifyEdge(tenantId, edgeId, customerId, savedEdge, ActionType.ASSIGNED_TO_CUSTOMER, user, + notificationEntityService.notifyCreateOrUpdateOrDeleteEdge(tenantId, edgeId, customerId, savedEdge, ActionType.ASSIGNED_TO_CUSTOMER, user, edgeId.toString(), customerId.toString(), publicCustomer.getName()); return savedEdge; @@ -138,7 +139,7 @@ public class DefaultTbEdgeService extends AbstractTbEntityService implements TbE EdgeId edgeId = edge.getId(); try { Edge updatedEdge = edgeNotificationService.setEdgeRootRuleChain(tenantId, edge, ruleChainId); - notificationEntityService.notifyEdge(tenantId, edgeId, null, updatedEdge, ActionType.UPDATED, user); + notificationEntityService.logEntityAction(tenantId, edgeId, edge, null, ActionType.UPDATED, user); return updatedEdge; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.EDGE), diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java index 8de6ec9aac..3ae5fe67f2 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/entityview/DefaultTbEntityViewService.java @@ -129,7 +129,7 @@ public class DefaultTbEntityViewService extends AbstractTbEntityService implemen TenantId tenantId = entityView.getTenantId(); EntityViewId entityViewId = entityView.getId(); try { - List relatedEdgeIds = findRelatedEdgeIds(tenantId, entityViewId); + List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, entityViewId); entityViewService.deleteEntityView(tenantId, entityViewId); notificationEntityService.notifyDeleteEntity(tenantId, entityViewId, entityView, entityView.getCustomerId(), ActionType.DELETED, relatedEdgeIds, user, entityViewId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java index 00cf70db0e..ac825c211e 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/ota/DefaultTbOtaPackageService.java @@ -50,9 +50,9 @@ public class DefaultTbOtaPackageService extends AbstractTbEntityService implemen try { OtaPackageInfo savedOtaPackageInfo = otaPackageService.saveOtaPackageInfo(new OtaPackageInfo(saveOtaPackageInfoRequest), saveOtaPackageInfoRequest.isUsesUrl()); - boolean sendToEdge = savedOtaPackageInfo.hasUrl() || savedOtaPackageInfo.isHasData(); + boolean sendMsgToEdge = savedOtaPackageInfo.hasUrl() || savedOtaPackageInfo.isHasData(); notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, null, savedOtaPackageInfo.getId(), - savedOtaPackageInfo, user, actionType, sendToEdge, null); + savedOtaPackageInfo, user, actionType, sendMsgToEdge, null); return savedOtaPackageInfo; } catch (Exception e) { diff --git a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java index a41bbb4385..a3df4b816b 100644 --- a/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java +++ b/application/src/main/java/org/thingsboard/server/service/entitiy/user/DefaultUserService.java @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.UserCredentials; @@ -34,7 +33,6 @@ import org.thingsboard.server.service.entitiy.AbstractTbEntityService; import org.thingsboard.server.service.security.system.SystemSecurityService; import javax.servlet.http.HttpServletRequest; -import java.util.List; import static org.thingsboard.server.controller.UserController.ACTIVATE_URL_PATTERN; @@ -82,10 +80,9 @@ public class DefaultUserService extends AbstractTbEntityService implements TbUse UserId userId = tbUser.getId(); try { - List relatedEdgeIds = findRelatedEdgeIds(tenantId, userId); userService.deleteUser(tenantId, userId); - notificationEntityService.notifyDeleteEntity(tenantId, userId, tbUser, customerId, - ActionType.DELETED, relatedEdgeIds, user, userId.toString()); + notificationEntityService.notifyCreateOrUpdateOrDelete(tenantId, customerId, userId, tbUser, + user, ActionType.DELETED, true, null, customerId.toString()); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.USER), ActionType.DELETED, user, e, userId.toString()); diff --git a/application/src/main/java/org/thingsboard/server/service/install/CassandraKeyspaceService.java b/application/src/main/java/org/thingsboard/server/service/install/CassandraKeyspaceService.java new file mode 100644 index 0000000000..dfdd05e829 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/CassandraKeyspaceService.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.install; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Service; +import org.thingsboard.server.dao.util.NoSqlAnyDaoNonCloud; + +/* +* Create keyspace for Cassandra NoSQL database for non-cloud deployment. +* For cloud service like Astra DBaas admin have to create keyspace manually on cloud UI. +* Then create tokens with database admin role and put it on Thingsboard parameters. +* Without this service cloud DB will end up with exception like +* UnauthorizedException: Missing correct permission on thingsboard +* */ +@Service +@NoSqlAnyDaoNonCloud +@Profile("install") +public class CassandraKeyspaceService extends CassandraAbstractDatabaseSchemaService + implements NoSqlKeyspaceService { + public CassandraKeyspaceService() { + super("schema-keyspace.cql"); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/DbUpgradeExecutorService.java b/application/src/main/java/org/thingsboard/server/service/install/DbUpgradeExecutorService.java new file mode 100644 index 0000000000..b3ce77ac7a --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/DbUpgradeExecutorService.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.install; + +import org.springframework.context.annotation.Profile; +import org.springframework.stereotype.Component; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; + +@Component +@Profile("install") +public class DbUpgradeExecutorService extends DbCallbackExecutorService { + +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/NoSqlKeyspaceService.java b/application/src/main/java/org/thingsboard/server/service/install/NoSqlKeyspaceService.java new file mode 100644 index 0000000000..5b8d772dca --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/install/NoSqlKeyspaceService.java @@ -0,0 +1,19 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.install; + +public interface NoSqlKeyspaceService extends DatabaseSchemaService { +} diff --git a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java index 092726d0c8..80665d544f 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java @@ -15,6 +15,8 @@ */ package org.thingsboard.server.service.install; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections.CollectionUtils; import org.springframework.beans.factory.annotation.Autowired; @@ -32,14 +34,14 @@ import org.thingsboard.server.common.data.queue.ProcessingStrategyType; import org.thingsboard.server.common.data.queue.Queue; import org.thingsboard.server.common.data.queue.SubmitStrategy; import org.thingsboard.server.common.data.queue.SubmitStrategyType; +import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.asset.AssetProfileService; -import org.thingsboard.server.dao.asset.AssetService; import org.thingsboard.server.dao.dashboard.DashboardService; import org.thingsboard.server.dao.device.DeviceProfileService; import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.dao.queue.QueueService; -import org.thingsboard.server.dao.rule.RuleChainService; -import org.thingsboard.server.dao.tenant.TenantProfileService; +import org.thingsboard.server.dao.sql.tenant.TenantRepository; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.usagerecord.ApiUsageStateService; import org.thingsboard.server.queue.settings.TbRuleEngineQueueConfiguration; @@ -56,7 +58,9 @@ import java.sql.SQLException; import java.sql.SQLSyntaxErrorException; import java.sql.SQLWarning; import java.sql.Statement; +import java.util.ArrayList; import java.util.List; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.thingsboard.server.service.install.DatabaseHelper.ADDITIONAL_INFO; @@ -106,11 +110,14 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService @Autowired private TenantService tenantService; + @Autowired + private TenantRepository tenantRepository; + @Autowired private DeviceService deviceService; @Autowired - private AssetService assetService; + private AssetDao assetDao; @Autowired private DeviceProfileService deviceProfileService; @@ -129,10 +136,7 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService private TbRuleEngineQueueConfigService queueConfig; @Autowired - private RuleChainService ruleChainService; - - @Autowired - private TenantProfileService tenantProfileService; + private DbUpgradeExecutorService dbUpgradeExecutor; @Override public void upgradeDatabase(String fromVersion) throws Exception { @@ -620,26 +624,45 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.4.1", "schema_update_before.sql"); loadSql(schemaUpdateFile, conn); + conn.createStatement().execute("DELETE FROM asset a WHERE NOT exists(SELECT id FROM tenant WHERE id = a.tenant_id);"); + log.info("Creating default asset profiles..."); - PageLink pageLink = new PageLink(100); - PageData pageData; + + PageLink pageLink = new PageLink(1000); + PageData tenantIds; do { - pageData = tenantService.findTenants(pageLink); - for (Tenant tenant : pageData.getData()) { - List assetTypes = assetService.findAssetTypesByTenantId(tenant.getId()).get(); - try { - assetProfileService.createDefaultAssetProfile(tenant.getId()); - } catch (Exception e) { - } - for (EntitySubtype assetType : assetTypes) { + List> futures = new ArrayList<>(); + tenantIds = tenantService.findTenantsIds(pageLink); + for (TenantId tenantId : tenantIds.getData()) { + futures.add(dbUpgradeExecutor.submit(() -> { try { - assetProfileService.findOrCreateAssetProfile(tenant.getId(), assetType.getType()); - } catch (Exception e) { - } + assetProfileService.createDefaultAssetProfile(tenantId); + } catch (Exception e) {} + })); + } + Futures.allAsList(futures).get(); + pageLink = pageLink.nextPageLink(); + } while (tenantIds.hasNext()); + + pageLink = new PageLink(1000); + PageData> pairs; + do { + List> futures = new ArrayList<>(); + pairs = assetDao.getAllAssetTypes(pageLink); + for (TbPair pair : pairs.getData()) { + TenantId tenantId = new TenantId(pair.getFirst()); + String assetType = pair.getSecond(); + if (!"default".equals(assetType)) { + futures.add(dbUpgradeExecutor.submit(() -> { + try { + assetProfileService.findOrCreateAssetProfile(tenantId, assetType); + } catch (Exception e) {} + })); } } + Futures.allAsList(futures).get(); pageLink = pageLink.nextPageLink(); - } while (pageData.hasNext()); + } while (pairs.hasNext()); log.info("Updating asset profiles..."); conn.createStatement().execute("call update_asset_profiles()"); @@ -728,5 +751,4 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService return queue; } - } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java index 9af49f4a7c..6e67876dc4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultCacheCleanupService.java @@ -73,6 +73,11 @@ public class DefaultCacheCleanupService implements CacheCleanupService { log.info("Clear cache to upgrade from version 3.3.4 to 3.4.0 ..."); clearAll(); break; + case "3.4.1": + log.info("Clear cache to upgrade from version 3.4.1 to 3.4.2 ..."); + clearCacheByName("assets"); + clearCacheByName("repositorySettings"); + break; default: //Do nothing, since cache cleanup is optional. } diff --git a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java index a8cf5374a2..93f83414ae 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/update/DefaultDataUpdateService.java @@ -64,6 +64,7 @@ import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfi import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.alarm.AlarmDao; import org.thingsboard.server.dao.audit.AuditLogDao; +import org.thingsboard.server.dao.edge.EdgeEventDao; import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.entityview.EntityViewService; import org.thingsboard.server.dao.event.EventService; @@ -142,6 +143,9 @@ public class DefaultDataUpdateService implements DataUpdateService { @Autowired private AuditLogDao auditLogDao; + @Autowired + private EdgeEventDao edgeEventDao; + @Override public void updateData(String fromVersion) throws Exception { switch (fromVersion) { @@ -181,14 +185,21 @@ public class DefaultDataUpdateService implements DataUpdateService { } break; case "3.4.1": + log.info("Updating data from version 3.4.1 to 3.4.2 ..."); boolean skipAuditLogsMigration = getEnv("TB_SKIP_AUDIT_LOGS_MIGRATION", false); if (!skipAuditLogsMigration) { - log.info("Updating data from version 3.4.1 to 3.4.2 ..."); log.info("Starting audit logs migration. Can be skipped with TB_SKIP_AUDIT_LOGS_MIGRATION env variable set to true"); auditLogDao.migrateAuditLogs(); } else { log.info("Skipping audit logs migration"); } + boolean skipEdgeEventsMigration = getEnv("TB_SKIP_EDGE_EVENTS_MIGRATION", false); + if (!skipEdgeEventsMigration) { + log.info("Starting edge events migration. Can be skipped with TB_SKIP_EDGE_EVENTS_MIGRATION env variable set to true"); + edgeEventDao.migrateEdgeEvents(); + } else { + log.info("Skipping edge events migration"); + } break; default: throw new RuntimeException("Unable to update data, unsupported fromVersion: " + fromVersion); diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index f433066c06..7b26af989c 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -42,9 +42,9 @@ import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.settings.AdminSettingsService; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; @@ -70,7 +70,7 @@ public class DefaultMailService implements MailService { private final MessageSource messages; private final Configuration freemarkerConfig; private final AdminSettingsService adminSettingsService; - private final TbApiUsageClient apiUsageClient; + private final TbApiUsageReportClient apiUsageClient; private static final long DEFAULT_TIMEOUT = 10_000; @@ -90,7 +90,7 @@ public class DefaultMailService implements MailService { private long timeout; - public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageClient apiUsageClient) { + public DefaultMailService(MessageSource messages, Configuration freemarkerConfig, AdminSettingsService adminSettingsService, TbApiUsageReportClient apiUsageClient) { this.messages = messages; this.freemarkerConfig = freemarkerConfig; this.adminSettingsService = adminSettingsService; diff --git a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java index 493ab7ef43..c0f6cc750f 100644 --- a/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java +++ b/application/src/main/java/org/thingsboard/server/service/rpc/DefaultTbRuleEngineRpcService.java @@ -19,6 +19,9 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.id.RpcId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rpc.Rpc; import org.thingsboard.server.common.data.rpc.RpcError; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcRequest; import org.thingsboard.rule.engine.api.RuleEngineDeviceRpcResponse; @@ -27,6 +30,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.msg.rpc.ToDeviceRpcRequest; +import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; @@ -52,6 +56,7 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi private final PartitionService partitionService; private final TbClusterService clusterService; private final TbServiceInfoProvider serviceInfoProvider; + private final RpcService rpcService; private final ConcurrentMap> toDeviceRpcRequests = new ConcurrentHashMap<>(); @@ -61,10 +66,12 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi public DefaultTbRuleEngineRpcService(PartitionService partitionService, TbClusterService clusterService, - TbServiceInfoProvider serviceInfoProvider) { + TbServiceInfoProvider serviceInfoProvider, + RpcService rpcService) { this.partitionService = partitionService; this.clusterService = clusterService; this.serviceInfoProvider = serviceInfoProvider; + this.rpcService = rpcService; } @Autowired(required = false) @@ -119,6 +126,11 @@ public class DefaultTbRuleEngineRpcService implements TbRuleEngineDeviceRpcServi }); } + @Override + public Rpc findRpcById(TenantId tenantId, RpcId id) { + return rpcService.findById(tenantId, id); + } + @Override public void processRpcResponseFromDevice(FromDeviceRpcResponse response) { log.trace("[{}] Received response to server-side RPC request from Core RPC Service", response.getId()); diff --git a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java index 5bf7d7415c..28a4e1e854 100644 --- a/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java +++ b/application/src/main/java/org/thingsboard/server/service/rule/DefaultTbRuleChainService.java @@ -203,7 +203,7 @@ public class DefaultTbRuleChainService extends AbstractTbEntityService implement List relatedEdgeIds = null; if (RuleChainType.EDGE.equals(ruleChain.getType())) { - relatedEdgeIds = findRelatedEdgeIds(tenantId, ruleChainId); + relatedEdgeIds = edgeService.findAllRelatedEdgeIds(tenantId, ruleChainId); } ruleChainService.deleteRuleChainById(tenantId, ruleChainId); diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java deleted file mode 100644 index cc46f0a74f..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractJsInvokeService.java +++ /dev/null @@ -1,249 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.script; - -import com.google.common.hash.Hashing; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.util.Pair; -import org.thingsboard.common.util.ThingsBoardThreadFactory; -import org.thingsboard.server.common.data.ApiUsageRecordKey; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; -import org.thingsboard.server.service.apiusage.TbApiUsageStateService; - -import java.util.Map; -import java.util.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.Executors; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.atomic.AtomicInteger; - -import static java.lang.String.format; - -/** - * Created by ashvayka on 26.09.18. - */ -@Slf4j -@SuppressWarnings("UnstableApiUsage") -public abstract class AbstractJsInvokeService implements JsInvokeService { - - private final TbApiUsageStateService apiUsageStateService; - private final TbApiUsageClient apiUsageClient; - protected ScheduledExecutorService timeoutExecutorService; - - protected final Map> scriptIdToNameAndHashMap = new ConcurrentHashMap<>(); - protected final Map disabledFunctions = new ConcurrentHashMap<>(); - - @Getter - @Value("${js.max_total_args_size:100000}") - private long maxTotalArgsSize; - @Getter - @Value("${js.max_result_size:300000}") - private long maxResultSize; - @Getter - @Value("${js.max_script_body_size:50000}") - private long maxScriptBodySize; - - protected AbstractJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { - this.apiUsageStateService = apiUsageStateService; - this.apiUsageClient = apiUsageClient; - } - - public void init(long maxRequestsTimeout) { - if (maxRequestsTimeout > 0) { - timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("nashorn-js-timeout")); - } - } - - public void stop() { - if (timeoutExecutorService != null) { - timeoutExecutorService.shutdownNow(); - } - } - - @Override - public ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) { - if (apiUsageStateService.getApiUsageState(tenantId).isJsExecEnabled()) { - if (scriptBodySizeExceeded(scriptBody)) { - return error(format("Script body exceeds maximum allowed size of %s symbols", getMaxScriptBodySize())); - } - UUID scriptId = UUID.randomUUID(); - String scriptHash = hash(tenantId, scriptBody); - String functionName = constructFunctionName(scriptId, scriptHash); - String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); - return doEval(scriptId, scriptHash, functionName, jsScript); - } else { - return error("JS Execution is disabled due to API limits!"); - } - } - - protected String constructFunctionName(UUID scriptId, String scriptHash) { - return "invokeInternal_" + scriptId.toString().replace('-', '_'); - } - - @Override - public ListenableFuture invokeFunction(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args) { - if (apiUsageStateService.getApiUsageState(tenantId).isJsExecEnabled()) { - Pair nameAndHash = scriptIdToNameAndHashMap.get(scriptId); - if (nameAndHash == null) { - return error("No compiled script found for scriptId: [" + scriptId + "]!"); - } - String functionName = nameAndHash.getFirst(); - String scriptHash = nameAndHash.getSecond(); - if (!isDisabled(scriptId)) { - if (argsSizeExceeded(args)) { - return scriptExecutionError(scriptId, format("Script input arguments exceed maximum allowed total args size of %s symbols", getMaxTotalArgsSize())); - } - apiUsageClient.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1); - return Futures.transformAsync(doInvokeFunction(scriptId, scriptHash, functionName, args), output -> { - String result = output.toString(); - if (resultSizeExceeded(result)) { - return scriptExecutionError(scriptId, format("Script invocation result exceeds maximum allowed size of %s symbols", getMaxResultSize())); - } - return Futures.immediateFuture(result); - }, MoreExecutors.directExecutor()); - } else { - String message = "Script invocation is blocked due to maximum error count " - + getMaxErrors() + ", scriptId " + scriptId + "!"; - log.warn(message); - return error(message); - } - } else { - return error("JS Execution is disabled due to API limits!"); - } - } - - @Override - public ListenableFuture release(UUID scriptId) { - Pair nameAndHash = scriptIdToNameAndHashMap.get(scriptId); - if (nameAndHash != null) { - try { - scriptIdToNameAndHashMap.remove(scriptId); - disabledFunctions.remove(scriptId); - doRelease(scriptId, nameAndHash.getSecond(), nameAndHash.getFirst()); - } catch (Exception e) { - return Futures.immediateFailedFuture(e); - } - } - return Futures.immediateFuture(null); - } - - protected abstract ListenableFuture doEval(UUID scriptId, String scriptHash, String functionName, String scriptBody); - - protected abstract ListenableFuture doInvokeFunction(UUID scriptId, String scriptHash, String functionName, Object[] args); - - protected abstract void doRelease(UUID scriptId, String scriptHash, String functionName) throws Exception; - - protected abstract int getMaxErrors(); - - protected abstract long getMaxBlacklistDuration(); - - protected String hash(TenantId tenantId, String scriptBody) { - return Hashing.murmur3_128().newHasher() - .putLong(tenantId.getId().getMostSignificantBits()) - .putLong(tenantId.getId().getLeastSignificantBits()) - .putUnencodedChars(scriptBody) - .hash().toString(); - } - - protected void onScriptExecutionError(UUID scriptId, Throwable t, String scriptBody) { - DisableListInfo disableListInfo = disabledFunctions.computeIfAbsent(scriptId, key -> new DisableListInfo()); - log.warn("Script has exception and will increment counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}", - disableListInfo.get(), scriptId, t, t.getCause(), scriptBody); - disableListInfo.incrementAndGet(); - } - - private boolean scriptBodySizeExceeded(String scriptBody) { - if (getMaxScriptBodySize() <= 0) return false; - return scriptBody.length() > getMaxScriptBodySize(); - } - - private boolean argsSizeExceeded(Object[] args) { - if (getMaxTotalArgsSize() <= 0) return false; - long totalArgsSize = 0; - for (Object arg : args) { - if (arg instanceof CharSequence) { - totalArgsSize += ((CharSequence) arg).length(); - } - } - return totalArgsSize > getMaxTotalArgsSize(); - } - - private boolean resultSizeExceeded(String result) { - if (getMaxResultSize() <= 0) return false; - return result.length() > getMaxResultSize(); - } - - private String generateJsScript(JsScriptType scriptType, String functionName, String scriptBody, String... argNames) { - if (scriptType == JsScriptType.RULE_NODE_SCRIPT) { - return RuleNodeScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames); - } - throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); - } - - private boolean isDisabled(UUID scriptId) { - DisableListInfo errorCount = disabledFunctions.get(scriptId); - if (errorCount != null) { - if (errorCount.getExpirationTime() <= System.currentTimeMillis()) { - disabledFunctions.remove(scriptId); - return false; - } else { - return errorCount.get() >= getMaxErrors(); - } - } else { - return false; - } - } - - private ListenableFuture error(String message) { - return Futures.immediateFailedFuture(new RuntimeException(message)); - } - - private ListenableFuture scriptExecutionError(UUID scriptId, String errorMsg) { - RuntimeException error = new RuntimeException(errorMsg); - onScriptExecutionError(scriptId, error, null); - return Futures.immediateFailedFuture(error); - } - - private class DisableListInfo { - private final AtomicInteger counter; - private long expirationTime; - - private DisableListInfo() { - this.counter = new AtomicInteger(0); - } - - public int get() { - return counter.get(); - } - - public int incrementAndGet() { - int result = counter.incrementAndGet(); - expirationTime = System.currentTimeMillis() + getMaxBlacklistDuration(); - return result; - } - - public long getExpirationTime() { - return expirationTime; - } - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java deleted file mode 100644 index 4b19bf9020..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/script/AbstractNashornJsInvokeService.java +++ /dev/null @@ -1,185 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.script; - -import com.google.common.util.concurrent.FutureCallback; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import delight.nashornsandbox.NashornSandbox; -import delight.nashornsandbox.NashornSandboxes; -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.data.util.Pair; -import org.springframework.scheduling.annotation.Scheduled; -import org.thingsboard.common.util.ThingsBoardExecutors; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; -import org.thingsboard.server.service.apiusage.TbApiUsageStateService; - -import javax.annotation.PostConstruct; -import javax.annotation.PreDestroy; -import javax.script.Invocable; -import javax.script.ScriptEngine; -import javax.script.ScriptEngineManager; -import javax.script.ScriptException; -import java.util.UUID; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.locks.ReentrantLock; - -@Slf4j -public abstract class AbstractNashornJsInvokeService extends AbstractJsInvokeService { - - private NashornSandbox sandbox; - private ScriptEngine engine; - private ExecutorService monitorExecutorService; - - private final AtomicInteger jsPushedMsgs = new AtomicInteger(0); - private final AtomicInteger jsInvokeMsgs = new AtomicInteger(0); - private final AtomicInteger jsEvalMsgs = new AtomicInteger(0); - private final AtomicInteger jsFailedMsgs = new AtomicInteger(0); - private final AtomicInteger jsTimeoutMsgs = new AtomicInteger(0); - private final FutureCallback evalCallback = new JsStatCallback<>(jsEvalMsgs, jsTimeoutMsgs, jsFailedMsgs); - private final FutureCallback invokeCallback = new JsStatCallback<>(jsInvokeMsgs, jsTimeoutMsgs, jsFailedMsgs); - - private final ReentrantLock evalLock = new ReentrantLock(); - - @Getter - private final JsExecutorService jsExecutor; - - @Value("${js.local.max_requests_timeout:0}") - private long maxRequestsTimeout; - - @Value("${js.local.stats.enabled:false}") - private boolean statsEnabled; - - public AbstractNashornJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient, JsExecutorService jsExecutor) { - super(apiUsageStateService, apiUsageClient); - this.jsExecutor = jsExecutor; - } - - @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}") - public void printStats() { - if (statsEnabled) { - int pushedMsgs = jsPushedMsgs.getAndSet(0); - int invokeMsgs = jsInvokeMsgs.getAndSet(0); - int evalMsgs = jsEvalMsgs.getAndSet(0); - int failed = jsFailedMsgs.getAndSet(0); - int timedOut = jsTimeoutMsgs.getAndSet(0); - if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { - log.info("Nashorn JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", - pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); - } - } - } - - @PostConstruct - public void init() { - super.init(maxRequestsTimeout); - if (useJsSandbox()) { - sandbox = NashornSandboxes.create(); - monitorExecutorService = ThingsBoardExecutors.newWorkStealingPool(getMonitorThreadPoolSize(), "nashorn-js-monitor"); - sandbox.setExecutor(monitorExecutorService); - sandbox.setMaxCPUTime(getMaxCpuTime()); - sandbox.allowNoBraces(false); - sandbox.allowLoadFunctions(true); - sandbox.setMaxPreparedStatements(30); - } else { - ScriptEngineManager factory = new ScriptEngineManager(); - engine = factory.getEngineByName("nashorn"); - } - } - - @PreDestroy - public void stop() { - super.stop(); - if (monitorExecutorService != null) { - monitorExecutorService.shutdownNow(); - } - } - - protected abstract boolean useJsSandbox(); - - protected abstract int getMonitorThreadPoolSize(); - - protected abstract long getMaxCpuTime(); - - @Override - protected ListenableFuture doEval(UUID scriptId, String scriptHash, String functionName, String jsScript) { - jsPushedMsgs.incrementAndGet(); - ListenableFuture result = jsExecutor.executeAsync(() -> { - try { - evalLock.lock(); - try { - if (useJsSandbox()) { - sandbox.eval(jsScript); - } else { - engine.eval(jsScript); - } - } finally { - evalLock.unlock(); - } - scriptIdToNameAndHashMap.put(scriptId, Pair.of(functionName, scriptHash)); - return scriptId; - } catch (Exception e) { - log.debug("Failed to compile JS script: {}", e.getMessage(), e); - throw new ExecutionException(e); - } - }); - if (maxRequestsTimeout > 0) { - result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); - } - Futures.addCallback(result, evalCallback, MoreExecutors.directExecutor()); - return result; - } - - @Override - protected ListenableFuture doInvokeFunction(UUID scriptId, String scriptHash, String functionName, Object[] args) { - jsPushedMsgs.incrementAndGet(); - ListenableFuture result = jsExecutor.executeAsync(() -> { - try { - if (useJsSandbox()) { - return sandbox.getSandboxedInvocable().invokeFunction(functionName, args); - } else { - return ((Invocable) engine).invokeFunction(functionName, args); - } - } catch (ScriptException e) { - throw new ExecutionException(e); - } catch (Exception e) { - onScriptExecutionError(scriptId, e, functionName); - throw new ExecutionException(e); - } - }); - - if (maxRequestsTimeout > 0) { - result = Futures.withTimeout(result, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); - } - Futures.addCallback(result, invokeCallback, MoreExecutors.directExecutor()); - return result; - } - - protected void doRelease(UUID scriptId, String scriptHash, String functionName) throws ScriptException { - if (useJsSandbox()) { - sandbox.eval(functionName + " = undefined;"); - } else { - engine.eval(functionName + " = undefined;"); - } - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java b/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java deleted file mode 100644 index 4b9e5dea57..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/script/NashornJsInvokeService.java +++ /dev/null @@ -1,64 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.script; - -import lombok.Getter; -import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.stereotype.Service; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; -import org.thingsboard.server.service.apiusage.TbApiUsageStateService; - -import java.util.concurrent.TimeUnit; - -@Slf4j -@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "local", matchIfMissing = true) -@Service -public class NashornJsInvokeService extends AbstractNashornJsInvokeService { - - @Value("${js.local.use_js_sandbox}") - private boolean useJsSandbox; - - @Getter - @Value("${js.local.monitor_thread_pool_size}") - private int monitorThreadPoolSize; - - @Getter - @Value("${js.local.max_cpu_time}") - private long maxCpuTime; - - @Getter - @Value("${js.local.max_errors}") - private int maxErrors; - - @Value("${js.local.max_black_list_duration_sec:60}") - private int maxBlackListDurationSec; - - public NashornJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient, JsExecutorService jsExecutor) { - super(apiUsageStateService, apiUsageClient, jsExecutor); - } - - @Override - protected boolean useJsSandbox() { - return useJsSandbox; - } - - @Override - protected long getMaxBlacklistDuration() { - return TimeUnit.SECONDS.toMillis(maxBlackListDurationSec); - } -} diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java index f1374711d5..9b5a678d35 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeJsScriptEngine.java @@ -17,14 +17,13 @@ package org.thingsboard.server.service.script; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.script.api.RuleNodeScriptFactory; +import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -36,86 +35,22 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.UUID; -import java.util.concurrent.ExecutionException; @Slf4j -public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.ScriptEngine { +public class RuleNodeJsScriptEngine extends RuleNodeScriptEngine { - private static final ObjectMapper mapper = new ObjectMapper(); - private final JsInvokeService sandboxService; - - private final UUID scriptId; - private final TenantId tenantId; - private final EntityId entityId; - - public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService sandboxService, EntityId entityId, String script, String... argNames) { - this.tenantId = tenantId; - this.sandboxService = sandboxService; - this.entityId = entityId; - try { - this.scriptId = this.sandboxService.eval(tenantId, JsScriptType.RULE_NODE_SCRIPT, script, argNames).get(); - } catch (Exception e) { - Throwable t = e; - if (e instanceof ExecutionException) { - t = e.getCause(); - } - throw new IllegalArgumentException("Can't compile script: " + t.getMessage(), t); - } - } - - private static String[] prepareArgs(TbMsg msg) { - try { - String[] args = new String[3]; - if (msg.getData() != null) { - args[0] = msg.getData(); - } else { - args[0] = ""; - } - args[1] = mapper.writeValueAsString(msg.getMetaData().getData()); - args[2] = msg.getType(); - return args; - } catch (Throwable th) { - throw new IllegalArgumentException("Cannot bind js args", th); - } - } - - private static TbMsg unbindMsg(JsonNode msgData, TbMsg msg) { - try { - String data = null; - Map metadata = null; - String messageType = null; - if (msgData.has(RuleNodeScriptFactory.MSG)) { - JsonNode msgPayload = msgData.get(RuleNodeScriptFactory.MSG); - data = mapper.writeValueAsString(msgPayload); - } - if (msgData.has(RuleNodeScriptFactory.METADATA)) { - JsonNode msgMetadata = msgData.get(RuleNodeScriptFactory.METADATA); - metadata = mapper.convertValue(msgMetadata, new TypeReference>() { - }); - } - if (msgData.has(RuleNodeScriptFactory.MSG_TYPE)) { - messageType = msgData.get(RuleNodeScriptFactory.MSG_TYPE).asText(); - } - String newData = data != null ? data : msg.getData(); - TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy(); - String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); - return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); - } catch (Throwable th) { - throw new RuntimeException("Failed to unbind message data from javascript result", th); - } + public RuleNodeJsScriptEngine(TenantId tenantId, JsInvokeService scriptInvokeService, String script, String... argNames) { + super(tenantId, scriptInvokeService, script, argNames); } @Override - public ListenableFuture> executeUpdateAsync(TbMsg msg) { - ListenableFuture result = executeScriptAsync(msg); - return Futures.transformAsync(result, - json -> executeUpdateTransform(msg, json), - MoreExecutors.directExecutor()); + public ListenableFuture executeJsonAsync(TbMsg msg) { + return executeScriptAsync(msg); } - ListenableFuture> executeUpdateTransform(TbMsg msg, JsonNode json) { + @Override + protected ListenableFuture> executeUpdateTransform(TbMsg msg, JsonNode json) { if (json.isObject()) { return Futures.immediateFuture(Collections.singletonList(unbindMsg(json, msg))); } else if (json.isArray()) { @@ -128,13 +63,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } @Override - public ListenableFuture executeGenerateAsync(TbMsg prevMsg) { - return Futures.transformAsync(executeScriptAsync(prevMsg), - result -> executeGenerateTransform(prevMsg, result), - MoreExecutors.directExecutor()); - } - - ListenableFuture executeGenerateTransform(TbMsg prevMsg, JsonNode result) { + protected ListenableFuture executeGenerateTransform(TbMsg prevMsg, JsonNode result) { if (!result.isObject()) { log.warn("Wrong result type: {}", result.getNodeType()); Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + result.getNodeType())); @@ -143,18 +72,12 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } @Override - public ListenableFuture executeJsonAsync(TbMsg msg) { - return executeScriptAsync(msg); + protected JsonNode convertResult(Object result) { + return JacksonUtil.toJsonNode(result != null ? result.toString() : null); } @Override - public ListenableFuture executeToStringAsync(TbMsg msg) { - return Futures.transformAsync(executeScriptAsync(msg), - this::executeToStringTransform, - MoreExecutors.directExecutor()); - } - - ListenableFuture executeToStringTransform(JsonNode result) { + protected ListenableFuture executeToStringTransform(JsonNode result) { if (result.isTextual()) { return Futures.immediateFuture(result.asText()); } @@ -163,13 +86,7 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } @Override - public ListenableFuture executeFilterAsync(TbMsg msg) { - return Futures.transformAsync(executeScriptAsync(msg), - this::executeFilterTransform, - MoreExecutors.directExecutor()); - } - - ListenableFuture executeFilterTransform(JsonNode json) { + protected ListenableFuture executeFilterTransform(JsonNode json) { if (json.isBoolean()) { return Futures.immediateFuture(json.asBoolean()); } @@ -177,7 +94,8 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + json.getNodeType())); } - ListenableFuture> executeSwitchTransform(JsonNode result) { + @Override + protected ListenableFuture> executeSwitchTransform(JsonNode result) { if (result.isTextual()) { return Futures.immediateFuture(Collections.singleton(result.asText())); } @@ -198,36 +116,37 @@ public class RuleNodeJsScriptEngine implements org.thingsboard.rule.engine.api.S } @Override - public ListenableFuture> executeSwitchAsync(TbMsg msg) { - return Futures.transformAsync(executeScriptAsync(msg), - this::executeSwitchTransform, - MoreExecutors.directExecutor()); //usually runs in a callbackExecutor - } - - ListenableFuture executeScriptAsync(TbMsg msg) { - log.trace("execute script async, msg {}", msg); - String[] inArgs = prepareArgs(msg); - return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]); - } - - ListenableFuture executeScriptAsync(CustomerId customerId, String... args) { - return Futures.transformAsync(sandboxService.invokeFunction(tenantId, customerId, this.scriptId, args), - o -> { - try { - return Futures.immediateFuture(mapper.readTree(o)); - } catch (Exception e) { - if (e.getCause() instanceof ScriptException) { - return Futures.immediateFailedFuture(e.getCause()); - } else if (e.getCause() instanceof RuntimeException) { - return Futures.immediateFailedFuture(new ScriptException(e.getCause().getMessage())); - } else { - return Futures.immediateFailedFuture(new ScriptException(e)); - } - } - }, MoreExecutors.directExecutor()); + protected Object[] prepareArgs(TbMsg msg) { + String[] args = new String[3]; + if (msg.getData() != null) { + args[0] = msg.getData(); + } else { + args[0] = ""; + } + args[1] = JacksonUtil.toString(msg.getMetaData().getData()); + args[2] = msg.getType(); + return args; } - public void destroy() { - sandboxService.release(this.scriptId); + private static TbMsg unbindMsg(JsonNode msgData, TbMsg msg) { + String data = null; + Map metadata = null; + String messageType = null; + if (msgData.has(RuleNodeScriptFactory.MSG)) { + JsonNode msgPayload = msgData.get(RuleNodeScriptFactory.MSG); + data = JacksonUtil.toString(msgPayload); + } + if (msgData.has(RuleNodeScriptFactory.METADATA)) { + JsonNode msgMetadata = msgData.get(RuleNodeScriptFactory.METADATA); + metadata = JacksonUtil.convertValue(msgMetadata, new TypeReference<>() { + }); + } + if (msgData.has(RuleNodeScriptFactory.MSG_TYPE)) { + messageType = msgData.get(RuleNodeScriptFactory.MSG_TYPE).asText(); + } + String newData = data != null ? data : msg.getData(); + TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy(); + String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); + return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeMvelScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeMvelScriptEngine.java new file mode 100644 index 0000000000..6ff08ce9b9 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeMvelScriptEngine.java @@ -0,0 +1,171 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.script; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.script.api.RuleNodeScriptFactory; +import org.thingsboard.script.api.mvel.MvelInvokeService; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import javax.script.ScriptException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; + + +@Slf4j +public class RuleNodeMvelScriptEngine extends RuleNodeScriptEngine { + + public RuleNodeMvelScriptEngine(TenantId tenantId, MvelInvokeService scriptInvokeService, String script, String... argNames) { + super(tenantId, scriptInvokeService, script, argNames); + } + + @Override + protected ListenableFuture executeFilterTransform(Object result) { + if (result instanceof Boolean) { + return Futures.immediateFuture((Boolean) result); + } + return wrongResultType(result); + } + + @Override + protected ListenableFuture> executeUpdateTransform(TbMsg msg, Object result) { + if (result instanceof Map) { + return Futures.immediateFuture(Collections.singletonList(unbindMsg((Map) result, msg))); + } else if (result instanceof Collection) { + List res = new ArrayList<>(); + for (Object resObject : (Collection) result) { + if (resObject instanceof Map) { + res.add(unbindMsg((Map) result, msg)); + } else { + return wrongResultType(resObject); + } + } + return Futures.immediateFuture(res); + } + return wrongResultType(result); + } + + @Override + protected ListenableFuture executeGenerateTransform(TbMsg prevMsg, Object result) { + if (result instanceof Map) { + return Futures.immediateFuture(unbindMsg((Map) result, prevMsg)); + } + return wrongResultType(result); + } + + @Override + protected ListenableFuture executeToStringTransform(Object result) { + if (result instanceof String) { + return Futures.immediateFuture((String) result); + } else { + return Futures.immediateFuture(JacksonUtil.toString(result)); + } + } + + @Override + protected ListenableFuture> executeSwitchTransform(Object result) { + if (result instanceof String) { + return Futures.immediateFuture(Collections.singleton((String) result)); + } else if (result instanceof Collection) { + Set res = new HashSet<>(); + for (Object resObject : (Collection) result) { + if (resObject instanceof String) { + res.add((String) resObject); + } else { + return wrongResultType(resObject); + } + } + return Futures.immediateFuture(res); + } + return wrongResultType(result); + } + + @Override + public ListenableFuture executeJsonAsync(TbMsg msg) { + return Futures.transform(executeScriptAsync(msg), JacksonUtil::valueToTree, MoreExecutors.directExecutor()); + + } + + @Override + protected Object convertResult(Object result) { + return result; + } + + @Override + protected Object[] prepareArgs(TbMsg msg) { + Object[] args = new Object[3]; + if (msg.getData() != null) { + args[0] = JacksonUtil.fromString(msg.getData(), Map.class); + } else { + args[0] = new HashMap<>(); + } + args[1] = new HashMap<>(msg.getMetaData().getData()); + args[2] = msg.getType(); + return args; + } + + private static TbMsg unbindMsg(Map msgData, TbMsg msg) { + String data = null; + Map metadata = null; + String messageType = null; + if (msgData.containsKey(RuleNodeScriptFactory.MSG)) { + data = JacksonUtil.toString(msgData.get(RuleNodeScriptFactory.MSG)); + } + if (msgData.containsKey(RuleNodeScriptFactory.METADATA)) { + Object msgMetadataObj = msgData.get(RuleNodeScriptFactory.METADATA); + if (msgMetadataObj instanceof Map) { + metadata = ((Map) msgMetadataObj).entrySet().stream().filter(e -> e.getValue() != null) + .collect(Collectors.toMap(e -> e.getKey().toString(), e -> e.getValue().toString())); + } else { + metadata = JacksonUtil.convertValue(msgMetadataObj, new TypeReference<>() { + }); + } + } + if (msgData.containsKey(RuleNodeScriptFactory.MSG_TYPE)) { + messageType = msgData.get(RuleNodeScriptFactory.MSG_TYPE).toString(); + } + String newData = data != null ? data : msg.getData(); + TbMsgMetaData newMetadata = metadata != null ? new TbMsgMetaData(metadata) : msg.getMetaData().copy(); + String newMessageType = !StringUtils.isEmpty(messageType) ? messageType : msg.getType(); + return TbMsg.transformMsg(msg, newMessageType, msg.getOriginator(), newMetadata, newData); + } + + private static ListenableFuture wrongResultType(Object result) { + String className = toClassName(result); + log.warn("Wrong result type: {}", className); + return Futures.immediateFailedFuture(new ScriptException("Wrong result type: " + className)); + } + + private static String toClassName(Object result) { + return result != null ? result.getClass().getSimpleName() : "null"; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java new file mode 100644 index 0000000000..3a88bb65a4 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptEngine.java @@ -0,0 +1,133 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.script; + +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.thingsboard.rule.engine.api.ScriptEngine; +import org.thingsboard.script.api.ScriptInvokeService; +import org.thingsboard.script.api.ScriptType; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; + +import javax.script.ScriptException; +import java.util.List; +import java.util.Set; +import java.util.UUID; +import java.util.concurrent.ExecutionException; + + +@Slf4j +public abstract class RuleNodeScriptEngine implements ScriptEngine { + + private final T scriptInvokeService; + + private final UUID scriptId; + private final TenantId tenantId; + + public RuleNodeScriptEngine(TenantId tenantId, T scriptInvokeService, String script, String... argNames) { + this.tenantId = tenantId; + this.scriptInvokeService = scriptInvokeService; + try { + this.scriptId = this.scriptInvokeService.eval(tenantId, ScriptType.RULE_NODE_SCRIPT, script, argNames).get(); + } catch (Exception e) { + Throwable t = e; + if (e instanceof ExecutionException) { + t = e.getCause(); + } + throw new IllegalArgumentException("Can't compile script: " + t.getMessage(), t); + } + } + + protected abstract Object[] prepareArgs(TbMsg msg); + + @Override + public ListenableFuture> executeUpdateAsync(TbMsg msg) { + ListenableFuture result = executeScriptAsync(msg); + return Futures.transformAsync(result, + json -> executeUpdateTransform(msg, json), + MoreExecutors.directExecutor()); + } + + protected abstract ListenableFuture> executeUpdateTransform(TbMsg msg, R result); + + @Override + public ListenableFuture executeGenerateAsync(TbMsg prevMsg) { + return Futures.transformAsync(executeScriptAsync(prevMsg), + result -> executeGenerateTransform(prevMsg, result), + MoreExecutors.directExecutor()); + } + + protected abstract ListenableFuture executeGenerateTransform(TbMsg prevMsg, R result); + + @Override + public ListenableFuture executeToStringAsync(TbMsg msg) { + return Futures.transformAsync(executeScriptAsync(msg), this::executeToStringTransform, MoreExecutors.directExecutor()); + } + + + @Override + public ListenableFuture executeFilterAsync(TbMsg msg) { + return Futures.transformAsync(executeScriptAsync(msg), + this::executeFilterTransform, + MoreExecutors.directExecutor()); + } + + protected abstract ListenableFuture executeToStringTransform(R result); + + protected abstract ListenableFuture executeFilterTransform(R result); + + protected abstract ListenableFuture> executeSwitchTransform(R result); + + @Override + public ListenableFuture> executeSwitchAsync(TbMsg msg) { + return Futures.transformAsync(executeScriptAsync(msg), + this::executeSwitchTransform, + MoreExecutors.directExecutor()); //usually runs in a callbackExecutor + } + + ListenableFuture executeScriptAsync(TbMsg msg) { + log.trace("execute script async, msg {}", msg); + Object[] inArgs = prepareArgs(msg); + return executeScriptAsync(msg.getCustomerId(), inArgs[0], inArgs[1], inArgs[2]); + } + + ListenableFuture executeScriptAsync(CustomerId customerId, Object... args) { + return Futures.transformAsync(scriptInvokeService.invokeScript(tenantId, customerId, this.scriptId, args), + o -> { + try { + return Futures.immediateFuture(convertResult(o)); + } catch (Exception e) { + if (e.getCause() instanceof ScriptException) { + return Futures.immediateFailedFuture(e.getCause()); + } else if (e.getCause() instanceof RuntimeException) { + return Futures.immediateFailedFuture(new ScriptException(e.getCause().getMessage())); + } else { + return Futures.immediateFailedFuture(new ScriptException(e)); + } + } + }, MoreExecutors.directExecutor()); + } + + public void destroy() { + scriptInvokeService.release(this.scriptId); + } + + protected abstract R convertResult(Object result); +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java new file mode 100644 index 0000000000..74230d93ec --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/DefaultTokenOutdatingService.java @@ -0,0 +1,71 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.security.auth; + +import io.jsonwebtoken.Claims; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.TbTransactionalCache; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; +import org.thingsboard.server.common.data.security.model.JwtToken; +import org.thingsboard.server.service.security.model.token.JwtTokenFactory; + +import java.util.Optional; + +import static java.util.concurrent.TimeUnit.MILLISECONDS; + +@Service +public class DefaultTokenOutdatingService implements TokenOutdatingService { + + private final TbTransactionalCache cache; + private final JwtTokenFactory tokenFactory; + + public DefaultTokenOutdatingService(@Qualifier("UsersSessionInvalidation") TbTransactionalCache cache, JwtTokenFactory tokenFactory) { + this.cache = cache; + this.tokenFactory = tokenFactory; + } + + @EventListener(classes = UserAuthDataChangedEvent.class) + public void onUserAuthDataChanged(UserAuthDataChangedEvent event) { + if (StringUtils.hasText(event.getId())) { + cache.put(event.getId(), event.getTs()); + } + } + + @Override + public boolean isOutdated(JwtToken token, UserId userId) { + Claims claims = tokenFactory.parseTokenClaims(token).getBody(); + long issueTime = claims.getIssuedAt().getTime(); + String sessionId = claims.get("sessionId", String.class); + if (isTokenOutdated(issueTime, userId.toString())){ + return true; + } else { + return sessionId != null && isTokenOutdated(issueTime, sessionId); + } + } + + private Boolean isTokenOutdated(long issueTime, String sessionId) { + return Optional.ofNullable(cache.get(sessionId)).map(outdatageTime -> isTokenOutdated(issueTime, outdatageTime.get())).orElse(false); + } + + private boolean isTokenOutdated(long issueTime, Long outdatageTime) { + return MILLISECONDS.toSeconds(issueTime) < MILLISECONDS.toSeconds(outdatageTime); + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java index a623fc6862..02c1bf685a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/TokenOutdatingService.java @@ -15,67 +15,11 @@ */ package org.thingsboard.server.service.security.auth; -import io.jsonwebtoken.Claims; -import lombok.RequiredArgsConstructor; -import org.springframework.cache.Cache; -import org.springframework.cache.CacheManager; -import org.springframework.context.event.EventListener; -import org.springframework.stereotype.Service; -import org.thingsboard.server.common.data.CacheConstants; import org.thingsboard.server.common.data.id.UserId; -import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; import org.thingsboard.server.common.data.security.model.JwtToken; -import org.thingsboard.server.config.JwtSettings; -import org.thingsboard.server.service.security.model.token.JwtTokenFactory; -import javax.annotation.PostConstruct; -import java.util.Optional; +public interface TokenOutdatingService { -import static java.util.concurrent.TimeUnit.MILLISECONDS; -import static java.util.concurrent.TimeUnit.SECONDS; + boolean isOutdated(JwtToken token, UserId userId); -@Service -@RequiredArgsConstructor -public class TokenOutdatingService { - private final CacheManager cacheManager; - private final JwtTokenFactory tokenFactory; - private final JwtSettings jwtSettings; - private Cache usersUpdateTimeCache; - - @PostConstruct - protected void initCache() { - usersUpdateTimeCache = cacheManager.getCache(CacheConstants.USERS_UPDATE_TIME_CACHE); - } - - @EventListener(classes = UserAuthDataChangedEvent.class) - public void onUserAuthDataChanged(UserAuthDataChangedEvent event) { - usersUpdateTimeCache.put(toKey(event.getUserId()), event.getTs()); - } - - public boolean isOutdated(JwtToken token, UserId userId) { - Claims claims = tokenFactory.parseTokenClaims(token).getBody(); - long issueTime = claims.getIssuedAt().getTime(); - - return Optional.ofNullable(usersUpdateTimeCache.get(toKey(userId), Long.class)) - .map(outdatageTime -> { - if (System.currentTimeMillis() - outdatageTime <= SECONDS.toMillis(jwtSettings.getRefreshTokenExpTime())) { - return MILLISECONDS.toSeconds(issueTime) < MILLISECONDS.toSeconds(outdatageTime); - } else { - /* - * Means that since the outdating has passed more than - * the lifetime of refresh token (the longest lived) - * and there is no need to store outdatage time anymore - * as all the tokens issued before the outdatage time - * are now expired by themselves - * */ - usersUpdateTimeCache.evict(toKey(userId)); - return false; - } - }) - .orElse(false); - } - - private String toKey(UserId userId) { - return userId.getId().toString(); - } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java index dcdce946e0..9ce2ae75fc 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/JwtAuthenticationProvider.java @@ -20,8 +20,8 @@ import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; import org.springframework.stereotype.Component; -import org.thingsboard.server.service.security.auth.TokenOutdatingService; import org.thingsboard.server.service.security.auth.JwtAuthenticationToken; +import org.thingsboard.server.service.security.auth.TokenOutdatingService; import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java index 8003cfd012..27549752e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenAuthenticationProvider.java @@ -33,11 +33,11 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.service.security.auth.TokenOutdatingService; import org.thingsboard.server.common.data.security.UserCredentials; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken; +import org.thingsboard.server.service.security.auth.TokenOutdatingService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; @@ -66,7 +66,7 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide } else { securityUser = authenticateByPublicId(principal.getValue()); } - + securityUser.setSessionId(unsafeUser.getSessionId()); if (tokenOutdatingService.isOutdated(rawAccessToken, securityUser.getId())) { throw new CredentialsExpiredException("Token is outdated"); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java deleted file mode 100644 index c5a666d464..0000000000 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java +++ /dev/null @@ -1,38 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.server.service.security.auth.jwt; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.security.model.JwtToken; -import org.thingsboard.server.service.security.model.SecurityUser; -import org.thingsboard.server.service.security.model.token.JwtTokenFactory; - -@Component -public class RefreshTokenRepository { - - private final JwtTokenFactory tokenFactory; - - @Autowired - public RefreshTokenRepository(final JwtTokenFactory tokenFactory) { - this.tokenFactory = tokenFactory; - } - - public JwtToken requestRefreshToken(SecurityUser user) { - return tokenFactory.createRefreshToken(user); - } - -} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java index cbb5fa7306..ba0e4aa0b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/AbstractOAuth2ClientMapper.java @@ -47,6 +47,7 @@ import org.thingsboard.server.dao.oauth2.OAuth2User; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.service.entitiy.user.TbUserService; import org.thingsboard.server.service.install.InstallScripts; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; @@ -81,6 +82,9 @@ public abstract class AbstractOAuth2ClientMapper { @Autowired private InstallScripts installScripts; + @Autowired + private TbUserService tbUserService; + @Autowired protected TbTenantProfileCache tenantProfileCache; @@ -146,7 +150,7 @@ public abstract class AbstractOAuth2ClientMapper { user.setAdditionalInfo(additionalInfo); - user = userService.saveUser(user); + user = tbUserService.save(tenantId, customerId, user, false, null, null); if (config.isActivateUser()) { UserCredentials userCredentials = userService.findUserCredentialsByUserId(user.getTenantId(), user.getId()); userService.activateUserCredentials(user.getTenantId(), userCredentials.getActivateToken(), passwordEncoder.encode("")); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java index 877855e705..a6a5fa456f 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/HttpCookieOAuth2AuthorizationRequestRepository.java @@ -25,6 +25,8 @@ import javax.servlet.http.HttpServletResponse; @Component public class HttpCookieOAuth2AuthorizationRequestRepository implements AuthorizationRequestRepository { public static final String OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME = "oauth2_auth_request"; + public static final String PREV_URI_PARAMETER = "prevUri"; + public static final String PREV_URI_COOKIE_NAME = "prev_uri"; private static final int cookieExpireSeconds = 180; @Override @@ -40,6 +42,9 @@ public class HttpCookieOAuth2AuthorizationRequestRepository implements Authoriza CookieUtils.deleteCookie(request, response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME); return; } + if (request.getParameter(PREV_URI_PARAMETER) != null) { + CookieUtils.addCookie(response, PREV_URI_COOKIE_NAME, request.getParameter(PREV_URI_PARAMETER), cookieExpireSeconds); + } CookieUtils.addCookie(response, OAUTH2_AUTHORIZATION_REQUEST_COOKIE_NAME, CookieUtils.serialize(authorizationRequest), cookieExpireSeconds); } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index e2a78eb605..9fd2a680b4 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java @@ -25,32 +25,36 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.oauth2.OAuth2Registration; -import org.thingsboard.server.common.data.security.model.JwtToken; import org.thingsboard.server.dao.oauth2.OAuth2Service; import org.thingsboard.server.queue.util.TbCoreComponent; -import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; +import org.thingsboard.server.service.security.model.JwtTokenPair; +import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.system.SystemSecurityService; +import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.IOException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Optional; import java.util.UUID; +import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAuth2AuthorizationRequestRepository.PREV_URI_COOKIE_NAME; + @Slf4j @Component(value = "oauth2AuthenticationSuccessHandler") @TbCoreComponent public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { private final JwtTokenFactory tokenFactory; - private final RefreshTokenRepository refreshTokenRepository; private final OAuth2ClientMapperProvider oauth2ClientMapperProvider; private final OAuth2Service oAuth2Service; private final OAuth2AuthorizedClientService oAuth2AuthorizedClientService; @@ -59,14 +63,12 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS @Autowired public Oauth2AuthenticationSuccessHandler(final JwtTokenFactory tokenFactory, - final RefreshTokenRepository refreshTokenRepository, final OAuth2ClientMapperProvider oauth2ClientMapperProvider, final OAuth2Service oAuth2Service, final OAuth2AuthorizedClientService oAuth2AuthorizedClientService, final HttpCookieOAuth2AuthorizationRequestRepository httpCookieOAuth2AuthorizationRequestRepository, final SystemSecurityService systemSecurityService) { this.tokenFactory = tokenFactory; - this.refreshTokenRepository = refreshTokenRepository; this.oauth2ClientMapperProvider = oauth2ClientMapperProvider; this.oAuth2Service = oAuth2Service; this.oAuth2AuthorizedClientService = oAuth2AuthorizedClientService; @@ -85,6 +87,11 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS baseUrl = callbackUrlScheme + ":"; } else { baseUrl = this.systemSecurityService.getBaseUrl(TenantId.SYS_TENANT_ID, new CustomerId(EntityId.NULL_UUID), request); + Optional prevUrlOpt = CookieUtils.getCookie(request, PREV_URI_COOKIE_NAME); + if (prevUrlOpt.isPresent()) { + baseUrl += prevUrlOpt.get().getValue(); + CookieUtils.deleteCookie(request, response, PREV_URI_COOKIE_NAME); + } } try { OAuth2AuthenticationToken token = (OAuth2AuthenticationToken) authentication; @@ -97,11 +104,11 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS SecurityUser securityUser = mapper.getOrCreateUserByClientPrincipal(request, token, oAuth2AuthorizedClient.getAccessToken().getTokenValue(), registration); - JwtToken accessToken = tokenFactory.createAccessJwtToken(securityUser); - JwtToken refreshToken = refreshTokenRepository.requestRefreshToken(securityUser); + JwtTokenPair tokenPair = tokenFactory.createTokenPair(securityUser); clearAuthenticationAttributes(request, response); - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); + getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + tokenPair.getToken() + "&refreshToken=" + tokenPair.getRefreshToken()); + systemSecurityService.logLoginAction(securityUser, new RestAuthenticationDetails(request), ActionType.LOGIN, registration.getName(), null); } catch (Exception e) { log.debug("Error occurred during processing authentication success result. " + "request [{}], response [{}], authentication [{}]", request, response, authentication, e); diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java index b4f0b293d3..4d7ef01914 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/rest/RestAwareAuthenticationSuccessHandler.java @@ -25,7 +25,6 @@ import org.springframework.security.web.authentication.AuthenticationSuccessHand import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.service.security.auth.MfaAuthenticationToken; -import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.model.JwtTokenPair; import org.thingsboard.server.service.security.model.SecurityUser; @@ -45,7 +44,6 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc private final ObjectMapper mapper; private final JwtTokenFactory tokenFactory; private final TwoFaConfigManager twoFaConfigManager; - private final RefreshTokenRepository refreshTokenRepository; @Override public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, @@ -62,8 +60,7 @@ public class RestAwareAuthenticationSuccessHandler implements AuthenticationSucc tokenPair.setRefreshToken(null); tokenPair.setScope(Authority.PRE_VERIFICATION_TOKEN); } else { - tokenPair.setToken(tokenFactory.createAccessJwtToken(securityUser).getToken()); - tokenPair.setRefreshToken(refreshTokenRepository.requestRefreshToken(securityUser).getToken()); + tokenPair = tokenFactory.createTokenPair(securityUser); } response.setStatus(HttpStatus.OK.value()); diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java index 380d6537f2..b7f480dfab 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/SecurityUser.java @@ -21,6 +21,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.UserId; import java.util.Collection; +import java.util.UUID; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -31,6 +32,7 @@ public class SecurityUser extends User { private Collection authorities; private boolean enabled; private UserPrincipal userPrincipal; + private String sessionId; public SecurityUser() { super(); @@ -44,6 +46,7 @@ public class SecurityUser extends User { super(user); this.enabled = enabled; this.userPrincipal = userPrincipal; + this.sessionId = UUID.randomUUID().toString(); } public Collection getAuthorities() { @@ -71,4 +74,11 @@ public class SecurityUser extends User { this.userPrincipal = userPrincipal; } + public String getSessionId() { + return sessionId; + } + + public void setSessionId(String sessionId) { + this.sessionId = sessionId; + } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java index 8200f0c3d3..dca366df18 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java @@ -60,6 +60,7 @@ public class JwtTokenFactory { private static final String IS_PUBLIC = "isPublic"; private static final String TENANT_ID = "tenantId"; private static final String CUSTOMER_ID = "customerId"; + private static final String SESSION_ID = "sessionId"; private final JwtSettings settings; @@ -119,6 +120,9 @@ public class JwtTokenFactory { if (customerId != null) { securityUser.setCustomerId(new CustomerId(UUID.fromString(customerId))); } + if (claims.get(SESSION_ID, String.class) != null) { + securityUser.setSessionId(claims.get(SESSION_ID, String.class)); + } UserPrincipal principal; if (securityUser.getAuthority() != Authority.PRE_VERIFICATION_TOKEN) { @@ -161,6 +165,9 @@ public class JwtTokenFactory { UserPrincipal principal = new UserPrincipal(isPublic ? UserPrincipal.Type.PUBLIC_ID : UserPrincipal.Type.USER_NAME, subject); SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class)))); securityUser.setUserPrincipal(principal); + if (claims.get(SESSION_ID, String.class) != null) { + securityUser.setSessionId(claims.get(SESSION_ID, String.class)); + } return securityUser; } @@ -183,6 +190,9 @@ public class JwtTokenFactory { Claims claims = Jwts.claims().setSubject(principal.getValue()); claims.put(USER_ID, securityUser.getId().getId().toString()); claims.put(SCOPES, scopes); + if (securityUser.getSessionId() != null) { + claims.put(SESSION_ID, securityUser.getSessionId()); + } ZonedDateTime currentTime = ZonedDateTime.now(); diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java index 5e8965c140..97b433661a 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/DefaultSystemSecurityService.java @@ -263,6 +263,11 @@ public class DefaultSystemSecurityService implements SystemSecurityService { @Override public void logLoginAction(User user, Object authenticationDetails, ActionType actionType, Exception e) { + logLoginAction(user, authenticationDetails, actionType, null, e); + } + + @Override + public void logLoginAction(User user, Object authenticationDetails, ActionType actionType, String provider, Exception e) { String clientAddress = "Unknown"; String browser = "Unknown"; String os = "Unknown"; @@ -278,38 +283,38 @@ public class DefaultSystemSecurityService implements SystemSecurityService { browser += " " + userAgent.userAgent.major; if (userAgent.userAgent.minor != null) { browser += "." + userAgent.userAgent.minor; - if (userAgent.userAgent.patch != null) { - browser += "." + userAgent.userAgent.patch; - } + if (userAgent.userAgent.patch != null) { + browser += "." + userAgent.userAgent.patch; } } } - if (userAgent.os != null) { - os = userAgent.os.family; - if (userAgent.os.major != null) { - os += " " + userAgent.os.major; - if (userAgent.os.minor != null) { - os += "." + userAgent.os.minor; - if (userAgent.os.patch != null) { - os += "." + userAgent.os.patch; - if (userAgent.os.patchMinor != null) { - os += "." + userAgent.os.patchMinor; - } + } + if (userAgent.os != null) { + os = userAgent.os.family; + if (userAgent.os.major != null) { + os += " " + userAgent.os.major; + if (userAgent.os.minor != null) { + os += "." + userAgent.os.minor; + if (userAgent.os.patch != null) { + os += "." + userAgent.os.patch; + if (userAgent.os.patchMinor != null) { + os += "." + userAgent.os.patchMinor; } } } } - if (userAgent.device != null) { - device = userAgent.device.family; - } + } + if (userAgent.device != null) { + device = userAgent.device.family; } } + } if (actionType == ActionType.LOGIN && e == null) { userService.setLastLoginTs(user.getTenantId(), user.getId()); } auditLogService.logEntityAction( user.getTenantId(), user.getCustomerId(), user.getId(), - user.getName(), user.getId(), null, actionType, e, clientAddress, browser, os, device); + user.getName(), user.getId(), null, actionType, e, clientAddress, browser, os, device, provider); } private static boolean isPositiveInteger(Integer val) { diff --git a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java index 6173d408c5..39d5935906 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java +++ b/application/src/main/java/org/thingsboard/server/service/security/system/SystemSecurityService.java @@ -44,4 +44,5 @@ public interface SystemSecurityService { void logLoginAction(User user, Object authenticationDetails, ActionType actionType, Exception e); + void logLoginAction(User user, Object authenticationDetails, ActionType actionType, String provider, Exception e); } diff --git a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java index 1c85e65325..b51221b1ba 100644 --- a/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java +++ b/application/src/main/java/org/thingsboard/server/service/sms/DefaultSmsService.java @@ -30,9 +30,9 @@ import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import javax.annotation.PostConstruct; @@ -45,11 +45,11 @@ public class DefaultSmsService implements SmsService { private final SmsSenderFactory smsSenderFactory; private final AdminSettingsService adminSettingsService; private final TbApiUsageStateService apiUsageStateService; - private final TbApiUsageClient apiUsageClient; + private final TbApiUsageReportClient apiUsageClient; private SmsSender smsSender; - public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { + public DefaultSmsService(SmsSenderFactory smsSenderFactory, AdminSettingsService adminSettingsService, TbApiUsageStateService apiUsageStateService, TbApiUsageReportClient apiUsageClient) { this.smsSenderFactory = smsSenderFactory; this.adminSettingsService = adminSettingsService; this.apiUsageStateService = apiUsageStateService; diff --git a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java index c9c54161dd..d52c712074 100644 --- a/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/state/DefaultDeviceStateService.java @@ -24,6 +24,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; +import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -121,7 +122,8 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService PERSISTENT_ATTRIBUTE_KEYS = Arrays.asList( new EntityKey(EntityKeyType.SERVER_ATTRIBUTE, LAST_ACTIVITY_TIME), @@ -152,14 +154,21 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService transformInactivityTimeout(ListenableFuture future) { return Futures.transformAsync(future, deviceStateData -> { - if (!persistToTelemetry || deviceStateData.getState().getInactivityTimeout() != TimeUnit.SECONDS.toMillis(defaultInactivityTimeoutInSec)) { + if (!persistToTelemetry || deviceStateData.getState().getInactivityTimeout() != defaultInactivityTimeoutMs) { return future; //fail fast } var attributesFuture = attributesService.find(TenantId.SYS_TENANT_ID, deviceStateData.getDeviceId(), SERVER_SCOPE, INACTIVITY_TIMEOUT); @@ -563,7 +572,7 @@ public class DefaultDeviceStateService extends AbstractPartitionBasedService data) { Arrays.stream(BulkImportColumnType.values()) diff --git a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java index b0e7b67f25..8cf17fd5b0 100644 --- a/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java +++ b/application/src/main/java/org/thingsboard/server/service/sync/vc/DefaultEntitiesVersionControlService.java @@ -37,6 +37,7 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.exception.ThingsboardErrorCode; import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.id.HasId; @@ -69,6 +70,7 @@ import org.thingsboard.server.common.data.sync.vc.request.load.SingleEntityVersi import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadConfig; import org.thingsboard.server.common.data.sync.vc.request.load.VersionLoadRequest; import org.thingsboard.server.dao.DaoUtil; +import org.thingsboard.server.dao.edge.EdgeService; import org.thingsboard.server.dao.exception.DeviceCredentialsValidationException; import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.entitiy.TbNotificationEntityService; @@ -112,6 +114,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont private final EntitiesExportImportService exportImportService; private final ExportableEntitiesService exportableEntitiesService; private final TbNotificationEntityService entityNotificationService; + private final EdgeService edgeService; private final TransactionTemplate transactionTemplate; private final TbTransactionalCache taskCache; @@ -427,11 +430,12 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont return exportableEntitiesService.findEntitiesByTenantId(ctx.getTenantId(), entityType, pageLink); }, 100, entity -> { if (ctx.getImportedEntities().get(entityType) == null || !ctx.getImportedEntities().get(entityType).contains(entity.getId())) { + List relatedEdgeIds = edgeService.findAllRelatedEdgeIds(ctx.getTenantId(), entity.getId()); exportableEntitiesService.removeById(ctx.getTenantId(), entity.getId()); ctx.addEventCallback(() -> { entityNotificationService.notifyDeleteEntity(ctx.getTenantId(), entity.getId(), - entity, null, ActionType.DELETED, null, ctx.getUser()); + entity, null, ActionType.DELETED, relatedEdgeIds, ctx.getUser()); }); ctx.registerDeleted(entityType); } @@ -533,7 +537,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont @Override public ListenableFuture autoCommit(User user, EntityId entityId) throws Exception { var repositorySettings = repositorySettingsService.get(user.getTenantId()); - if (repositorySettings == null) { + if (repositorySettings == null || repositorySettings.isReadOnly()) { return Futures.immediateFuture(null); } var autoCommitSettings = autoCommitSettingsService.get(user.getTenantId()); @@ -560,7 +564,7 @@ public class DefaultEntitiesVersionControlService implements EntitiesVersionCont @Override public ListenableFuture autoCommit(User user, EntityType entityType, List entityIds) throws Exception { var repositorySettings = repositorySettingsService.get(user.getTenantId()); - if (repositorySettings == null) { + if (repositorySettings == null || repositorySettings.isReadOnly()) { return Futures.immediateFuture(null); } var autoCommitSettings = autoCommitSettingsService.get(user.getTenantId()); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java index 4dc0dc238b..956dde8024 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java @@ -40,11 +40,11 @@ import org.thingsboard.server.common.data.query.AlarmDataQuery; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.alarm.AlarmOperationResult; import org.thingsboard.server.dao.alarm.AlarmService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.service.subscription.SubscriptionManagerService; @@ -61,13 +61,13 @@ import java.util.Optional; public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService implements AlarmSubscriptionService { private final AlarmService alarmService; - private final TbApiUsageClient apiUsageClient; + private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageStateService apiUsageStateService; public DefaultAlarmSubscriptionService(TbClusterService clusterService, PartitionService partitionService, AlarmService alarmService, - TbApiUsageClient apiUsageClient, + TbApiUsageReportClient apiUsageClient, TbApiUsageStateService apiUsageStateService) { super(clusterService, partitionService); this.alarmService = alarmService; @@ -129,6 +129,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService return alarmService.findAlarmByIdAsync(tenantId, alarmId); } + @Override + public Alarm findAlarmById(TenantId tenantId, AlarmId alarmId) { + return alarmService.findAlarmById(tenantId, alarmId); + } + @Override public ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId) { return alarmService.findAlarmInfoByIdAsync(tenantId, alarmId); diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java index a5f66968da..c8c76bb8f4 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/DefaultTelemetrySubscriptionService.java @@ -43,11 +43,11 @@ import org.thingsboard.server.common.data.kv.TsKvLatestRemovingResult; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.gen.transport.TransportProtos; import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import org.thingsboard.server.service.entitiy.entityview.TbEntityViewService; import org.thingsboard.server.service.subscription.TbSubscriptionUtils; @@ -77,7 +77,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer private final AttributesService attrService; private final TimeseriesService tsService; private final TbEntityViewService tbEntityViewService; - private final TbApiUsageClient apiUsageClient; + private final TbApiUsageReportClient apiUsageClient; private final TbApiUsageStateService apiUsageStateService; private ExecutorService tsCallBackExecutor; @@ -87,7 +87,7 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Lazy TbEntityViewService tbEntityViewService, TbClusterService clusterService, PartitionService partitionService, - TbApiUsageClient apiUsageClient, + TbApiUsageReportClient apiUsageClient, TbApiUsageStateService apiUsageStateService) { super(clusterService, partitionService); this.attrService = attrService; diff --git a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java index c4ce4e49b2..a56b1963d6 100644 --- a/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java +++ b/application/src/main/java/org/thingsboard/server/service/ttl/EdgeEventsCleanUpService.java @@ -17,15 +17,22 @@ package org.thingsboard.server.service.ttl; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.thingsboard.server.dao.edge.EdgeEventService; +import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.queue.discovery.PartitionService; import org.thingsboard.server.queue.util.TbCoreComponent; +import java.util.concurrent.TimeUnit; + +import static org.thingsboard.server.dao.model.ModelConstants.EDGE_EVENT_COLUMN_FAMILY_NAME; + @TbCoreComponent @Slf4j @Service +@ConditionalOnExpression("${sql.ttl.edge_events.enabled:true} && ${sql.ttl.edge_events.edge_event_ttl:0} > 0") public class EdgeEventsCleanUpService extends AbstractCleanUpService { public static final String RANDOM_DELAY_INTERVAL_MS_EXPRESSION = @@ -34,20 +41,29 @@ public class EdgeEventsCleanUpService extends AbstractCleanUpService { @Value("${sql.ttl.edge_events.edge_events_ttl}") private long ttl; - @Value("${sql.ttl.edge_events.enabled}") + @Value("${sql.edge_events.partition_size:168}") + private int partitionSizeInHours; + + @Value("${sql.ttl.edge_events.enabled:true}") private boolean ttlTaskExecutionEnabled; private final EdgeEventService edgeEventService; - public EdgeEventsCleanUpService(PartitionService partitionService, EdgeEventService edgeEventService) { + private final SqlPartitioningRepository partitioningRepository; + + public EdgeEventsCleanUpService(PartitionService partitionService, EdgeEventService edgeEventService, SqlPartitioningRepository partitioningRepository) { super(partitionService); this.edgeEventService = edgeEventService; + this.partitioningRepository = partitioningRepository; } @Scheduled(initialDelayString = RANDOM_DELAY_INTERVAL_MS_EXPRESSION, fixedDelayString = "${sql.ttl.edge_events.execution_interval_ms}") public void cleanUp() { + long edgeEventsExpTime = System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(ttl); if (ttlTaskExecutionEnabled && isSystemTenantPartitionMine()) { - edgeEventService.cleanupEvents(ttl); + edgeEventService.cleanupEvents(edgeEventsExpTime); + } else { + partitioningRepository.cleanupPartitionsCache(EDGE_EVENT_COLUMN_FAMILY_NAME, edgeEventsExpTime, TimeUnit.HOURS.toMillis(partitionSizeInHours)); } } diff --git a/application/src/main/resources/thingsboard.yml b/application/src/main/resources/thingsboard.yml index 9f791cee9b..0f110da90c 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -109,7 +109,7 @@ security: # JWT Token parameters jwt: tokenExpirationTime: "${JWT_TOKEN_EXPIRATION_TIME:9000}" # Number of seconds (2.5 hours) - refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}" # Number of seconds (1 week) + refreshTokenExpTime: "${JWT_REFRESH_TOKEN_EXPIRATION_TIME:604800}" # Number of seconds (1 week). tokenIssuer: "${JWT_TOKEN_ISSUER:thingsboard.io}" tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}" # Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator @@ -200,6 +200,14 @@ cassandra: username: "${CASSANDRA_USERNAME:}" # Specify your password password: "${CASSANDRA_PASSWORD:}" + # Astra DB connect https://astra.datastax.com/ + cloud: + # /etc/thingsboard/astra/secure-connect-thingsboard.zip + secure_connect_bundle_path: "${CASSANDRA_CLOUD_SECURE_BUNDLE_PATH:}" + # DucitQPHMzPCBOZqFYexAfKk + client_id: "${CASSANDRA_CLOUD_CLIENT_ID:}" + # ZnF7FpuHp43FP5BzM+KY8wGmSb4Ql6BhT4Z7sOU13ze+gXQ-n7OkFpNuB,oACUIQObQnK0g4bSPoZhK5ejkcF9F.j6f64j71Sr.tiRe0Fsq2hPS1ZCGSfAaIgg63IydG + client_secret: "${CASSANDRA_CLOUD_CLIENT_SECRET:}" # Cassandra cluster connection socket parameters # socket: @@ -265,6 +273,7 @@ sql: batch_size: "${SQL_EDGE_EVENTS_BATCH_SIZE:1000}" batch_max_delay: "${SQL_EDGE_EVENTS_BATCH_MAX_DELAY_MS:100}" stats_print_interval_ms: "${SQL_EDGE_EVENTS_BATCH_STATS_PRINT_MS:10000}" + partition_size: "${SQL_EDGE_EVENTS_PARTITION_SIZE_HOURS:168}" # Number of hours to partition the events. The current value corresponds to one week. audit_logs: partition_size: "${SQL_AUDIT_LOGS_PARTITION_SIZE_HOURS:168}" # Default value - 1 week # Specify whether to sort entities before batch update. Should be enabled for cluster mode to avoid deadlocks @@ -310,7 +319,9 @@ sql: ttl: "${SQL_TTL_AUDIT_LOGS_SECS:0}" # Disabled by default. Accuracy of the cleanup depends on the sql.audit_logs.partition_size checking_interval_ms: "${SQL_TTL_AUDIT_LOGS_CHECKING_INTERVAL_MS:86400000}" # Default value - 1 day relations: - max_level: "${SQL_RELATIONS_MAX_LEVEL:50}" # //This value has to be reasonable small to prevent infinite recursion as early as possible + max_level: "${SQL_RELATIONS_MAX_LEVEL:50}" # This value has to be reasonable small to prevent infinite recursion as early as possible + pool_size: "${SQL_RELATIONS_POOL_SIZE:4}" # This value has to be reasonable small to prevent relation query blocking all other DB calls + query_timeout: "${SQL_RELATIONS_QUERY_TIMEOUT_SEC:20}" # This value has to be reasonable small to prevent relation query blocking all other DB calls # Actor system parameters actors: @@ -332,8 +343,6 @@ actors: rule: # Specify thread pool size for database request callbacks executor service db_callback_thread_pool_size: "${ACTORS_RULE_DB_CALLBACK_THREAD_POOL_SIZE:50}" - # Specify thread pool size for javascript executor service - js_thread_pool_size: "${ACTORS_RULE_JS_THREAD_POOL_SIZE:50}" # Specify thread pool size for mail sender executor service mail_thread_pool_size: "${ACTORS_RULE_MAIL_THREAD_POOL_SIZE:40}" # Specify thread pool size for password reset emails @@ -420,8 +429,9 @@ cache: attributes: timeToLiveInMinutes: "${CACHE_SPECS_ATTRIBUTES_TTL:1440}" maxSize: "${CACHE_SPECS_ATTRIBUTES_MAX_SIZE:100000}" - usersUpdateTime: - timeToLiveInMinutes: "${CACHE_SPECS_USERS_UPDATE_TIME_TTL:20000}" + userSessionsInvalidation: + # The value of this TTL is ignored and replaced by JWT refresh token expiration time + timeToLiveInMinutes: "0" maxSize: "${CACHE_SPECS_USERS_UPDATE_TIME_MAX_SIZE:10000}" otaPackages: timeToLiveInMinutes: "${CACHE_SPECS_OTA_PACKAGES_TTL:60}" @@ -533,6 +543,7 @@ spring.servlet.multipart.max-file-size: "50MB" spring.servlet.multipart.max-request-size: "50MB" spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation: "true" +# Note: as for current Spring JPA version, custom NullHandling for the Sort.Order is ignored and this parameter is used spring.jpa.properties.hibernate.order_by.default_null_ordering: "${SPRING_JPA_PROPERTIES_HIBERNATE_ORDER_BY_DEFAULT_NULL_ORDERING:last}" # SQL DAO Configuration @@ -599,6 +610,25 @@ state: defaultStateCheckIntervalInSec: "${DEFAULT_STATE_CHECK_INTERVAL:60}" persistToTelemetry: "${PERSIST_STATE_TO_TELEMETRY:false}" +mvel: + enabled: "${MVEL_ENABLED:true}" + max_total_args_size: "${MVEL_MAX_TOTAL_ARGS_SIZE:100000}" + max_result_size: "${MVEL_MAX_RESULT_SIZE:300000}" + max_script_body_size: "${MVEL_MAX_SCRIPT_BODY_SIZE:50000}" + # Maximum allowed MVEL script execution memory + max_memory_limit_mb: "${MVEL_MAX_MEMORY_LIMIT_MB: 8}" + # Maximum allowed MVEL script execution errors before it will be blacklisted + max_errors: "${MVEL_MAX_ERRORS:3}" + # MVEL Eval max request timeout in milliseconds. 0 - no timeout + max_requests_timeout: "${MVEL_MAX_REQUEST_TIMEOUT:500}" + # Maximum time in seconds for black listed function to stay in the list. + max_black_list_duration_sec: "${MVEL_MAX_BLACKLIST_DURATION_SEC:60}" + # Specify thread pool size for javascript executor service + thread_pool_size: "${MVEL_THREAD_POOL_SIZE:50}" + stats: + enabled: "${TB_MVEL_STATS_ENABLED:false}" + print_interval_ms: "${TB_MVEL_STATS_PRINT_INTERVAL_MS:10000}" + js: evaluator: "${JS_EVALUATOR:local}" # local/remote max_total_args_size: "${JS_MAX_TOTAL_ARGS_SIZE:100000}" @@ -606,6 +636,8 @@ js: max_script_body_size: "${JS_MAX_SCRIPT_BODY_SIZE:50000}" # Built-in JVM JavaScript environment properties local: + # Specify thread pool size for javascript executor service + js_thread_pool_size: "${LOCAL_JS_THREAD_POOL_SIZE:50}" # Use Sandboxed (secured) JVM JavaScript environment use_js_sandbox: "${USE_LOCAL_JS_SANDBOX:true}" # Specify thread pool size for JavaScript sandbox resource monitor @@ -623,6 +655,8 @@ js: print_interval_ms: "${TB_JS_LOCAL_STATS_PRINT_INTERVAL_MS:10000}" # Remote JavaScript environment properties remote: + # Specify thread pool size for javascript executor service + js_thread_pool_size: "${REMOTE_JS_THREAD_POOL_SIZE:50}" # Maximum allowed JavaScript execution errors before JavaScript will be blacklisted max_errors: "${REMOTE_JS_SANDBOX_MAX_ERRORS:3}" # Maximum time in seconds for black listed function to stay in the list. diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java index 3783b746f3..e2b7d700bd 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -234,7 +234,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { customerUser = createUserAndLogin(customerUser, CUSTOMER_USER_PASSWORD); customerUserId = customerUser.getId(); - logout(); + resetTokens(); log.info("Executed web test setup"); } @@ -336,7 +336,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { Assert.assertNotNull(savedDifferentCustomer); differentCustomerId = savedDifferentCustomer.getId(); - logout(); + resetTokens(); } protected void deleteDifferentTenant() throws Exception { @@ -350,7 +350,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { protected User createUserAndLogin(User user, String password) throws Exception { User savedUser = doPost("/api/user", user, User.class); - logout(); + resetTokens(); JsonNode activateRequest = getActivateRequest(password); JsonNode tokenInfo = readResponse(doPost("/api/noauth/activate", activateRequest).andExpect(status().isOk()), JsonNode.class); validateAndSetJwtToken(tokenInfo, user.getEmail()); @@ -375,9 +375,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { } protected void login(String username, String password) throws Exception { - this.token = null; - this.refreshToken = null; - this.username = null; + resetTokens(); JsonNode tokenInfo = readResponse(doPost("/api/auth/login", new LoginRequest(username, password)).andExpect(status().isOk()), JsonNode.class); validateAndSetJwtToken(tokenInfo, username); } @@ -413,12 +411,16 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { Assert.assertEquals(username, subject); } - protected void logout() throws Exception { + protected void resetTokens() throws Exception { this.token = null; this.refreshToken = null; this.username = null; } + protected void logout() throws Exception { + doPost("/api/auth/logout").andExpect(status().isOk()); + } + protected void setJwtToken(MockHttpServletRequestBuilder request) { if (this.token != null) { request.header(ThingsboardSecurityConfiguration.JWT_TOKEN_HEADER_PARAM, "Bearer " + this.token); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java index 4419e0e425..493b1818d9 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAlarmControllerTest.java @@ -77,7 +77,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { device.setCustomerId(customerId); customerDevice = doPost("/api/device", device, Device.class); - logout(); + resetTokens(); } @After @@ -423,7 +423,7 @@ public abstract class BaseAlarmControllerTest extends AbstractControllerTest { testNotifyEntityNeverMsgToEdgeServiceOneTime(alarm, alarm.getId(), tenantId, ActionType.ADDED); - logout(); + resetTokens(); JsonNode publicLoginRequest = JacksonUtil.toJsonNode("{\"publicId\": \"" + publicId + "\"}"); JsonNode tokens = doPost("/api/auth/login/public", publicLoginRequest, JsonNode.class); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAuthControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAuthControllerTest.java index f64cac0f99..627ce1a02b 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAuthControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAuthControllerTest.java @@ -15,13 +15,15 @@ */ package org.thingsboard.server.controller; +import org.junit.Test; +import org.thingsboard.server.common.data.security.Authority; + +import java.util.concurrent.TimeUnit; + import static org.hamcrest.Matchers.is; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import org.thingsboard.server.common.data.security.Authority; -import org.junit.Test; - public abstract class BaseAuthControllerTest extends AbstractControllerTest { @Test @@ -57,9 +59,13 @@ public abstract class BaseAuthControllerTest extends AbstractControllerTest { .andExpect(jsonPath("$.authority",is(Authority.SYS_ADMIN.name()))) .andExpect(jsonPath("$.email",is(SYS_ADMIN_EMAIL))); + TimeUnit.SECONDS.sleep(1); //We need to make sure that event for invalidating token was successfully processed + logout(); doGet("/api/auth/user") .andExpect(status().isUnauthorized()); + + resetTokens(); } @Test diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java index b811497b9f..3fe80b1112 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseDeviceProfileControllerTest.java @@ -16,12 +16,6 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.core.type.TypeReference; -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.google.protobuf.Descriptors; -import com.google.protobuf.DynamicMessage; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.util.JsonFormat; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; import org.junit.After; import org.junit.Assert; import org.junit.Before; @@ -46,11 +40,9 @@ import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.audit.ActionType; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.TransportPayloadTypeConfiguration; import org.thingsboard.server.common.data.id.DeviceProfileId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; @@ -62,13 +54,9 @@ import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; import java.util.List; -import java.util.Set; import java.util.stream.Collectors; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; import static org.thingsboard.server.common.data.ota.OtaPackageType.SOFTWARE; @@ -749,137 +737,6 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController "}", "[Transport Configuration] invalid attributes proto schema provided! OneOf definition groups don't support!"); } - @Test - public void testSaveProtoDeviceProfileWithMessageNestedTypes() throws Exception { - String schema = "syntax = \"proto3\";\n" + - "\n" + - "package testnested;\n" + - "\n" + - "message Outer {\n" + - " message MiddleAA {\n" + - " message Inner {\n" + - " optional int64 ival = 1;\n" + - " optional bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " message MiddleBB {\n" + - " message Inner {\n" + - " optional int32 ival = 1;\n" + - " optional bool booly = 2;\n" + - " }\n" + - " Inner inner = 1;\n" + - " }\n" + - " MiddleAA middleAA = 1;\n" + - " MiddleBB middleBB = 2;\n" + - "}"; - DynamicSchema dynamicSchema = getDynamicSchema(schema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(5, messageTypes.size()); - assertTrue(messageTypes.contains("testnested.Outer")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); - assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); - - DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder - .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) - .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) - .build(); - - DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); - Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); - DynamicMessage middleAAMsg = middleAAMsgBuilder - .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) - .build(); - - DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); - Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder - .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) - .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) - .build(); - - DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); - Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); - DynamicMessage middleBBMsg = middleBBMsgBuilder - .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) - .build(); - - - DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); - Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); - DynamicMessage outerMsg = outerMsgBuilder - .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) - .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) - .build(); - - assertEquals("{\n" + - " \"middleAA\": {\n" + - " \"inner\": {\n" + - " \"ival\": \"1\",\n" + - " \"booly\": true\n" + - " }\n" + - " },\n" + - " \"middleBB\": {\n" + - " \"inner\": {\n" + - " \"ival\": 0,\n" + - " \"booly\": false\n" + - " }\n" + - " }\n" + - "}", dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); - } - - @Test - public void testSaveProtoDeviceProfileWithMessageOneOfs() throws Exception { - String schema = "syntax = \"proto3\";\n" + - "\n" + - "package testoneofs;\n" + - "\n" + - "message SubMessage {\n" + - " repeated string name = 1;\n" + - "}\n" + - "\n" + - "message SampleMessage {\n" + - " optional int32 id = 1;\n" + - " oneof testOneOf {\n" + - " string name = 4;\n" + - " SubMessage subMessage = 9;\n" + - " }\n" + - "}"; - DynamicSchema dynamicSchema = getDynamicSchema(schema); - assertNotNull(dynamicSchema); - Set messageTypes = dynamicSchema.getMessageTypes(); - assertEquals(2, messageTypes.size()); - assertTrue(messageTypes.contains("testoneofs.SubMessage")); - assertTrue(messageTypes.contains("testoneofs.SampleMessage")); - - DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); - Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); - assertNotNull(sampleMsgDescriptor); - - List fields = sampleMsgDescriptor.getFields(); - assertEquals(3, fields.size()); - DynamicMessage sampleMsg = sampleMsgBuilder - .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") - .build(); - assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); - - DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); - Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); - DynamicMessage subMsg = subMsgBuilder - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") - .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") - .build(); - - DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); - assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", - dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); - } - @Test public void testSaveProtoDeviceProfileWithInvalidTelemetrySchemaTsField() throws Exception { testSaveDeviceProfileWithInvalidProtoSchema("syntax =\"proto3\";\n" + @@ -1127,23 +984,6 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController tenantAdmin.getId(), tenantAdmin.getEmail(), ActionType.ADDED, new DataValidationException(errorMsg)); } - private DynamicSchema getDynamicSchema(String schema) throws Exception { - DeviceProfile deviceProfile = testSaveDeviceProfileWithProtoPayloadType(schema); - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); - MqttDeviceProfileTransportConfiguration mqttDeviceProfileTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttDeviceProfileTransportConfiguration.getTransportPayloadTypeConfiguration(); - assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement protoFile = protoTransportPayloadConfiguration.getTransportProtoSchema(schema); - return protoTransportPayloadConfiguration.getDynamicSchema(protoFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); - } - - private String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { - DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); - return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); - } - @Test public void testDeleteDeviceProfileWithDeleteRelationsOk() throws Exception { DeviceProfileId deviceProfileId = savedDeviceProfile("DeviceProfile for Test WithRelationsOk").getId(); diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java index 4d852051fc..fa466ce93f 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeControllerTest.java @@ -820,30 +820,31 @@ public abstract class BaseEdgeControllerTest extends AbstractControllerTest { EdgeImitator edgeImitator = new EdgeImitator(EDGE_HOST, EDGE_PORT, edge.getRoutingKey(), edge.getSecret()); edgeImitator.ignoreType(UserCredentialsUpdateMsg.class); - edgeImitator.expectMessageAmount(14); + edgeImitator.expectMessageAmount(19); edgeImitator.connect(); assertThat(edgeImitator.waitForMessages()).as("await for messages on first connect").isTrue(); assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("one msg during sync process").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("one msg during sync process, another from edge creation").hasSize(2); - assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(2); + assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("one msg during sync process for 'default' device profile").hasSize(3); + assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("one msg once device assigned to edge").hasSize(2); + assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("two msgs during sync process for 'default' and 'test' asset profiles").hasSize(4); assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("two msgs - one during sync process, and one more once asset assigned to edge").hasSize(2); assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("one msg during sync process for tenant admin user").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update").hasSize(4); - edgeImitator.expectMessageAmount(11); + edgeImitator.expectMessageAmount(14); doPost("/api/edge/sync/" + edge.getId()); assertThat(edgeImitator.waitForMessages()).as("await for messages after edge sync rest api call").isTrue(); assertThat(edgeImitator.findAllMessagesByType(QueueUpdateMsg.class)).as("queue msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class)).as("rule chain msg").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(1); - assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(2); + assertThat(edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class)).as("device profile msg").hasSize(2); + assertThat(edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class)).as("asset profile msg").hasSize(3); assertThat(edgeImitator.findAllMessagesByType(AssetUpdateMsg.class)).as("asset update msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(UserUpdateMsg.class)).as("user update msg").hasSize(1); assertThat(edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class)).as("admin setting update msg").hasSize(4); + assertThat(edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class)).as("asset update msg").hasSize(1); edgeImitator.allowIgnoredTypes(); try { diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java index ef66270130..ccf218c132 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEdgeEventControllerTest.java @@ -22,6 +22,9 @@ import org.junit.After; import org.junit.Assert; import org.junit.Before; import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.test.mock.mockito.SpyBean; import org.springframework.test.context.TestPropertySource; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Tenant; @@ -29,16 +32,27 @@ import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.edge.Edge; import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; import org.thingsboard.server.common.data.edge.EdgeEventType; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.dao.edge.EdgeEventDao; +import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; +import org.thingsboard.server.service.ttl.EdgeEventsCleanUpService; +import java.time.LocalDate; +import java.time.ZoneOffset; import java.util.List; +import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @TestPropertySource(properties = { @@ -50,6 +64,18 @@ public abstract class BaseEdgeEventControllerTest extends AbstractControllerTest private Tenant savedTenant; private User tenantAdmin; + @Autowired + private EdgeEventDao edgeEventDao; + @SpyBean + private SqlPartitioningRepository partitioningRepository; + @Autowired + private EdgeEventsCleanUpService edgeEventsCleanUpService; + + @Value("#{${sql.edge_events.partition_size} * 60 * 60 * 1000}") + private long partitionDurationInMs; + @Value("${sql.ttl.edge_events.edge_event_ttl}") + private long edgeEventTtlInSec; + @Before public void beforeTest() throws Exception { loginSysAdmin(); @@ -114,6 +140,34 @@ public abstract class BaseEdgeEventControllerTest extends AbstractControllerTest Assert.assertTrue(edgeEvents.stream().anyMatch(ee -> EdgeEventType.RELATION.equals(ee.getType()))); } + @Test + public void saveEdgeEvent_thenCreatePartitionIfNotExist() { + reset(partitioningRepository); + EdgeEvent edgeEvent = createEdgeEvent(); + verify(partitioningRepository).createPartitionIfNotExists(eq("edge_event"), eq(edgeEvent.getCreatedTime()), eq(partitionDurationInMs)); + List partitions = partitioningRepository.fetchPartitions("edge_event"); + assertThat(partitions).singleElement().satisfies(partitionStartTs -> { + assertThat(partitionStartTs).isEqualTo(partitioningRepository.calculatePartitionStartTime(edgeEvent.getCreatedTime(), partitionDurationInMs)); + }); + } + + @Test + public void cleanUpEdgeEventByTtl_dropOldPartitions() { + long oldEdgeEventTs = LocalDate.of(2020, 10, 1).atStartOfDay().toInstant(ZoneOffset.UTC).toEpochMilli(); + long partitionStartTs = partitioningRepository.calculatePartitionStartTime(oldEdgeEventTs, partitionDurationInMs); + partitioningRepository.createPartitionIfNotExists("edge_event", oldEdgeEventTs, partitionDurationInMs); + List partitions = partitioningRepository.fetchPartitions("edge_event"); + assertThat(partitions).contains(partitionStartTs); + + edgeEventsCleanUpService.cleanUp(); + partitions = partitioningRepository.fetchPartitions("edge_event"); + assertThat(partitions).doesNotContain(partitionStartTs); + assertThat(partitions).allSatisfy(partitionsStart -> { + long partitionEndTs = partitionsStart + partitionDurationInMs; + assertThat(partitionEndTs).isGreaterThan(System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(edgeEventTtlInSec)); + }); + } + private List findEdgeEvents(EdgeId edgeId) throws Exception { return doGetTypedWithTimePageLink("/api/edge/" + edgeId.toString() + "/events?", new TypeReference>() { @@ -134,4 +188,19 @@ public abstract class BaseEdgeEventControllerTest extends AbstractControllerTest return asset; } + private EdgeEvent createEdgeEvent() { + EdgeEvent edgeEvent = new EdgeEvent(); + edgeEvent.setCreatedTime(System.currentTimeMillis()); + edgeEvent.setTenantId(tenantId); + edgeEvent.setAction(EdgeEventActionType.ADDED); + edgeEvent.setEntityId(tenantAdmin.getUuidId()); + edgeEvent.setType(EdgeEventType.ALARM); + try { + edgeEventDao.saveAsync(edgeEvent).get(); + } catch (InterruptedException | ExecutionException e) { + throw new RuntimeException(e); + } + return edgeEvent; + } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java index 9c01f4323d..133a53cc2d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -106,7 +106,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { ActionType.ADDED, ActionType.ADDED, 1, 1, 1); Mockito.reset(tbClusterService, auditLogService); - logout(); + resetTokens(); doGet("/api/noauth/activate?activateToken={activateToken}", TestMailService.currentActivateToken) .andExpect(status().isSeeOther()) .andExpect(header().string(HttpHeaders.LOCATION, "/login/createPassword?activateToken=" + TestMailService.currentActivateToken)); @@ -123,7 +123,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.email", is(email))); - logout(); + resetTokens(); login(email, "testPassword"); @@ -140,9 +140,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { doDelete("/api/user/" + savedUser.getId().getId().toString()) .andExpect(status().isOk()); - testNotifyEntityOneTimeMsgToEdgeServiceNever(foundUser, foundUser.getId(), foundUser.getId(), + testNotifyEntityAllOneTimeLogEntityActionEntityEqClass(foundUser, foundUser.getId(), foundUser.getId(), SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, - ActionType.DELETED, foundUser.getId().getId().toString()); + ActionType.DELETED, SYSTEM_TENANT.getId().toString()); } @Test @@ -218,7 +218,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { user.setLastName("Downs"); User savedUser = createUserAndLogin(user, "testPassword1"); - logout(); + resetTokens(); JsonNode resetPasswordByEmailRequest = new ObjectMapper().createObjectNode() .put("email", email); @@ -244,7 +244,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { .andExpect(jsonPath("$.authority", is(Authority.TENANT_ADMIN.name()))) .andExpect(jsonPath("$.email", is(email))); - logout(); + resetTokens(); login(email, "testPassword2"); doGet("/api/auth/user") @@ -461,7 +461,9 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { String email1 = "testEmail1"; List tenantAdminsEmail1 = new ArrayList<>(); - for (int i = 0; i < 124; i++) { + final int NUMBER_OF_USERS = 124; + + for (int i = 0; i < NUMBER_OF_USERS; i++) { User user = new User(); user.setAuthority(Authority.TENANT_ADMIN); user.setTenantId(tenantId); @@ -532,7 +534,7 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { testManyUser.setTenantId(tenantId); testNotifyManyEntityManyTimeMsgToEdgeServiceEntityEqAny(testManyUser, testManyUser, SYSTEM_TENANT, customerNUULId, null, SYS_ADMIN_EMAIL, - ActionType.DELETED, ActionType.DELETED, cntEntity, 0, cntEntity, new String()); + ActionType.DELETED, ActionType.DELETED, cntEntity, NUMBER_OF_USERS, cntEntity, new String()); pageLink = new PageLink(4, 0, email1); pageData = doGetTypedWithPageLink("/api/tenant/" + tenantId.getId().toString() + "/users?", diff --git a/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java new file mode 100644 index 0000000000..02406da08a --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/AbstractEdgeTest.java @@ -0,0 +1,587 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.MessageLite; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; +import org.thingsboard.server.common.data.device.data.DeviceData; +import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.AlarmCondition; +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; +import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; +import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; +import org.thingsboard.server.common.data.device.profile.AlarmRule; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.EntityKeyValueType; +import org.thingsboard.server.common.data.query.FilterPredicateValue; +import org.thingsboard.server.common.data.query.NumericFilterPredicate; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.dao.edge.EdgeEventService; +import org.thingsboard.server.edge.imitator.EdgeImitator; +import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; +import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; +import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UplinkMsg; +import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.TreeMap; +import java.util.UUID; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; + +@TestPropertySource(properties = { + "edges.enabled=true", +}) +abstract public class AbstractEdgeTest extends AbstractControllerTest { + + private static final String THERMOSTAT_DEVICE_PROFILE_NAME = "Thermostat"; + + protected Tenant savedTenant; + protected TenantId tenantId; + protected User tenantAdmin; + + protected DeviceProfile thermostatDeviceProfile; + + protected EdgeImitator edgeImitator; + protected Edge edge; + + @Autowired + protected EdgeEventService edgeEventService; + + @Autowired + protected DataDecodingEncodingService dataDecodingEncodingService; + + @Autowired + protected TbClusterService clusterService; + + @Before + public void beforeTest() throws Exception { + loginSysAdmin(); + + Tenant tenant = new Tenant(); + tenant.setTitle("My tenant"); + savedTenant = doPost("/api/tenant", tenant, Tenant.class); + tenantId = savedTenant.getId(); + Assert.assertNotNull(savedTenant); + + tenantAdmin = new User(); + tenantAdmin.setAuthority(Authority.TENANT_ADMIN); + tenantAdmin.setTenantId(savedTenant.getId()); + tenantAdmin.setEmail("tenant2@thingsboard.org"); + tenantAdmin.setFirstName("Joe"); + tenantAdmin.setLastName("Downs"); + + tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); + // sleep 0.5 second to avoid CREDENTIALS updated message for the user + // user credentials is going to be stored and updated event pushed to edge notification service + // while service will be processing this event edge could be already added and additional message will be pushed + Thread.sleep(500); + + installation(); + + edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); + edgeImitator.expectMessageAmount(21); + edgeImitator.connect(); + + requestEdgeRuleChainMetadata(); + + verifyEdgeConnectionAndInitialData(); + } + + private void requestEdgeRuleChainMetadata() throws Exception { + RuleChainId rootRuleChainId = getEdgeRootRuleChainId(); + RuleChainMetadataRequestMsg.Builder builder = RuleChainMetadataRequestMsg.newBuilder() + .setRuleChainIdMSB(rootRuleChainId.getId().getMostSignificantBits()) + .setRuleChainIdLSB(rootRuleChainId.getId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(builder); + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder() + .addRuleChainMetadataRequestMsg(builder.build()); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + } + + private RuleChainId getEdgeRootRuleChainId() throws Exception { + List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", + new TypeReference>() {}, new PageLink(100)).getData(); + for (RuleChain edgeRuleChain : edgeRuleChains) { + if (edgeRuleChain.isRoot()) { + return edgeRuleChain.getId(); + } + } + throw new RuntimeException("Root rule chain not found"); + } + + @After + public void afterTest() throws Exception { + loginSysAdmin(); + + doDelete("/api/tenant/" + savedTenant.getUuidId()) + .andExpect(status().isOk()); + + try { + edgeImitator.disconnect(); + } catch (Exception ignored) {} + } + + private void installation() { + edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); + + thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, + createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false)); + + extendDeviceProfileData(thermostatDeviceProfile); + thermostatDeviceProfile = doPost("/api/deviceProfile", thermostatDeviceProfile, DeviceProfile.class); + + Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME); + doPost("/api/edge/" + edge.getUuidId() + + "/device/" + savedDevice.getUuidId(), Device.class); + + Asset savedAsset = saveAsset("Edge Asset 1"); + doPost("/api/edge/" + edge.getUuidId() + + "/asset/" + savedAsset.getUuidId(), Asset.class); + } + + protected void extendDeviceProfileData(DeviceProfile deviceProfile) { + DeviceProfileData profileData = deviceProfile.getProfileData(); + List alarms = new ArrayList<>(); + DeviceProfileAlarm deviceProfileAlarm = new DeviceProfileAlarm(); + deviceProfileAlarm.setAlarmType("High Temperature"); + AlarmRule alarmRule = new AlarmRule(); + alarmRule.setAlarmDetails("Alarm Details"); + AlarmCondition alarmCondition = new AlarmCondition(); + alarmCondition.setSpec(new SimpleAlarmConditionSpec()); + List condition = new ArrayList<>(); + AlarmConditionFilter alarmConditionFilter = new AlarmConditionFilter(); + alarmConditionFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperature")); + NumericFilterPredicate predicate = new NumericFilterPredicate(); + predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); + predicate.setValue(new FilterPredicateValue<>(55.0)); + alarmConditionFilter.setPredicate(predicate); + alarmConditionFilter.setValueType(EntityKeyValueType.NUMERIC); + condition.add(alarmConditionFilter); + alarmCondition.setCondition(condition); + alarmRule.setCondition(alarmCondition); + deviceProfileAlarm.setClearRule(alarmRule); + TreeMap createRules = new TreeMap<>(); + createRules.put(AlarmSeverity.CRITICAL, alarmRule); + deviceProfileAlarm.setCreateRules(createRules); + alarms.add(deviceProfileAlarm); + profileData.setAlarms(alarms); + profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123")); + } + + private void verifyEdgeConnectionAndInitialData() throws Exception { + Assert.assertTrue(edgeImitator.waitForMessages()); + + validateEdgeConfiguration(); + + // 5 messages + // - 2 from device profile fetcher (default and thermostat) + // - 1 from device fetcher + // - 1 from device profile controller (thermostat) + // - 1 from device controller (thermostat) + validateDeviceProfiles(); + + // 2 messages - 1 from device fetcher and 1 from device controller + validateDevices(); + + // 2 messages - 1 from asset fetcher and 1 from asset controller + validateAssets(); + + // 2 messages - 1 from rule chain fetcher and 1 from rule chain controller + UUID ruleChainUUID = validateRuleChains(); + + // 1 from request message + validateRuleChainMetadataUpdates(ruleChainUUID); + + // 4 messages - 4 messages from fetcher - 2 from system level ('mail', 'mailTemplates') and 2 from admin level ('mail', 'mailTemplates') + validateAdminSettings(); + + // 3 messages + // - 1 message from asset profile fetcher + // - 1 message from asset fetcher + // - 1 message from asset controller + validateAssetProfiles(); + + // 1 message from queue fetcher + validateQueues(); + + // 1 message from user fetcher + validateUsers(); + } + + private void validateEdgeConfiguration() throws Exception { + EdgeConfiguration configuration = edgeImitator.getConfiguration(); + Assert.assertNotNull(configuration); + testAutoGeneratedCodeByProtobuf(configuration); + } + + private void validateDeviceProfiles() throws Exception { + List deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class); + // default msg + // thermostat msg from fetcher + // thermostat msg from device fetcher + // thermostat msg from controller + // thermostat msg from creation of device + Assert.assertEquals(5, deviceProfileUpdateMsgList.size()); + Optional thermostatProfileUpdateMsgOpt = + deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); + Assert.assertTrue(thermostatProfileUpdateMsgOpt.isPresent()); + DeviceProfileUpdateMsg thermostatProfileUpdateMsg = thermostatProfileUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, thermostatProfileUpdateMsg.getMsgType()); + UUID deviceProfileUUID = new UUID(thermostatProfileUpdateMsg.getIdMSB(), thermostatProfileUpdateMsg.getIdLSB()); + DeviceProfile deviceProfile = doGet("/api/deviceProfile/" + deviceProfileUUID, DeviceProfile.class); + Assert.assertNotNull(deviceProfile); + Assert.assertNotNull(deviceProfile.getProfileData()); + Assert.assertNotNull(deviceProfile.getProfileData().getAlarms()); + Assert.assertNotNull(deviceProfile.getProfileData().getAlarms().get(0).getClearRule()); + + testAutoGeneratedCodeByProtobuf(thermostatProfileUpdateMsg); + } + + private void validateDevices() throws Exception { + List deviceUpdateMsgs = edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class); + Assert.assertEquals(2, deviceUpdateMsgs.size()); + validateDevice(deviceUpdateMsgs.get(0)); + validateDevice(deviceUpdateMsgs.get(1)); + } + + private void validateDevice(DeviceUpdateMsg deviceUpdateMsg) throws Exception { + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()); + Device device = doGet("/api/device/" + deviceUUID, Device.class); + Assert.assertNotNull(device); + List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", + new TypeReference>() {}, new PageLink(100)).getData(); + Assert.assertTrue(edgeDevices.contains(device)); + + testAutoGeneratedCodeByProtobuf(deviceUpdateMsg); + } + + private void validateAssets() throws Exception { + List assetUpdateMsgs = edgeImitator.findAllMessagesByType(AssetUpdateMsg.class); + Assert.assertEquals(2, assetUpdateMsgs.size()); + validateAsset(assetUpdateMsgs.get(0)); + validateAsset(assetUpdateMsgs.get(1)); + } + + private void validateAsset(AssetUpdateMsg assetUpdateMsg) throws Exception { + Assert.assertNotNull(assetUpdateMsg); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB()); + Asset asset = doGet("/api/asset/" + assetUUID, Asset.class); + Assert.assertNotNull(asset); + List edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?", + new TypeReference>() {}, new PageLink(100)).getData(); + Assert.assertTrue(edgeAssets.contains(asset)); + + testAutoGeneratedCodeByProtobuf(assetUpdateMsg); + } + + private UUID validateRuleChains() throws Exception { + List ruleChainUpdateMsgs = edgeImitator.findAllMessagesByType(RuleChainUpdateMsg.class); + Assert.assertEquals(2, ruleChainUpdateMsgs.size()); + RuleChainUpdateMsg ruleChainCreateMsg = ruleChainUpdateMsgs.get(0); + RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgs.get(1); + validateRuleChain(ruleChainCreateMsg, UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + validateRuleChain(ruleChainUpdateMsg, UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE); + return new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB()); + } + + private void validateRuleChain(RuleChainUpdateMsg ruleChainUpdateMsg, UpdateMsgType expectedMsgType) throws Exception { + Assert.assertEquals(expectedMsgType, ruleChainUpdateMsg.getMsgType()); + UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB()); + RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID, RuleChain.class); + Assert.assertNotNull(ruleChain); + List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", + new TypeReference>() {}, new PageLink(100)).getData(); + Assert.assertTrue(edgeRuleChains.contains(ruleChain)); + testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg); + } + + private void validateRuleChainMetadataUpdates(UUID expectedRuleChainUUID) { + Optional ruleChainMetadataUpdateOpt = edgeImitator.findMessageByType(RuleChainMetadataUpdateMsg.class); + Assert.assertTrue(ruleChainMetadataUpdateOpt.isPresent()); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ruleChainMetadataUpdateOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetadataUpdateMsg.getMsgType()); + UUID ruleChainUUID = new UUID(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB()); + Assert.assertEquals(expectedRuleChainUUID, ruleChainUUID); + } + + private void validateAdminSettings() throws JsonProcessingException { + List adminSettingsUpdateMsgs = edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class); + Assert.assertEquals(4, adminSettingsUpdateMsgs.size()); + + for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) { + if (adminSettingsUpdateMsg.getKey().equals("mail")) { + validateMailAdminSettings(adminSettingsUpdateMsg); + } + if (adminSettingsUpdateMsg.getKey().equals("mailTemplates")) { + validateMailTemplatesAdminSettings(adminSettingsUpdateMsg); + } + } + } + + private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException { + JsonNode jsonNode = mapper.readTree(adminSettingsUpdateMsg.getJsonValue()); + Assert.assertNotNull(jsonNode.get("mailFrom")); + Assert.assertNotNull(jsonNode.get("smtpProtocol")); + Assert.assertNotNull(jsonNode.get("smtpHost")); + Assert.assertNotNull(jsonNode.get("smtpPort")); + Assert.assertNotNull(jsonNode.get("timeout")); + } + + private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException { + JsonNode jsonNode = mapper.readTree(adminSettingsUpdateMsg.getJsonValue()); + Assert.assertNotNull(jsonNode.get("accountActivated")); + Assert.assertNotNull(jsonNode.get("accountLockout")); + Assert.assertNotNull(jsonNode.get("activation")); + Assert.assertNotNull(jsonNode.get("passwordWasReset")); + Assert.assertNotNull(jsonNode.get("resetPassword")); + Assert.assertNotNull(jsonNode.get("test")); + } + + private void validateAssetProfiles() throws Exception { + List assetProfileUpdateMsgs = edgeImitator.findAllMessagesByType(AssetProfileUpdateMsg.class); + Assert.assertEquals(3, assetProfileUpdateMsgs.size()); + AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileUpdateMsgs.get(0); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); + UUID assetProfileUUID = new UUID(assetProfileUpdateMsg.getIdMSB(), assetProfileUpdateMsg.getIdLSB()); + AssetProfile assetProfile = doGet("/api/assetProfile/" + assetProfileUUID, AssetProfile.class); + Assert.assertNotNull(assetProfile); + Assert.assertEquals("default", assetProfile.getName()); + Assert.assertTrue(assetProfile.isDefault()); + testAutoGeneratedCodeByProtobuf(assetProfileUpdateMsg); + } + + private void validateQueues() throws Exception { + Optional queueUpdateMsgOpt = edgeImitator.findMessageByType(QueueUpdateMsg.class); + Assert.assertTrue(queueUpdateMsgOpt.isPresent()); + QueueUpdateMsg queueUpdateMsg = queueUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); + UUID queueUUID = new UUID(queueUpdateMsg.getIdMSB(), queueUpdateMsg.getIdLSB()); + Queue queue = doGet("/api/queues/" + queueUUID, Queue.class); + Assert.assertNotNull(queue); + Assert.assertEquals("Main", queueUpdateMsg.getName()); + Assert.assertEquals("tb_rule_engine.main", queueUpdateMsg.getTopic()); + Assert.assertEquals(10, queueUpdateMsg.getPartitions()); + Assert.assertEquals(25, queueUpdateMsg.getPollInterval()); + testAutoGeneratedCodeByProtobuf(queueUpdateMsg); + } + + private void validateUsers() throws Exception { + Optional userUpdateMsgOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); + Assert.assertTrue(userUpdateMsgOpt.isPresent()); + UserUpdateMsg userUpdateMsg = userUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); + UUID userUUID = new UUID(userUpdateMsg.getIdMSB(), userUpdateMsg.getIdLSB()); + User user = doGet("/api/user/" + userUUID, User.class); + Assert.assertNotNull(user); + Assert.assertEquals("tenant2@thingsboard.org", userUpdateMsg.getEmail()); + testAutoGeneratedCodeByProtobuf(userUpdateMsg); + } + + protected Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception { + // create ota package + edgeImitator.expectMessageAmount(1); + OtaPackageInfo firmwareOtaPackageInfo = saveOtaPackageInfo(thermostatDeviceProfile.getId()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + // create device and assign to edge + Device savedDevice = saveDevice(StringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName()); + edgeImitator.expectMessageAmount(2); // device and device profile messages + doPost("/api/edge/" + edge.getUuidId() + + "/device/" + savedDevice.getUuidId(), Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + Optional deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class); + Assert.assertTrue(deviceUpdateMsgOpt.isPresent()); + DeviceUpdateMsg deviceUpdateMsg = deviceUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB()); + + Optional deviceProfileUpdateMsgOpt = edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class); + Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent()); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + + // update device + edgeImitator.expectMessageAmount(1); + savedDevice.setFirmwareId(firmwareOtaPackageInfo.getId()); + + DeviceData deviceData = new DeviceData(); + deviceData.setConfiguration(new DefaultDeviceConfiguration()); + MqttDeviceTransportConfiguration transportConfiguration = new MqttDeviceTransportConfiguration(); + transportConfiguration.getProperties().put("topic", "tb_rule_engine.thermostat"); + deviceData.setTransportConfiguration(transportConfiguration); + savedDevice.setDeviceData(deviceData); + + savedDevice = doPost("/api/device", savedDevice, Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB()); + Assert.assertEquals(savedDevice.getName(), deviceUpdateMsg.getName()); + Assert.assertEquals(savedDevice.getType(), deviceUpdateMsg.getType()); + Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getFirmwareIdMSB()); + Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getFirmwareIdLSB()); + Optional deviceDataOpt = + dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); + Assert.assertTrue(deviceDataOpt.isPresent()); + deviceData = deviceDataOpt.get(); + Assert.assertTrue(deviceData.getTransportConfiguration() instanceof MqttDeviceTransportConfiguration); + MqttDeviceTransportConfiguration mqttDeviceTransportConfiguration = + (MqttDeviceTransportConfiguration) deviceData.getTransportConfiguration(); + Assert.assertEquals("tb_rule_engine.thermostat", mqttDeviceTransportConfiguration.getProperties().get("topic")); + return savedDevice; + } + + protected Device findDeviceByName(String deviceName) throws Exception { + List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", + new TypeReference>() { + }, new PageLink(100)).getData(); + Optional foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny(); + Assert.assertTrue(foundDevice.isPresent()); + Device device = foundDevice.get(); + Assert.assertEquals(deviceName, device.getName()); + return device; + } + + protected Asset findAssetByName(String assetName) throws Exception { + List edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?", + new TypeReference>() { + }, new PageLink(100)).getData(); + + Assert.assertEquals(1, edgeAssets.size()); + Asset asset = edgeAssets.get(0); + Assert.assertEquals(assetName, asset.getName()); + return asset; + } + + protected Device saveDevice(String deviceName, String type) { + Device device = new Device(); + device.setName(deviceName); + device.setType(type); + return doPost("/api/device", device, Device.class); + } + + protected Asset saveAsset(String assetName) { + Asset asset = new Asset(); + asset.setName(assetName); + return doPost("/api/asset", asset, Asset.class); + } + + protected OtaPackageInfo saveOtaPackageInfo(DeviceProfileId deviceProfileId) { + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); + firmwareInfo.setDeviceProfileId(deviceProfileId); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("Firmware Edge " + StringUtils.randomAlphanumeric(3)); + firmwareInfo.setVersion("v1.0"); + firmwareInfo.setTag("My firmware #1 v1.0"); + firmwareInfo.setUsesUrl(true); + firmwareInfo.setUrl("http://localhost:8080/v1/package"); + firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + firmwareInfo.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); + return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + } + + protected EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, + UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) { + EdgeEvent edgeEvent = new EdgeEvent(); + edgeEvent.setEdgeId(edgeId); + edgeEvent.setTenantId(tenantId); + edgeEvent.setAction(edgeEventAction); + edgeEvent.setEntityId(entityId); + edgeEvent.setType(edgeEventType); + edgeEvent.setBody(entityBody); + return edgeEvent; + } + + protected void testAutoGeneratedCodeByProtobuf(MessageLite.Builder builder) throws InvalidProtocolBufferException { + MessageLite source = builder.build(); + + testAutoGeneratedCodeByProtobuf(source); + + MessageLite target = source.getParserForType().parseFrom(source.toByteArray()); + builder.clear().mergeFrom(target); + } + + protected void testAutoGeneratedCodeByProtobuf(MessageLite source) throws InvalidProtocolBufferException { + MessageLite target = source.getParserForType().parseFrom(source.toByteArray()); + Assert.assertEquals(source, target); + Assert.assertEquals(source.hashCode(), target.hashCode()); + } + + + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java new file mode 100644 index 0000000000..d952e478a0 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseAlarmEdgeTest.java @@ -0,0 +1,136 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UplinkMsg; + +import java.util.List; +import java.util.Optional; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseAlarmEdgeTest extends AbstractEdgeTest { + + @Test + public void testSendAlarmToCloud() throws Exception { + Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder(); + alarmUpdateMgBuilder.setName("alarm from edge"); + alarmUpdateMgBuilder.setStatus(AlarmStatus.ACTIVE_UNACK.name()); + alarmUpdateMgBuilder.setSeverity(AlarmSeverity.CRITICAL.name()); + alarmUpdateMgBuilder.setOriginatorName(device.getName()); + alarmUpdateMgBuilder.setOriginatorType(EntityType.DEVICE.name()); + testAutoGeneratedCodeByProtobuf(alarmUpdateMgBuilder); + uplinkMsgBuilder.addAlarmUpdateMsg(alarmUpdateMgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + + List alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?", + new TypeReference>() {}, + new PageLink(100), device.getId().getEntityType().name(), device.getUuidId()) + .getData(); + Optional foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny(); + Assert.assertTrue(foundAlarm.isPresent()); + AlarmInfo alarmInfo = foundAlarm.get(); + Assert.assertEquals(device.getId(), alarmInfo.getOriginator()); + Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, alarmInfo.getStatus()); + Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity()); + } + + @Test + public void testAlarms() throws Exception { + // create alarm + Device device = findDeviceByName("Edge Device 1"); + Alarm alarm = new Alarm(); + alarm.setOriginator(device.getId()); + alarm.setStatus(AlarmStatus.ACTIVE_UNACK); + alarm.setType("alarm"); + alarm.setSeverity(AlarmSeverity.CRITICAL); + edgeImitator.expectMessageAmount(1); + Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); + AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); + Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType()); + Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName()); + Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName()); + Assert.assertEquals(savedAlarm.getStatus().name(), alarmUpdateMsg.getStatus()); + Assert.assertEquals(savedAlarm.getSeverity().name(), alarmUpdateMsg.getSeverity()); + + // ack alarm + edgeImitator.expectMessageAmount(1); + doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack"); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); + alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ALARM_ACK_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); + Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType()); + Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName()); + Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName()); + Assert.assertEquals(AlarmStatus.ACTIVE_ACK.name(), alarmUpdateMsg.getStatus()); + + // clear alarm + edgeImitator.expectMessageAmount(1); + doPost("/api/alarm/" + savedAlarm.getUuidId() + "/clear"); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); + alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); + Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType()); + Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName()); + Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName()); + Assert.assertEquals(AlarmStatus.CLEARED_ACK.name(), alarmUpdateMsg.getStatus()); + + // delete alarm + edgeImitator.expectMessageAmount(1); + doDelete("/api/alarm/" + savedAlarm.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); + alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); + Assert.assertEquals(savedAlarm.getType(), alarmUpdateMsg.getType()); + Assert.assertEquals(savedAlarm.getName(), alarmUpdateMsg.getName()); + Assert.assertEquals(device.getName(), alarmUpdateMsg.getOriginatorName()); + Assert.assertEquals(AlarmStatus.CLEARED_ACK.name(), alarmUpdateMsg.getStatus()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseAssetEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseAssetEdgeTest.java new file mode 100644 index 0000000000..9a68637eba --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseAssetEdgeTest.java @@ -0,0 +1,157 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +import java.util.Optional; +import java.util.UUID; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseAssetEdgeTest extends AbstractEdgeTest { + + @Test + public void testAssets() throws Exception { + // create asset and assign to edge + edgeImitator.expectMessageAmount(2); + Asset savedAsset = saveAsset("Edge Asset 2"); + doPost("/api/edge/" + edge.getUuidId() + + "/asset/" + savedAsset.getUuidId(), Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + Optional assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class); + Assert.assertTrue(assetUpdateMsgOpt.isPresent()); + AssetUpdateMsg assetUpdateMsg = assetUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals(savedAsset.getUuidId().getMostSignificantBits(), assetUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAsset.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getIdLSB()); + Assert.assertEquals(savedAsset.getName(), assetUpdateMsg.getName()); + Assert.assertEquals(savedAsset.getType(), assetUpdateMsg.getType()); + Optional assetProfileUpdateMsgOpt = edgeImitator.findMessageByType(AssetProfileUpdateMsg.class); + Assert.assertTrue(assetProfileUpdateMsgOpt.isPresent()); + AssetProfileUpdateMsg assetProfileUpdateMsg = assetProfileUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); + Assert.assertEquals(savedAsset.getAssetProfileId().getId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAsset.getAssetProfileId().getId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB()); + + // update asset + edgeImitator.expectMessageAmount(1); + savedAsset.setName("Edge Asset 2 Updated"); + savedAsset = doPost("/api/asset", savedAsset, Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); + assetUpdateMsg = (AssetUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals(savedAsset.getName(), assetUpdateMsg.getName()); + + // unassign asset from edge + edgeImitator.expectMessageAmount(1); + doDelete("/api/edge/" + edge.getUuidId() + + "/asset/" + savedAsset.getUuidId(), Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); + assetUpdateMsg = (AssetUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals(savedAsset.getUuidId().getMostSignificantBits(), assetUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAsset.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getIdLSB()); + + // delete asset - no messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/asset/" + savedAsset.getUuidId()) + .andExpect(status().isOk()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + + // create asset #2 and assign to edge + edgeImitator.expectMessageAmount(2); + savedAsset = saveAsset("Edge Asset 3"); + doPost("/api/edge/" + edge.getUuidId() + + "/asset/" + savedAsset.getUuidId(), Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class); + Assert.assertTrue(assetUpdateMsgOpt.isPresent()); + assetUpdateMsg = assetUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals(savedAsset.getUuidId().getMostSignificantBits(), assetUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAsset.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getIdLSB()); + Assert.assertEquals(savedAsset.getName(), assetUpdateMsg.getName()); + Assert.assertEquals(savedAsset.getType(), assetUpdateMsg.getType()); + assetProfileUpdateMsgOpt = edgeImitator.findMessageByType(AssetProfileUpdateMsg.class); + Assert.assertTrue(assetProfileUpdateMsgOpt.isPresent()); + assetProfileUpdateMsg = assetProfileUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); + Assert.assertEquals(savedAsset.getAssetProfileId().getId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAsset.getAssetProfileId().getId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB()); + + // assign asset #2 to customer + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + edgeImitator.expectMessageAmount(2); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + edgeImitator.expectMessageAmount(1); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/asset/" + savedAsset.getUuidId(), Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); + assetUpdateMsg = (AssetUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), assetUpdateMsg.getCustomerIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getCustomerIdLSB()); + + // unassign asset #2 from customer + edgeImitator.expectMessageAmount(1); + doDelete("/api/customer/asset/" + savedAsset.getUuidId(), Asset.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); + assetUpdateMsg = (AssetUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals( + new CustomerId(EntityId.NULL_UUID), + new CustomerId(new UUID(assetUpdateMsg.getCustomerIdMSB(), assetUpdateMsg.getCustomerIdLSB()))); + + // delete asset #2 - messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/asset/" + savedAsset.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); + assetUpdateMsg = (AssetUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); + Assert.assertEquals(savedAsset.getUuidId().getMostSignificantBits(), assetUpdateMsg.getIdMSB()); + Assert.assertEquals(savedAsset.getUuidId().getLeastSignificantBits(), assetUpdateMsg.getIdLSB()); + } + + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseAssetProfileEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseAssetProfileEdgeTest.java new file mode 100644 index 0000000000..567a196cec --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseAssetProfileEdgeTest.java @@ -0,0 +1,70 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ByteString; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.gen.edge.v1.AssetProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +import java.nio.charset.StandardCharsets; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseAssetProfileEdgeTest extends AbstractEdgeTest { + + @Test + public void testAssetProfiles() throws Exception { + // create asset profile + AssetProfile assetProfile = this.createAssetProfile("Building"); + edgeImitator.expectMessageAmount(1); + assetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetProfileUpdateMsg); + AssetProfileUpdateMsg assetProfileUpdateMsg = (AssetProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); + Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB()); + Assert.assertEquals("Building", assetProfileUpdateMsg.getName()); + + // update asset profile + assetProfile.setImage("IMAGE"); + edgeImitator.expectMessageAmount(1); + assetProfile = doPost("/api/assetProfile", assetProfile, AssetProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetProfileUpdateMsg); + assetProfileUpdateMsg = (AssetProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); + Assert.assertEquals(ByteString.copyFrom("IMAGE".getBytes(StandardCharsets.UTF_8)), assetProfileUpdateMsg.getImage()); + + // delete profile + edgeImitator.expectMessageAmount(1); + doDelete("/api/assetProfile/" + assetProfile.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof AssetProfileUpdateMsg); + assetProfileUpdateMsg = (AssetProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetProfileUpdateMsg.getMsgType()); + Assert.assertEquals(assetProfile.getUuidId().getMostSignificantBits(), assetProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(assetProfile.getUuidId().getLeastSignificantBits(), assetProfileUpdateMsg.getIdLSB()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseCustomerEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseCustomerEdgeTest.java new file mode 100644 index 0000000000..dd2880d2ba --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseCustomerEdgeTest.java @@ -0,0 +1,87 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; +import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +import java.util.Optional; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseCustomerEdgeTest extends AbstractEdgeTest { + + @Test + public void testCreateUpdateDeleteCustomer() throws Exception { + // create customer + edgeImitator.expectMessageAmount(1); + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + + // assign edge to customer + edgeImitator.expectMessageAmount(2); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + Optional edgeConfigurationOpt = edgeImitator.findMessageByType(EdgeConfiguration.class); + Assert.assertTrue(edgeConfigurationOpt.isPresent()); + EdgeConfiguration edgeConfiguration = edgeConfigurationOpt.get(); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), edgeConfiguration.getCustomerIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), edgeConfiguration.getCustomerIdLSB()); + Optional customerUpdateOpt = edgeImitator.findMessageByType(CustomerUpdateMsg.class); + Assert.assertTrue(customerUpdateOpt.isPresent()); + CustomerUpdateMsg customerUpdateMsg = customerUpdateOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), customerUpdateMsg.getIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), customerUpdateMsg.getIdLSB()); + Assert.assertEquals(savedCustomer.getTitle(), customerUpdateMsg.getTitle()); + testAutoGeneratedCodeByProtobuf(customerUpdateMsg); + + // update customer + edgeImitator.expectMessageAmount(1); + savedCustomer.setTitle("Edge Customer Updated"); + savedCustomer = doPost("/api/customer", savedCustomer, Customer.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); + customerUpdateMsg = (CustomerUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), customerUpdateMsg.getIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), customerUpdateMsg.getIdLSB()); + Assert.assertEquals(savedCustomer.getTitle(), customerUpdateMsg.getTitle()); + + // delete customer + edgeImitator.expectMessageAmount(1); + doDelete("/api/customer/" + savedCustomer.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); + customerUpdateMsg = (CustomerUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); + Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); + Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseDashboardEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseDashboardEdgeTest.java new file mode 100644 index 0000000000..1e85200419 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseDashboardEdgeTest.java @@ -0,0 +1,150 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.ShortCustomerInfo; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +import java.util.Set; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseDashboardEdgeTest extends AbstractEdgeTest { + + @Test + public void testDashboards() throws Exception { + // create dashboard and assign to edge + edgeImitator.expectMessageAmount(1); + Dashboard dashboard = new Dashboard(); + dashboard.setTitle("Edge Test Dashboard"); + Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + doPost("/api/edge/" + edge.getUuidId() + + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); + DashboardUpdateMsg dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); + Assert.assertEquals(savedDashboard.getUuidId().getMostSignificantBits(), dashboardUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDashboard.getUuidId().getLeastSignificantBits(), dashboardUpdateMsg.getIdLSB()); + Assert.assertEquals(savedDashboard.getTitle(), dashboardUpdateMsg.getTitle()); + testAutoGeneratedCodeByProtobuf(dashboardUpdateMsg); + + // update dashboard + edgeImitator.expectMessageAmount(1); + savedDashboard.setTitle("Updated Edge Test Dashboard"); + savedDashboard = doPost("/api/dashboard", savedDashboard, Dashboard.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); + dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); + Assert.assertEquals(savedDashboard.getTitle(), dashboardUpdateMsg.getTitle()); + + // unassign dashboard from edge + edgeImitator.expectMessageAmount(1); + doDelete("/api/edge/" + edge.getUuidId() + + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); + dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); + Assert.assertEquals(savedDashboard.getUuidId().getMostSignificantBits(), dashboardUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDashboard.getUuidId().getLeastSignificantBits(), dashboardUpdateMsg.getIdLSB()); + + // delete dashboard - no messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/dashboard/" + savedDashboard.getUuidId()) + .andExpect(status().isOk()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + + // create dashboard #2 and assign to edge + edgeImitator.expectMessageAmount(1); + dashboard = new Dashboard(); + dashboard.setTitle("Edge Test Dashboard #2"); + savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); + doPost("/api/edge/" + edge.getUuidId() + + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); + dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); + Assert.assertEquals(savedDashboard.getUuidId().getMostSignificantBits(), dashboardUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDashboard.getUuidId().getLeastSignificantBits(), dashboardUpdateMsg.getIdLSB()); + Assert.assertEquals(savedDashboard.getTitle(), dashboardUpdateMsg.getTitle()); + + // assign dashboard #2 to customer + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + edgeImitator.expectMessageAmount(2); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + edgeImitator.expectMessageAmount(1); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); + dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); + Set assignedCustomers = + JacksonUtil.fromString(dashboardUpdateMsg.getAssignedCustomers(), new TypeReference<>() {}); + Assert.assertNotNull(assignedCustomers); + Assert.assertFalse(assignedCustomers.isEmpty()); + Assert.assertTrue(assignedCustomers.contains(new ShortCustomerInfo(savedCustomer.getId(), customer.getTitle(), customer.isPublic()))); + + // unassign dashboard #2 from customer + edgeImitator.expectMessageAmount(1); + doDelete("/api/customer/" + savedCustomer.getUuidId() + + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); + dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); + assignedCustomers = + JacksonUtil.fromString(dashboardUpdateMsg.getAssignedCustomers(), new TypeReference<>() {}); + Assert.assertNotNull(assignedCustomers); + Assert.assertTrue(assignedCustomers.isEmpty()); + + // delete dashboard #2 - messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/dashboard/" + savedDashboard.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); + dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); + Assert.assertEquals(savedDashboard.getUuidId().getMostSignificantBits(), dashboardUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDashboard.getUuidId().getLeastSignificantBits(), dashboardUpdateMsg.getIdLSB()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java new file mode 100644 index 0000000000..ffe21ae961 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java @@ -0,0 +1,632 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.JsonObject; +import com.google.protobuf.AbstractMessage; +import io.netty.handler.codec.mqtt.MqttQoS; +import org.awaitility.Awaitility; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.transport.adaptor.JsonConverter; +import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; +import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; +import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; +import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; +import org.thingsboard.server.gen.edge.v1.EntityDataProto; +import org.thingsboard.server.gen.edge.v1.RpcResponseMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UplinkMsg; +import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.transport.mqtt.MqttTestCallback; +import org.thingsboard.server.transport.mqtt.MqttTestClient; + +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.Random; +import java.util.UUID; +import java.util.concurrent.TimeUnit; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@TestPropertySource(properties = { + "transport.mqtt.enabled=true" +}) +abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { + + @Test + public void testDevices() throws Exception { + // create device and assign to edge; update device + Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + + // unassign device from edge + edgeImitator.expectMessageAmount(1); + doDelete("/api/edge/" + edge.getUuidId() + + "/device/" + savedDevice.getUuidId(), Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB()); + + // delete device - no messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/device/" + savedDevice.getUuidId()) + .andExpect(status().isOk()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + + // create device #2 and assign to edge + edgeImitator.expectMessageAmount(2); + savedDevice = saveDevice("Edge Device 3", "Default"); + doPost("/api/edge/" + edge.getUuidId() + + "/device/" + savedDevice.getUuidId(), Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + Optional deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class); + Assert.assertTrue(deviceUpdateMsgOpt.isPresent()); + deviceUpdateMsg = deviceUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB()); + Assert.assertEquals(savedDevice.getName(), deviceUpdateMsg.getName()); + Assert.assertEquals(savedDevice.getType(), deviceUpdateMsg.getType()); + + Optional deviceProfileUpdateMsgOpt = edgeImitator.findMessageByType(DeviceProfileUpdateMsg.class); + Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent()); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(savedDevice.getDeviceProfileId().getId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDevice.getDeviceProfileId().getId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + + // assign device #2 to customer + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + edgeImitator.expectMessageAmount(2); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + edgeImitator.expectMessageAmount(1); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/device/" + savedDevice.getUuidId(), Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getCustomerIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getCustomerIdLSB()); + + // unassign device #2 from customer + edgeImitator.expectMessageAmount(1); + doDelete("/api/customer/device/" + savedDevice.getUuidId(), Device.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals( + new CustomerId(EntityId.NULL_UUID), + new CustomerId(new UUID(deviceUpdateMsg.getCustomerIdMSB(), deviceUpdateMsg.getCustomerIdLSB()))); + + // delete device #2 - messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/device/" + savedDevice.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); + Assert.assertEquals(savedDevice.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getIdMSB()); + Assert.assertEquals(savedDevice.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getIdLSB()); + + } + + @Test + public void testUpdateDeviceCredentials() throws Exception { + // create device and assign to edge; update device + Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + + // update device credentials - ACCESS_TOKEN + edgeImitator.expectMessageAmount(1); + DeviceCredentials deviceCredentials = + doGet("/api/device/" + savedDevice.getId().getId() + "/credentials", DeviceCredentials.class); + Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId()); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + deviceCredentials.setCredentialsId("access_token"); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); + DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = (DeviceCredentialsUpdateMsg) latestMessage; + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), deviceCredentialsUpdateMsg.getCredentialsType()); + Assert.assertEquals(deviceCredentials.getCredentialsId(), deviceCredentialsUpdateMsg.getCredentialsId()); + Assert.assertFalse(deviceCredentialsUpdateMsg.hasCredentialsValue()); + + // update device credentials - X509_CERTIFICATE + edgeImitator.expectMessageAmount(1); + deviceCredentials.setCredentialsType(DeviceCredentialsType.X509_CERTIFICATE); + deviceCredentials.setCredentialsId(null); + deviceCredentials.setCredentialsValue("-----BEGIN RSA PRIVATE KEY-----"); + doPost("/api/device/credentials", deviceCredentials) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); + deviceCredentialsUpdateMsg = (DeviceCredentialsUpdateMsg) latestMessage; + Assert.assertEquals(deviceCredentials.getCredentialsType().name(), deviceCredentialsUpdateMsg.getCredentialsType()); + Assert.assertFalse(deviceCredentialsUpdateMsg.getCredentialsId().isEmpty()); + Assert.assertTrue(deviceCredentialsUpdateMsg.hasCredentialsValue()); + Assert.assertEquals(deviceCredentials.getCredentialsValue(), deviceCredentialsUpdateMsg.getCredentialsValue()); + } + + @Test + public void testDeviceReachedMaximumAllowedOnCloud() throws Exception { + // update tenant profile configuration + loginSysAdmin(); + TenantProfile tenantProfile = doGet("/api/tenantProfile/" + savedTenant.getTenantProfileId().getId(), TenantProfile.class); + DefaultTenantProfileConfiguration profileConfiguration = + (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); + profileConfiguration.setMaxDevices(1); + tenantProfile.getProfileData().setConfiguration(profileConfiguration); + doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class); + + loginTenantAdmin(); + + UUID uuid = Uuids.timeBased(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); + deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + deviceUpdateMsgBuilder.setName("Edge Device"); + deviceUpdateMsgBuilder.setType("default"); + deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); + + edgeImitator.expectResponsesAmount(1); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + + UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg(); + Assert.assertTrue(latestResponseMsg.getSuccess()); + } + + @Test + public void testSendDeviceRpcResponseToCloud() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceRpcCallMsg.Builder deviceRpcCallResponseBuilder = DeviceRpcCallMsg.newBuilder(); + deviceRpcCallResponseBuilder.setDeviceIdMSB(device.getUuidId().getMostSignificantBits()); + deviceRpcCallResponseBuilder.setDeviceIdLSB(device.getUuidId().getLeastSignificantBits()); + deviceRpcCallResponseBuilder.setOneway(true); + deviceRpcCallResponseBuilder.setRequestId(0); + deviceRpcCallResponseBuilder.setExpirationTime(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10)); + RpcResponseMsg.Builder responseBuilder = + RpcResponseMsg.newBuilder().setResponse("{}"); + testAutoGeneratedCodeByProtobuf(responseBuilder); + + deviceRpcCallResponseBuilder.setResponseMsg(responseBuilder.build()); + testAutoGeneratedCodeByProtobuf(deviceRpcCallResponseBuilder); + + uplinkMsgBuilder.addDeviceRpcCallMsg(deviceRpcCallResponseBuilder.build()); + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + } + + @Test + public void testSendDeviceCredentialsUpdateToCloud() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceCredentialsUpdateMsg.Builder deviceCredentialsUpdateMsgBuilder = DeviceCredentialsUpdateMsg.newBuilder(); + deviceCredentialsUpdateMsgBuilder.setDeviceIdMSB(device.getUuidId().getMostSignificantBits()); + deviceCredentialsUpdateMsgBuilder.setDeviceIdLSB(device.getUuidId().getLeastSignificantBits()); + deviceCredentialsUpdateMsgBuilder.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN.name()); + deviceCredentialsUpdateMsgBuilder.setCredentialsId("NEW_TOKEN"); + testAutoGeneratedCodeByProtobuf(deviceCredentialsUpdateMsgBuilder); + uplinkMsgBuilder.addDeviceCredentialsUpdateMsg(deviceCredentialsUpdateMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + } + + @Test + public void testSendDeviceCredentialsRequestToCloud() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + + DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getUuidId() + "/credentials", DeviceCredentials.class); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceCredentialsRequestMsg.Builder deviceCredentialsRequestMsgBuilder = DeviceCredentialsRequestMsg.newBuilder(); + deviceCredentialsRequestMsgBuilder.setDeviceIdMSB(device.getUuidId().getMostSignificantBits()); + deviceCredentialsRequestMsgBuilder.setDeviceIdLSB(device.getUuidId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(deviceCredentialsRequestMsgBuilder); + uplinkMsgBuilder.addDeviceCredentialsRequestMsg(deviceCredentialsRequestMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); + DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = (DeviceCredentialsUpdateMsg) latestMessage; + Assert.assertEquals(deviceCredentialsUpdateMsg.getDeviceIdMSB(), device.getUuidId().getMostSignificantBits()); + Assert.assertEquals(deviceCredentialsUpdateMsg.getDeviceIdLSB(), device.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(deviceCredentialsUpdateMsg.getCredentialsType(), deviceCredentials.getCredentialsType().name()); + Assert.assertEquals(deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentials.getCredentialsId()); + } + + @Test + public void testSendAttributesRequestToCloud() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}", + "key1", "value1"); + sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}", + "key2", "value2"); + } + + @Test + public void testSendDeleteDeviceOnEdgeToCloud() throws Exception { + Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder(); + deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE); + deviceDeleteMsgBuilder.setIdMSB(device.getId().getId().getMostSignificantBits()); + deviceDeleteMsgBuilder.setIdLSB(device.getId().getId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(deviceDeleteMsgBuilder); + + upLinkMsgBuilder.addDeviceUpdateMsg(deviceDeleteMsgBuilder.build()); + testAutoGeneratedCodeByProtobuf(upLinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + device = doGet("/api/device/" + device.getUuidId(), Device.class); + Assert.assertNotNull(device); + List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", + new TypeReference>() { + }, new PageLink(100)).getData(); + Assert.assertFalse(edgeDevices.contains(device)); + } + + @Test + public void testSendTelemetryToCloud() throws Exception { + Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + + edgeImitator.expectResponsesAmount(2); + + JsonObject data = new JsonObject(); + String timeseriesKey = "key"; + String timeseriesValue = "25"; + data.addProperty(timeseriesKey, timeseriesValue); + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + EntityDataProto.Builder entityDataBuilder = EntityDataProto.newBuilder(); + entityDataBuilder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data, System.currentTimeMillis())); + entityDataBuilder.setEntityType(device.getId().getEntityType().name()); + entityDataBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits()); + entityDataBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(entityDataBuilder); + uplinkMsgBuilder.addEntityData(entityDataBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + JsonObject attributesData = new JsonObject(); + String attributesKey = "test_attr"; + String attributesValue = "test_value"; + attributesData.addProperty(attributesKey, attributesValue); + UplinkMsg.Builder uplinkMsgBuilder2 = UplinkMsg.newBuilder(); + EntityDataProto.Builder entityDataBuilder2 = EntityDataProto.newBuilder(); + entityDataBuilder2.setEntityType(device.getId().getEntityType().name()); + entityDataBuilder2.setEntityIdMSB(device.getId().getId().getMostSignificantBits()); + entityDataBuilder2.setEntityIdLSB(device.getId().getId().getLeastSignificantBits()); + entityDataBuilder2.setAttributesUpdatedMsg(JsonConverter.convertToAttributesProto(attributesData)); + entityDataBuilder2.setPostAttributeScope(DataConstants.SERVER_SCOPE); + + uplinkMsgBuilder2.addEntityData(entityDataBuilder2.build()); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + + Awaitility.await() + .atMost(2, TimeUnit.SECONDS) + .until(() -> loadDeviceTimeseries(device, timeseriesKey).containsKey(timeseriesKey)); + + Map>> timeseries = loadDeviceTimeseries(device, timeseriesKey); + Assert.assertTrue(timeseries.containsKey(timeseriesKey)); + Assert.assertEquals(1, timeseries.get(timeseriesKey).size()); + Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value")); + + String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE; + List> attributes = doGetAsyncTyped(attributeValuesUrl, new TypeReference<>() {}); + + Assert.assertEquals(3, attributes.size()); + + Optional> activeAttributeOpt = getAttributeByKey("active", attributes); + Assert.assertTrue(activeAttributeOpt.isPresent()); + Map activeAttribute = activeAttributeOpt.get(); + Assert.assertEquals("true", activeAttribute.get("value")); + + Optional> customAttributeOpt = getAttributeByKey(attributesKey, attributes); + Assert.assertTrue(customAttributeOpt.isPresent()); + Map customAttribute = customAttributeOpt.get(); + Assert.assertEquals(attributesValue, customAttribute.get("value")); + + doDelete("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/SERVER_SCOPE?keys=" + attributesKey, String.class); + } + + @Test + public void testSendDeviceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception { + String deviceOnCloudName = StringUtils.randomAlphanumeric(15); + Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default"); + + UUID uuid = Uuids.timeBased(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); + deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + deviceUpdateMsgBuilder.setName(deviceOnCloudName); + deviceUpdateMsgBuilder.setType("test"); + deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder); + uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(2); + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getMessageFromTail(2); + Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); + DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Assert.assertNotEquals(deviceOnCloudName, latestDeviceUpdateMsg.getName()); + Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName()); + + UUID newDeviceId = new UUID(latestDeviceUpdateMsg.getIdMSB(), latestDeviceUpdateMsg.getIdLSB()); + + Assert.assertNotEquals(deviceOnCloud.getId().getId(), newDeviceId); + + Device device = doGet("/api/device/" + newDeviceId, Device.class); + Assert.assertNotNull(device); + Assert.assertNotEquals(deviceOnCloudName, device.getName()); + + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg); + DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage; + Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB()); + Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); + + newDeviceId = new UUID(latestDeviceCredentialsRequestMsg.getDeviceIdMSB(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); + + device = doGet("/api/device/" + newDeviceId, Device.class); + Assert.assertNotNull(device); + Assert.assertNotEquals(deviceOnCloudName, device.getName()); + } + + @Test + public void testSendDeviceToCloud() throws Exception { + UUID uuid = Uuids.timeBased(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); + deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); + deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); + deviceUpdateMsgBuilder.setName("Edge Device 2"); + deviceUpdateMsgBuilder.setType("test"); + deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); + uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg); + DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage; + Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB()); + Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); + + UUID newDeviceId = new UUID(latestDeviceCredentialsRequestMsg.getDeviceIdMSB(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); + + Device device = doGet("/api/device/" + newDeviceId, Device.class); + Assert.assertNotNull(device); + Assert.assertEquals("Edge Device 2", device.getName()); + } + + @Test + public void testRpcCall() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + + ObjectNode body = mapper.createObjectNode(); + body.put("requestId", new Random().nextInt()); + body.put("requestUUID", Uuids.timeBased().toString()); + body.put("oneway", false); + body.put("expirationTime", System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10)); + body.put("method", "test_method"); + body.put("params", "{\"param1\":\"value1\"}"); + body.put("persisted", true); + body.put("retries", 2); + + EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.RPC_CALL, + device.getId().getId(), EdgeEventType.DEVICE, body); + edgeImitator.expectMessageAmount(1); + edgeEventService.saveAsync(edgeEvent).get(); + clusterService.onEdgeEventUpdate(tenantId, edge.getId()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceRpcCallMsg); + DeviceRpcCallMsg latestDeviceRpcCallMsg = (DeviceRpcCallMsg) latestMessage; + Assert.assertEquals("test_method", latestDeviceRpcCallMsg.getRequestMsg().getMethod()); + Assert.assertTrue(latestDeviceRpcCallMsg.getPersisted()); + Assert.assertEquals(2, latestDeviceRpcCallMsg.getRetries()); + } + + private void sendAttributesRequestAndVerify(Device device, String scope, String attributesDataStr, String expectedKey, + String expectedValue) throws Exception { + JsonNode attributesData = mapper.readTree(attributesDataStr); + + doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + scope, + attributesData); + + // Wait before device attributes saved to database before requesting them from edge + Awaitility.await() + .atMost(10, TimeUnit.SECONDS) + .until(() -> { + String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/attributes/" + scope; + List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {}); + return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains(expectedKey); + }); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + AttributesRequestMsg.Builder attributesRequestMsgBuilder = AttributesRequestMsg.newBuilder(); + attributesRequestMsgBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits()); + attributesRequestMsgBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits()); + attributesRequestMsgBuilder.setEntityType(EntityType.DEVICE.name()); + attributesRequestMsgBuilder.setScope(scope); + testAutoGeneratedCodeByProtobuf(attributesRequestMsgBuilder); + uplinkMsgBuilder.addAttributesRequestMsg(attributesRequestMsgBuilder.build()); + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityDataProto); + EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; + Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); + Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); + Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); + Assert.assertEquals(scope, latestEntityDataMsg.getPostAttributeScope()); + Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg()); + + TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg(); + + boolean found = false; + for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) { + if (keyValueProto.getKey().equals(expectedKey)) { + Assert.assertEquals(expectedKey, keyValueProto.getKey()); + Assert.assertEquals(expectedValue, keyValueProto.getStringV()); + found = true; + } + } + Assert.assertTrue("Expected key and value must be found", found); + } + + private Optional> getAttributeByKey(String key, List> attributes) { + return attributes.stream().filter(kv -> kv.get("key").equals(key)).findFirst(); + } + + private Map>> loadDeviceTimeseries(Device device, String timeseriesKey) throws Exception { + return doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey, + new TypeReference<>() {}); + } + + @Test + public void sendUpdateSharedAttributeToCloudAndValidateDeviceSubscription() throws Exception { + Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + + DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getUuidId() + "/credentials", DeviceCredentials.class); + + MqttTestClient client = new MqttTestClient(); + client.connectAndWait(deviceCredentials.getCredentialsId()); + MqttTestCallback onUpdateCallback = new MqttTestCallback(); + client.setCallback(onUpdateCallback); + client.subscribeAndWait("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE); + + edgeImitator.expectResponsesAmount(1); + + JsonObject attributesData = new JsonObject(); + String attrKey = "sharedAttrName"; + String attrValue = "sharedAttrValue"; + attributesData.addProperty(attrKey, attrValue); + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + EntityDataProto.Builder entityDataBuilder = EntityDataProto.newBuilder(); + entityDataBuilder.setEntityType(device.getId().getEntityType().name()); + entityDataBuilder.setEntityIdMSB(device.getId().getId().getMostSignificantBits()); + entityDataBuilder.setEntityIdLSB(device.getId().getId().getLeastSignificantBits()); + entityDataBuilder.setAttributesUpdatedMsg(JsonConverter.convertToAttributesProto(attributesData)); + entityDataBuilder.setPostAttributeScope(DataConstants.SHARED_SCOPE); + uplinkMsgBuilder.addEntityData(entityDataBuilder.build()); + + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + + Assert.assertTrue(onUpdateCallback.getSubscribeLatch().await(5, TimeUnit.SECONDS)); + + Assert.assertEquals(JacksonUtil.OBJECT_MAPPER.createObjectNode().put(attrKey, attrValue), + JacksonUtil.fromBytes(onUpdateCallback.getPayloadBytes())); + + client.disconnect(); + } +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceProfileEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceProfileEdgeTest.java new file mode 100644 index 0000000000..d0f0f48477 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceProfileEdgeTest.java @@ -0,0 +1,319 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceTransportType; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.device.data.PowerMode; +import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; +import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; +import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential; +import org.thingsboard.server.common.data.kv.DataType; +import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; +import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; +import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig; +import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.transport.AbstractTransportIntegrationTest; +import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseDeviceProfileEdgeTest extends AbstractEdgeTest { + + @Test + public void testDeviceProfiles() throws Exception { + // create device profile + DeviceProfile deviceProfile = this.createDeviceProfile("ONE_MORE_DEVICE_PROFILE", null); + extendDeviceProfileData(deviceProfile); + edgeImitator.expectMessageAmount(1); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + + // update device profile + OtaPackageInfo firmwareOtaPackageInfo = saveOtaPackageInfo(deviceProfile.getId()); + edgeImitator.expectMessageAmount(1); + Assert.assertTrue(edgeImitator.waitForMessages()); + + deviceProfile.setFirmwareId(firmwareOtaPackageInfo.getId()); + edgeImitator.expectMessageAmount(1); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getFirmwareIdMSB()); + Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getFirmwareIdLSB()); + + // delete profile + edgeImitator.expectMessageAmount(1); + doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + } + + @Test + public void testDeviceProfiles_snmp() throws Exception { + DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("SNMP", createSnmpDeviceProfileTransportConfiguration()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + Assert.assertEquals(DeviceTransportType.SNMP.name(), deviceProfileUpdateMsg.getTransportType()); + + Optional deviceProfileDataOpt = + dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); + + Assert.assertTrue(deviceProfileDataOpt.isPresent()); + DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); + + Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof SnmpDeviceProfileTransportConfiguration); + SnmpDeviceProfileTransportConfiguration transportConfiguration = + (SnmpDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); + Assert.assertEquals(Integer.valueOf(1000), transportConfiguration.getTimeoutMs()); + Assert.assertEquals(Integer.valueOf(3), transportConfiguration.getRetries()); + + Assert.assertFalse(transportConfiguration.getCommunicationConfigs().isEmpty()); + SnmpCommunicationConfig communicationConfig = transportConfiguration.getCommunicationConfigs().get(0); + Assert.assertTrue(communicationConfig instanceof TelemetryQueryingSnmpCommunicationConfig); + TelemetryQueryingSnmpCommunicationConfig snmpCommunicationConfig = + (TelemetryQueryingSnmpCommunicationConfig) communicationConfig; + + Assert.assertEquals(Long.valueOf(500L), snmpCommunicationConfig.getQueryingFrequencyMs()); + Assert.assertFalse(snmpCommunicationConfig.getMappings().isEmpty()); + + SnmpMapping snmpMapping = snmpCommunicationConfig.getMappings().get(0); + Assert.assertEquals("temperature", snmpMapping.getKey()); + Assert.assertEquals("1.3.3.5.6.7.8.9.1", snmpMapping.getOid()); + Assert.assertEquals(DataType.DOUBLE, snmpMapping.getDataType()); + + removeDeviceProfileAndDoBasicAssert(deviceProfile); + } + + @Test + public void testDeviceProfiles_lwm2m() throws Exception { + DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("LWM2M", createLwm2mDeviceProfileTransportConfiguration()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + Assert.assertEquals(DeviceTransportType.LWM2M.name(), deviceProfileUpdateMsg.getTransportType()); + + Optional deviceProfileDataOpt = + dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); + + Assert.assertTrue(deviceProfileDataOpt.isPresent()); + DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); + + Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof Lwm2mDeviceProfileTransportConfiguration); + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = + (Lwm2mDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); + + OtherConfiguration clientLwM2mSettings = transportConfiguration.getClientLwM2mSettings(); + Assert.assertEquals(PowerMode.DRX, clientLwM2mSettings.getPowerMode()); + Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getFwUpdateStrategy()); + Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getSwUpdateStrategy()); + Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getClientOnlyObserveAfterConnect()); + + Assert.assertTrue(transportConfiguration.isBootstrapServerUpdateEnable()); + + Assert.assertFalse(transportConfiguration.getBootstrap().isEmpty()); + LwM2MBootstrapServerCredential lwM2MBootstrapServerCredential = transportConfiguration.getBootstrap().get(0); + Assert.assertTrue(lwM2MBootstrapServerCredential instanceof NoSecLwM2MBootstrapServerCredential); + NoSecLwM2MBootstrapServerCredential noSecLwM2MBootstrapServerCredential = (NoSecLwM2MBootstrapServerCredential) lwM2MBootstrapServerCredential; + + Assert.assertEquals("PUBLIC_KEY", noSecLwM2MBootstrapServerCredential.getServerPublicKey()); + Assert.assertEquals(Integer.valueOf(123), noSecLwM2MBootstrapServerCredential.getShortServerId()); + Assert.assertTrue(noSecLwM2MBootstrapServerCredential.isBootstrapServerIs()); + Assert.assertEquals("localhost", noSecLwM2MBootstrapServerCredential.getHost()); + Assert.assertEquals(Integer.valueOf(5687), noSecLwM2MBootstrapServerCredential.getPort()); + + TelemetryMappingConfiguration observeAttr = transportConfiguration.getObserveAttr(); + Assert.assertEquals("batteryLevel", observeAttr.getKeyName().get("/3_1.0/0/9")); + Assert.assertTrue(observeAttr.getObserve().isEmpty()); + Assert.assertTrue(observeAttr.getAttribute().isEmpty()); + Assert.assertFalse(observeAttr.getTelemetry().isEmpty()); + Assert.assertTrue(observeAttr.getTelemetry().contains("/3_1.0/0/9")); + Assert.assertTrue(observeAttr.getAttributeLwm2m().isEmpty()); + + removeDeviceProfileAndDoBasicAssert(deviceProfile); + } + + @Test + public void testDeviceProfiles_coap() throws Exception { + DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("COAP", createCoapDeviceProfileTransportConfiguration()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + Assert.assertEquals(DeviceTransportType.COAP.name(), deviceProfileUpdateMsg.getTransportType()); + + Optional deviceProfileDataOpt = + dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); + + Assert.assertTrue(deviceProfileDataOpt.isPresent()); + DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); + + Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration transportConfiguration = + (CoapDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); + + PowerSavingConfiguration clientSettings = transportConfiguration.getClientSettings(); + + Assert.assertEquals(PowerMode.DRX, clientSettings.getPowerMode()); + Assert.assertEquals(Long.valueOf(1L), clientSettings.getEdrxCycle()); + Assert.assertEquals(Long.valueOf(1L), clientSettings.getPsmActivityTimer()); + Assert.assertEquals(Long.valueOf(1L), clientSettings.getPagingTransmissionWindow()); + + Assert.assertTrue(transportConfiguration.getCoapDeviceTypeConfiguration() instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration coapDeviceTypeConfiguration = + (DefaultCoapDeviceTypeConfiguration) transportConfiguration.getCoapDeviceTypeConfiguration(); + + Assert.assertTrue(coapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration); + + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = + (ProtoTransportPayloadConfiguration) coapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_TELEMETRY_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema()); + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_ATTRIBUTES_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema()); + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_RPC_RESPONSE_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema()); + Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_RPC_REQUEST_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema()); + + removeDeviceProfileAndDoBasicAssert(deviceProfile); + } + + + private DeviceProfile createDeviceProfileAndDoBasicAssert(String deviceProfileName, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) throws Exception { + DeviceProfile deviceProfile = this.createDeviceProfile(deviceProfileName, deviceProfileTransportConfiguration); + extendDeviceProfileData(deviceProfile); + edgeImitator.expectMessageAmount(1); + deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + return deviceProfile; + } + + private void removeDeviceProfileAndDoBasicAssert(DeviceProfile deviceProfile) throws Exception { + edgeImitator.expectMessageAmount(1); + doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); + DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); + Assert.assertEquals(deviceProfile.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getIdMSB()); + Assert.assertEquals(deviceProfile.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getIdLSB()); + } + + private SnmpDeviceProfileTransportConfiguration createSnmpDeviceProfileTransportConfiguration() { + SnmpDeviceProfileTransportConfiguration transportConfiguration = new SnmpDeviceProfileTransportConfiguration(); + List communicationConfigs = new ArrayList<>(); + TelemetryQueryingSnmpCommunicationConfig communicationConfig = new TelemetryQueryingSnmpCommunicationConfig(); + communicationConfig.setQueryingFrequencyMs(500L); + List mappings = new ArrayList<>(); + mappings.add(new SnmpMapping("1.3.3.5.6.7.8.9.1", "temperature", DataType.DOUBLE)); + communicationConfig.setMappings(mappings); + communicationConfigs.add(communicationConfig); + transportConfiguration.setCommunicationConfigs(communicationConfigs); + transportConfiguration.setTimeoutMs(1000); + transportConfiguration.setRetries(3); + return transportConfiguration; + } + + private Lwm2mDeviceProfileTransportConfiguration createLwm2mDeviceProfileTransportConfiguration() { + Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); + + OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(AbstractLwM2MIntegrationTest.CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); + transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings); + + transportConfiguration.setBootstrapServerUpdateEnable(true); + + TelemetryMappingConfiguration observeAttrConfiguration = + JacksonUtil.fromString(AbstractLwM2MIntegrationTest.OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class); + transportConfiguration.setObserveAttr(observeAttrConfiguration); + + List bootstrap = new ArrayList<>(); + AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential(); + bootstrapServerCredential.setServerPublicKey("PUBLIC_KEY"); + bootstrapServerCredential.setShortServerId(123); + bootstrapServerCredential.setBootstrapServerIs(true); + bootstrapServerCredential.setHost("localhost"); + bootstrapServerCredential.setPort(5687); + bootstrap.add(bootstrapServerCredential); + transportConfiguration.setBootstrap(bootstrap); + + return transportConfiguration; + } + + private CoapDeviceProfileTransportConfiguration createCoapDeviceProfileTransportConfiguration() { + CoapDeviceProfileTransportConfiguration transportConfiguration = new CoapDeviceProfileTransportConfiguration(); + PowerSavingConfiguration clientSettings = new PowerSavingConfiguration(); + clientSettings.setPowerMode(PowerMode.DRX); + clientSettings.setEdrxCycle(1L); + clientSettings.setPsmActivityTimer(1L); + clientSettings.setPagingTransmissionWindow(1L); + transportConfiguration.setClientSettings(clientSettings); + DefaultCoapDeviceTypeConfiguration coapDeviceTypeConfiguration = new DefaultCoapDeviceTypeConfiguration(); + ProtoTransportPayloadConfiguration transportPayloadTypeConfiguration = new ProtoTransportPayloadConfiguration(); + transportPayloadTypeConfiguration.setDeviceTelemetryProtoSchema(AbstractTransportIntegrationTest.DEVICE_TELEMETRY_PROTO_SCHEMA); + transportPayloadTypeConfiguration.setDeviceAttributesProtoSchema(AbstractTransportIntegrationTest.DEVICE_ATTRIBUTES_PROTO_SCHEMA); + transportPayloadTypeConfiguration.setDeviceRpcResponseProtoSchema(AbstractTransportIntegrationTest.DEVICE_RPC_RESPONSE_PROTO_SCHEMA); + transportPayloadTypeConfiguration.setDeviceRpcRequestProtoSchema(AbstractTransportIntegrationTest.DEVICE_RPC_REQUEST_PROTO_SCHEMA); + coapDeviceTypeConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); + transportConfiguration.setCoapDeviceTypeConfiguration(coapDeviceTypeConfiguration); + return transportConfiguration; + } +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java index 50c69110ad..1c01ba93aa 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEdgeTest.java @@ -15,2306 +15,64 @@ */ package org.thingsboard.server.edge; -import com.datastax.oss.driver.api.core.uuid.Uuids; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.core.type.TypeReference; -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.databind.node.ObjectNode; -import com.google.gson.JsonObject; import com.google.protobuf.AbstractMessage; -import com.google.protobuf.ByteString; -import com.google.protobuf.InvalidProtocolBufferException; -import com.google.protobuf.MessageLite; -import io.netty.handler.codec.mqtt.MqttQoS; -import org.awaitility.Awaitility; -import org.junit.After; import org.junit.Assert; -import org.junit.Before; import org.junit.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockMultipartFile; -import org.springframework.test.context.TestPropertySource; -import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; -import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; -import org.thingsboard.common.util.JacksonUtil; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.Dashboard; -import org.thingsboard.server.common.data.DataConstants; -import org.thingsboard.server.common.data.Device; -import org.thingsboard.server.common.data.DeviceProfile; -import org.thingsboard.server.common.data.DeviceTransportType; -import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -import org.thingsboard.server.common.data.OtaPackageInfo; -import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; -import org.thingsboard.server.common.data.StringUtils; -import org.thingsboard.server.common.data.Tenant; -import org.thingsboard.server.common.data.TenantProfile; -import org.thingsboard.server.common.data.User; -import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmInfo; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; -import org.thingsboard.server.common.data.asset.Asset; -import org.thingsboard.server.common.data.device.data.DefaultDeviceConfiguration; -import org.thingsboard.server.common.data.device.data.DeviceData; -import org.thingsboard.server.common.data.device.data.MqttDeviceTransportConfiguration; -import org.thingsboard.server.common.data.device.data.PowerMode; -import org.thingsboard.server.common.data.device.data.PowerSavingConfiguration; -import org.thingsboard.server.common.data.device.profile.AlarmCondition; -import org.thingsboard.server.common.data.device.profile.AlarmConditionFilter; -import org.thingsboard.server.common.data.device.profile.AlarmConditionFilterKey; -import org.thingsboard.server.common.data.device.profile.AlarmConditionKeyType; -import org.thingsboard.server.common.data.device.profile.AlarmRule; -import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; -import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; -import org.thingsboard.server.common.data.device.profile.DeviceProfileAlarm; -import org.thingsboard.server.common.data.device.profile.DeviceProfileData; -import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.JsonTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.Lwm2mDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.ProtoTransportPayloadConfiguration; -import org.thingsboard.server.common.data.device.profile.SimpleAlarmConditionSpec; -import org.thingsboard.server.common.data.device.profile.SnmpDeviceProfileTransportConfiguration; -import org.thingsboard.server.common.data.device.profile.lwm2m.OtherConfiguration; -import org.thingsboard.server.common.data.device.profile.lwm2m.TelemetryMappingConfiguration; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.AbstractLwM2MBootstrapServerCredential; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; -import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.NoSecLwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.edge.Edge; -import org.thingsboard.server.common.data.edge.EdgeEvent; -import org.thingsboard.server.common.data.edge.EdgeEventActionType; -import org.thingsboard.server.common.data.edge.EdgeEventType; -import org.thingsboard.server.common.data.id.DeviceProfileId; -import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.EntityIdFactory; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.kv.DataType; -import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; -import org.thingsboard.server.common.data.ota.OtaPackageType; -import org.thingsboard.server.common.data.page.PageData; -import org.thingsboard.server.common.data.page.PageLink; -import org.thingsboard.server.common.data.query.EntityKeyValueType; -import org.thingsboard.server.common.data.query.FilterPredicateValue; -import org.thingsboard.server.common.data.query.NumericFilterPredicate; -import org.thingsboard.server.common.data.queue.ProcessingStrategy; -import org.thingsboard.server.common.data.queue.ProcessingStrategyType; -import org.thingsboard.server.common.data.queue.Queue; -import org.thingsboard.server.common.data.queue.SubmitStrategy; -import org.thingsboard.server.common.data.queue.SubmitStrategyType; -import org.thingsboard.server.common.data.relation.EntityRelation; -import org.thingsboard.server.common.data.relation.RelationTypeGroup; -import org.thingsboard.server.common.data.rule.RuleChain; -import org.thingsboard.server.common.data.rule.RuleChainMetaData; -import org.thingsboard.server.common.data.rule.RuleChainType; -import org.thingsboard.server.common.data.rule.RuleNode; -import org.thingsboard.server.common.data.security.Authority; -import org.thingsboard.server.common.data.security.DeviceCredentials; -import org.thingsboard.server.common.data.security.DeviceCredentialsType; -import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; -import org.thingsboard.server.common.data.transport.snmp.SnmpMapping; -import org.thingsboard.server.common.data.transport.snmp.config.SnmpCommunicationConfig; -import org.thingsboard.server.common.data.transport.snmp.config.impl.TelemetryQueryingSnmpCommunicationConfig; -import org.thingsboard.server.common.data.widget.WidgetType; -import org.thingsboard.server.common.data.widget.WidgetsBundle; -import org.thingsboard.server.common.msg.queue.ServiceType; -import org.thingsboard.server.common.transport.adaptor.JsonConverter; -import org.thingsboard.server.controller.AbstractControllerTest; -import org.thingsboard.server.dao.edge.EdgeEventService; -import org.thingsboard.server.edge.imitator.EdgeImitator; -import org.thingsboard.server.gen.edge.v1.AdminSettingsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AlarmUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AssetUpdateMsg; -import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; -import org.thingsboard.server.gen.edge.v1.AttributesRequestMsg; import org.thingsboard.server.gen.edge.v1.CustomerUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DashboardUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsRequestMsg; -import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceProfileUpdateMsg; -import org.thingsboard.server.gen.edge.v1.DeviceRpcCallMsg; -import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; import org.thingsboard.server.gen.edge.v1.EdgeConfiguration; -import org.thingsboard.server.gen.edge.v1.EntityDataProto; -import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; -import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg; -import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; -import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; -import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RpcResponseMsg; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; -import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; -import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; import org.thingsboard.server.gen.edge.v1.UpdateMsgType; -import org.thingsboard.server.gen.edge.v1.UplinkMsg; -import org.thingsboard.server.gen.edge.v1.UplinkResponseMsg; -import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; -import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; -import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; -import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; -import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; -import org.thingsboard.server.gen.transport.TransportProtos; -import org.thingsboard.server.queue.util.DataDecodingEncodingService; -import org.thingsboard.server.transport.AbstractTransportIntegrationTest; -import org.thingsboard.server.transport.lwm2m.AbstractLwM2MIntegrationTest; -import org.thingsboard.server.transport.mqtt.MqttTestCallback; -import org.thingsboard.server.transport.mqtt.MqttTestClient; -import java.nio.ByteBuffer; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; import java.util.Optional; -import java.util.Random; -import java.util.TreeMap; import java.util.UUID; -import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; - -@TestPropertySource(properties = { - "edges.enabled=true", - "transport.mqtt.enabled=true" -}) -abstract public class BaseEdgeTest extends AbstractControllerTest { - - private static final String THERMOSTAT_DEVICE_PROFILE_NAME = "Thermostat"; - - private Tenant savedTenant; - private TenantId tenantId; - private User tenantAdmin; - - private DeviceProfile thermostatDeviceProfile; - - private EdgeImitator edgeImitator; - private Edge edge; - - @Autowired - private EdgeEventService edgeEventService; - - @Autowired - private DataDecodingEncodingService dataDecodingEncodingService; - - @Autowired - private TbClusterService clusterService; - - @Before - public void beforeTest() throws Exception { - loginSysAdmin(); - - Tenant tenant = new Tenant(); - tenant.setTitle("My tenant"); - savedTenant = doPost("/api/tenant", tenant, Tenant.class); - tenantId = savedTenant.getId(); - Assert.assertNotNull(savedTenant); - - tenantAdmin = new User(); - tenantAdmin.setAuthority(Authority.TENANT_ADMIN); - tenantAdmin.setTenantId(savedTenant.getId()); - tenantAdmin.setEmail("tenant2@thingsboard.org"); - tenantAdmin.setFirstName("Joe"); - tenantAdmin.setLastName("Downs"); - - tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1"); - // sleep 0.5 second to avoid CREDENTIALS updated message for the user - // user credentials is going to be stored and updated event pushed to edge notification service - // while service will be processing this event edge could be already added and additional message will be pushed - Thread.sleep(500); - - installation(); - - edgeImitator = new EdgeImitator("localhost", 7070, edge.getRoutingKey(), edge.getSecret()); - edgeImitator.expectMessageAmount(17); - edgeImitator.connect(); - - requestEdgeRuleChainMetadata(); - - verifyEdgeConnectionAndInitialData(); - } - - private void requestEdgeRuleChainMetadata() throws Exception { - RuleChainId rootRuleChainId = getEdgeRootRuleChainId(); - RuleChainMetadataRequestMsg.Builder builder = RuleChainMetadataRequestMsg.newBuilder() - .setRuleChainIdMSB(rootRuleChainId.getId().getMostSignificantBits()) - .setRuleChainIdLSB(rootRuleChainId.getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(builder); - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder() - .addRuleChainMetadataRequestMsg(builder.build()); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - } - - private RuleChainId getEdgeRootRuleChainId() throws Exception { - List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", - new TypeReference>() {}, new PageLink(100)).getData(); - for (RuleChain edgeRuleChain : edgeRuleChains) { - if (edgeRuleChain.isRoot()) { - return edgeRuleChain.getId(); - } - } - throw new RuntimeException("Root rule chain not found"); - } - - @After - public void afterTest() throws Exception { - try { - edgeImitator.disconnect(); - } catch (Exception ignored) {} - - loginSysAdmin(); - - doDelete("/api/tenant/" + savedTenant.getUuidId()) - .andExpect(status().isOk()); - } - - private void installation() throws Exception { - edge = doPost("/api/edge", constructEdge("Test Edge", "test"), Edge.class); - - thermostatDeviceProfile = this.createDeviceProfile(THERMOSTAT_DEVICE_PROFILE_NAME, - createMqttDeviceProfileTransportConfiguration(new JsonTransportPayloadConfiguration(), false)); - - extendDeviceProfileData(thermostatDeviceProfile); - thermostatDeviceProfile = doPost("/api/deviceProfile", thermostatDeviceProfile, DeviceProfile.class); - - Device savedDevice = saveDevice("Edge Device 1", THERMOSTAT_DEVICE_PROFILE_NAME); - doPost("/api/edge/" + edge.getUuidId() - + "/device/" + savedDevice.getUuidId(), Device.class); - - Asset savedAsset = saveAsset("Edge Asset 1"); - doPost("/api/edge/" + edge.getUuidId() - + "/asset/" + savedAsset.getUuidId(), Asset.class); - } - - private void extendDeviceProfileData(DeviceProfile deviceProfile) { - DeviceProfileData profileData = deviceProfile.getProfileData(); - List alarms = new ArrayList<>(); - DeviceProfileAlarm deviceProfileAlarm = new DeviceProfileAlarm(); - deviceProfileAlarm.setAlarmType("High Temperature"); - AlarmRule alarmRule = new AlarmRule(); - alarmRule.setAlarmDetails("Alarm Details"); - AlarmCondition alarmCondition = new AlarmCondition(); - alarmCondition.setSpec(new SimpleAlarmConditionSpec()); - List condition = new ArrayList<>(); - AlarmConditionFilter alarmConditionFilter = new AlarmConditionFilter(); - alarmConditionFilter.setKey(new AlarmConditionFilterKey(AlarmConditionKeyType.ATTRIBUTE, "temperature")); - NumericFilterPredicate predicate = new NumericFilterPredicate(); - predicate.setOperation(NumericFilterPredicate.NumericOperation.GREATER); - predicate.setValue(new FilterPredicateValue<>(55.0)); - alarmConditionFilter.setPredicate(predicate); - alarmConditionFilter.setValueType(EntityKeyValueType.NUMERIC); - condition.add(alarmConditionFilter); - alarmCondition.setCondition(condition); - alarmRule.setCondition(alarmCondition); - deviceProfileAlarm.setClearRule(alarmRule); - TreeMap createRules = new TreeMap<>(); - createRules.put(AlarmSeverity.CRITICAL, alarmRule); - deviceProfileAlarm.setCreateRules(createRules); - alarms.add(deviceProfileAlarm); - profileData.setAlarms(alarms); - profileData.setProvisionConfiguration(new AllowCreateNewDevicesDeviceProfileProvisionConfiguration("123")); - } - - private void verifyEdgeConnectionAndInitialData() throws Exception { - Assert.assertTrue(edgeImitator.waitForMessages()); - - EdgeConfiguration configuration = edgeImitator.getConfiguration(); - Assert.assertNotNull(configuration); - - testAutoGeneratedCodeByProtobuf(configuration); - - Optional deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class); - Assert.assertTrue(deviceUpdateMsgOpt.isPresent()); - DeviceUpdateMsg deviceUpdateMsg = deviceUpdateMsgOpt.get(); - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); - UUID deviceUUID = new UUID(deviceUpdateMsg.getIdMSB(), deviceUpdateMsg.getIdLSB()); - Device device = doGet("/api/device/" + deviceUUID, Device.class); - Assert.assertNotNull(device); - List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", - new TypeReference>() {}, new PageLink(100)).getData(); - Assert.assertTrue(edgeDevices.contains(device)); - - List deviceProfileUpdateMsgList = edgeImitator.findAllMessagesByType(DeviceProfileUpdateMsg.class); - Assert.assertEquals(3, deviceProfileUpdateMsgList.size()); - Optional deviceProfileUpdateMsgOpt = - deviceProfileUpdateMsgList.stream().filter(dfum -> THERMOSTAT_DEVICE_PROFILE_NAME.equals(dfum.getName())).findAny(); - Assert.assertTrue(deviceProfileUpdateMsgOpt.isPresent()); - DeviceProfileUpdateMsg deviceProfileUpdateMsg = deviceProfileUpdateMsgOpt.get(); - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); - UUID deviceProfileUUID = new UUID(deviceProfileUpdateMsg.getIdMSB(), deviceProfileUpdateMsg.getIdLSB()); - DeviceProfile deviceProfile = doGet("/api/deviceProfile/" + deviceProfileUUID, DeviceProfile.class); - Assert.assertNotNull(deviceProfile); - Assert.assertNotNull(deviceProfile.getProfileData()); - Assert.assertNotNull(deviceProfile.getProfileData().getAlarms()); - Assert.assertNotNull(deviceProfile.getProfileData().getAlarms().get(0).getClearRule()); - - testAutoGeneratedCodeByProtobuf(deviceProfileUpdateMsg); - - Optional assetUpdateMsgOpt = edgeImitator.findMessageByType(AssetUpdateMsg.class); - Assert.assertTrue(assetUpdateMsgOpt.isPresent()); - AssetUpdateMsg assetUpdateMsg = assetUpdateMsgOpt.get(); - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); - UUID assetUUID = new UUID(assetUpdateMsg.getIdMSB(), assetUpdateMsg.getIdLSB()); - Asset asset = doGet("/api/asset/" + assetUUID.toString(), Asset.class); - Assert.assertNotNull(asset); - List edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?", - new TypeReference>() {}, new PageLink(100)).getData(); - Assert.assertTrue(edgeAssets.contains(asset)); - - testAutoGeneratedCodeByProtobuf(assetUpdateMsg); - - Optional ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); - Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); - RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType()); - UUID ruleChainUUID = new UUID(ruleChainUpdateMsg.getIdMSB(), ruleChainUpdateMsg.getIdLSB()); - RuleChain ruleChain = doGet("/api/ruleChain/" + ruleChainUUID, RuleChain.class); - Assert.assertNotNull(ruleChain); - List edgeRuleChains = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/ruleChains?", - new TypeReference>() {}, new PageLink(100)).getData(); - Assert.assertTrue(edgeRuleChains.contains(ruleChain)); - - testAutoGeneratedCodeByProtobuf(ruleChainUpdateMsg); - - Optional ruleChainMetadataUpdateOpt = edgeImitator.findMessageByType(RuleChainMetadataUpdateMsg.class); - Assert.assertTrue(ruleChainMetadataUpdateOpt.isPresent()); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = ruleChainMetadataUpdateOpt.get(); - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, ruleChainMetadataUpdateMsg.getMsgType()); - Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdMSB()); - Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB()); - - validateAdminSettings(); - } - - private void validateAdminSettings() throws JsonProcessingException { - List adminSettingsUpdateMsgs = edgeImitator.findAllMessagesByType(AdminSettingsUpdateMsg.class); - Assert.assertEquals(4, adminSettingsUpdateMsgs.size()); - - for (AdminSettingsUpdateMsg adminSettingsUpdateMsg : adminSettingsUpdateMsgs) { - if (adminSettingsUpdateMsg.getKey().equals("mail")) { - validateMailAdminSettings(adminSettingsUpdateMsg); - } - if (adminSettingsUpdateMsg.getKey().equals("mailTemplates")) { - validateMailTemplatesAdminSettings(adminSettingsUpdateMsg); - } - } - } - - private void validateMailAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException { - JsonNode jsonNode = mapper.readTree(adminSettingsUpdateMsg.getJsonValue()); - Assert.assertNotNull(jsonNode.get("mailFrom")); - Assert.assertNotNull(jsonNode.get("smtpProtocol")); - Assert.assertNotNull(jsonNode.get("smtpHost")); - Assert.assertNotNull(jsonNode.get("smtpPort")); - Assert.assertNotNull(jsonNode.get("timeout")); - } - - private void validateMailTemplatesAdminSettings(AdminSettingsUpdateMsg adminSettingsUpdateMsg) throws JsonProcessingException { - JsonNode jsonNode = mapper.readTree(adminSettingsUpdateMsg.getJsonValue()); - Assert.assertNotNull(jsonNode.get("accountActivated")); - Assert.assertNotNull(jsonNode.get("accountLockout")); - Assert.assertNotNull(jsonNode.get("activation")); - Assert.assertNotNull(jsonNode.get("passwordWasReset")); - Assert.assertNotNull(jsonNode.get("resetPassword")); - Assert.assertNotNull(jsonNode.get("test")); - } - - @Test - public void testDeviceProfiles() throws Exception { - // 1 - DeviceProfile deviceProfile = this.createDeviceProfile("ONE_MORE_DEVICE_PROFILE", null); - extendDeviceProfileData(deviceProfile); - edgeImitator.expectMessageAmount(1); - deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); - DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); - - // 2 - OtaPackageInfo firmwareOtaPackageInfo = saveOtaPackageInfo(deviceProfile.getId()); - edgeImitator.expectMessageAmount(1); - Assert.assertTrue(edgeImitator.waitForMessages()); - - deviceProfile.setFirmwareId(firmwareOtaPackageInfo.getId()); - edgeImitator.expectMessageAmount(1); - deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); - deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; - Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getMostSignificantBits(), deviceProfileUpdateMsg.getFirmwareIdMSB()); - Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getLeastSignificantBits(), deviceProfileUpdateMsg.getFirmwareIdLSB()); - - // 3 - edgeImitator.expectMessageAmount(1); - doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); - deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); - } - - @Test - public void testDeviceProfiles_snmp() throws Exception { - DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("SNMP", createSnmpDeviceProfileTransportConfiguration()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); - DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(DeviceTransportType.SNMP.name(), deviceProfileUpdateMsg.getTransportType()); - - Optional deviceProfileDataOpt = - dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); - - Assert.assertTrue(deviceProfileDataOpt.isPresent()); - DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); - - Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof SnmpDeviceProfileTransportConfiguration); - SnmpDeviceProfileTransportConfiguration transportConfiguration = - (SnmpDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); - Assert.assertEquals(Integer.valueOf(1000), transportConfiguration.getTimeoutMs()); - Assert.assertEquals(Integer.valueOf(3), transportConfiguration.getRetries()); - - Assert.assertFalse(transportConfiguration.getCommunicationConfigs().isEmpty()); - SnmpCommunicationConfig communicationConfig = transportConfiguration.getCommunicationConfigs().get(0); - Assert.assertTrue(communicationConfig instanceof TelemetryQueryingSnmpCommunicationConfig); - TelemetryQueryingSnmpCommunicationConfig snmpCommunicationConfig = - (TelemetryQueryingSnmpCommunicationConfig) communicationConfig; - - Assert.assertEquals(Long.valueOf(500L), snmpCommunicationConfig.getQueryingFrequencyMs()); - Assert.assertFalse(snmpCommunicationConfig.getMappings().isEmpty()); - - SnmpMapping snmpMapping = snmpCommunicationConfig.getMappings().get(0); - Assert.assertEquals("temperature", snmpMapping.getKey()); - Assert.assertEquals("1.3.3.5.6.7.8.9.1", snmpMapping.getOid()); - Assert.assertEquals(DataType.DOUBLE, snmpMapping.getDataType()); - - removeDeviceProfileAndDoBasicAssert(deviceProfile); - } - - @Test - public void testDeviceProfiles_lwm2m() throws Exception { - DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("LWM2M", createLwm2mDeviceProfileTransportConfiguration()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); - DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(DeviceTransportType.LWM2M.name(), deviceProfileUpdateMsg.getTransportType()); - - Optional deviceProfileDataOpt = - dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); - - Assert.assertTrue(deviceProfileDataOpt.isPresent()); - DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); - - Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof Lwm2mDeviceProfileTransportConfiguration); - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = - (Lwm2mDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); - - OtherConfiguration clientLwM2mSettings = transportConfiguration.getClientLwM2mSettings(); - Assert.assertEquals(PowerMode.DRX, clientLwM2mSettings.getPowerMode()); - Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getFwUpdateStrategy()); - Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getSwUpdateStrategy()); - Assert.assertEquals(Integer.valueOf(1), clientLwM2mSettings.getClientOnlyObserveAfterConnect()); - - Assert.assertTrue(transportConfiguration.isBootstrapServerUpdateEnable()); - - Assert.assertFalse(transportConfiguration.getBootstrap().isEmpty()); - LwM2MBootstrapServerCredential lwM2MBootstrapServerCredential = transportConfiguration.getBootstrap().get(0); - Assert.assertTrue(lwM2MBootstrapServerCredential instanceof NoSecLwM2MBootstrapServerCredential); - NoSecLwM2MBootstrapServerCredential noSecLwM2MBootstrapServerCredential = (NoSecLwM2MBootstrapServerCredential) lwM2MBootstrapServerCredential; - - Assert.assertEquals("PUBLIC_KEY", noSecLwM2MBootstrapServerCredential.getServerPublicKey()); - Assert.assertEquals(Integer.valueOf(123), noSecLwM2MBootstrapServerCredential.getShortServerId()); - Assert.assertTrue(noSecLwM2MBootstrapServerCredential.isBootstrapServerIs()); - Assert.assertEquals("localhost", noSecLwM2MBootstrapServerCredential.getHost()); - Assert.assertEquals(Integer.valueOf(5687), noSecLwM2MBootstrapServerCredential.getPort()); - - TelemetryMappingConfiguration observeAttr = transportConfiguration.getObserveAttr(); - Assert.assertEquals("batteryLevel", observeAttr.getKeyName().get("/3_1.0/0/9")); - Assert.assertTrue(observeAttr.getObserve().isEmpty()); - Assert.assertTrue(observeAttr.getAttribute().isEmpty()); - Assert.assertFalse(observeAttr.getTelemetry().isEmpty()); - Assert.assertTrue(observeAttr.getTelemetry().contains("/3_1.0/0/9")); - Assert.assertTrue(observeAttr.getAttributeLwm2m().isEmpty()); - - removeDeviceProfileAndDoBasicAssert(deviceProfile); - } - - @Test - public void testDeviceProfiles_coap() throws Exception { - DeviceProfile deviceProfile = createDeviceProfileAndDoBasicAssert("COAP", createCoapDeviceProfileTransportConfiguration()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); - DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(DeviceTransportType.COAP.name(), deviceProfileUpdateMsg.getTransportType()); - - Optional deviceProfileDataOpt = - dataDecodingEncodingService.decode(deviceProfileUpdateMsg.getProfileDataBytes().toByteArray()); - - Assert.assertTrue(deviceProfileDataOpt.isPresent()); - DeviceProfileData deviceProfileData = deviceProfileDataOpt.get(); - - Assert.assertTrue(deviceProfileData.getTransportConfiguration() instanceof CoapDeviceProfileTransportConfiguration); - CoapDeviceProfileTransportConfiguration transportConfiguration = - (CoapDeviceProfileTransportConfiguration) deviceProfileData.getTransportConfiguration(); - - PowerSavingConfiguration clientSettings = transportConfiguration.getClientSettings(); - - Assert.assertEquals(PowerMode.DRX, clientSettings.getPowerMode()); - Assert.assertEquals(Long.valueOf(1L), clientSettings.getEdrxCycle()); - Assert.assertEquals(Long.valueOf(1L), clientSettings.getPsmActivityTimer()); - Assert.assertEquals(Long.valueOf(1L), clientSettings.getPagingTransmissionWindow()); - - Assert.assertTrue(transportConfiguration.getCoapDeviceTypeConfiguration() instanceof DefaultCoapDeviceTypeConfiguration); - DefaultCoapDeviceTypeConfiguration coapDeviceTypeConfiguration = - (DefaultCoapDeviceTypeConfiguration) transportConfiguration.getCoapDeviceTypeConfiguration(); - - Assert.assertTrue(coapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration() instanceof ProtoTransportPayloadConfiguration); - - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = - (ProtoTransportPayloadConfiguration) coapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); - - Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_TELEMETRY_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema()); - Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_ATTRIBUTES_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema()); - Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_RPC_RESPONSE_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema()); - Assert.assertEquals(AbstractTransportIntegrationTest.DEVICE_RPC_REQUEST_PROTO_SCHEMA, protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema()); - - removeDeviceProfileAndDoBasicAssert(deviceProfile); - } - - private DeviceProfile createDeviceProfileAndDoBasicAssert(String deviceProfileName, DeviceProfileTransportConfiguration deviceProfileTransportConfiguration) throws Exception { - DeviceProfile deviceProfile = this.createDeviceProfile(deviceProfileName, deviceProfileTransportConfiguration); - extendDeviceProfileData(deviceProfile); - edgeImitator.expectMessageAmount(1); - deviceProfile = doPost("/api/deviceProfile", deviceProfile, DeviceProfile.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - return deviceProfile; - } - - private void removeDeviceProfileAndDoBasicAssert(DeviceProfile deviceProfile) throws Exception { - edgeImitator.expectMessageAmount(1); - doDelete("/api/deviceProfile/" + deviceProfile.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceProfileUpdateMsg); - DeviceProfileUpdateMsg deviceProfileUpdateMsg = (DeviceProfileUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceProfileUpdateMsg.getMsgType()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdMSB(), deviceProfile.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceProfileUpdateMsg.getIdLSB(), deviceProfile.getUuidId().getLeastSignificantBits()); - } - - private SnmpDeviceProfileTransportConfiguration createSnmpDeviceProfileTransportConfiguration() { - SnmpDeviceProfileTransportConfiguration transportConfiguration = new SnmpDeviceProfileTransportConfiguration(); - List communicationConfigs = new ArrayList<>(); - TelemetryQueryingSnmpCommunicationConfig communicationConfig = new TelemetryQueryingSnmpCommunicationConfig(); - communicationConfig.setQueryingFrequencyMs(500L); - List mappings = new ArrayList<>(); - mappings.add(new SnmpMapping("1.3.3.5.6.7.8.9.1", "temperature", DataType.DOUBLE)); - communicationConfig.setMappings(mappings); - communicationConfigs.add(communicationConfig); - transportConfiguration.setCommunicationConfigs(communicationConfigs); - transportConfiguration.setTimeoutMs(1000); - transportConfiguration.setRetries(3); - return transportConfiguration; - } - - private Lwm2mDeviceProfileTransportConfiguration createLwm2mDeviceProfileTransportConfiguration() { - Lwm2mDeviceProfileTransportConfiguration transportConfiguration = new Lwm2mDeviceProfileTransportConfiguration(); - - OtherConfiguration clientLwM2mSettings = JacksonUtil.fromString(AbstractLwM2MIntegrationTest.CLIENT_LWM2M_SETTINGS, OtherConfiguration.class); - transportConfiguration.setClientLwM2mSettings(clientLwM2mSettings); - - transportConfiguration.setBootstrapServerUpdateEnable(true); - - TelemetryMappingConfiguration observeAttrConfiguration = - JacksonUtil.fromString(AbstractLwM2MIntegrationTest.OBSERVE_ATTRIBUTES_WITH_PARAMS, TelemetryMappingConfiguration.class); - transportConfiguration.setObserveAttr(observeAttrConfiguration); - - List bootstrap = new ArrayList<>(); - AbstractLwM2MBootstrapServerCredential bootstrapServerCredential = new NoSecLwM2MBootstrapServerCredential(); - bootstrapServerCredential.setServerPublicKey("PUBLIC_KEY"); - bootstrapServerCredential.setShortServerId(123); - bootstrapServerCredential.setBootstrapServerIs(true); - bootstrapServerCredential.setHost("localhost"); - bootstrapServerCredential.setPort(5687); - bootstrap.add(bootstrapServerCredential); - transportConfiguration.setBootstrap(bootstrap); - - return transportConfiguration; - } - - private CoapDeviceProfileTransportConfiguration createCoapDeviceProfileTransportConfiguration() { - CoapDeviceProfileTransportConfiguration transportConfiguration = new CoapDeviceProfileTransportConfiguration(); - PowerSavingConfiguration clientSettings = new PowerSavingConfiguration(); - clientSettings.setPowerMode(PowerMode.DRX); - clientSettings.setEdrxCycle(1L); - clientSettings.setPsmActivityTimer(1L); - clientSettings.setPagingTransmissionWindow(1L); - transportConfiguration.setClientSettings(clientSettings); - DefaultCoapDeviceTypeConfiguration coapDeviceTypeConfiguration = new DefaultCoapDeviceTypeConfiguration(); - ProtoTransportPayloadConfiguration transportPayloadTypeConfiguration = new ProtoTransportPayloadConfiguration(); - transportPayloadTypeConfiguration.setDeviceTelemetryProtoSchema(AbstractTransportIntegrationTest.DEVICE_TELEMETRY_PROTO_SCHEMA); - transportPayloadTypeConfiguration.setDeviceAttributesProtoSchema(AbstractTransportIntegrationTest.DEVICE_ATTRIBUTES_PROTO_SCHEMA); - transportPayloadTypeConfiguration.setDeviceRpcResponseProtoSchema(AbstractTransportIntegrationTest.DEVICE_RPC_RESPONSE_PROTO_SCHEMA); - transportPayloadTypeConfiguration.setDeviceRpcRequestProtoSchema(AbstractTransportIntegrationTest.DEVICE_RPC_REQUEST_PROTO_SCHEMA); - coapDeviceTypeConfiguration.setTransportPayloadTypeConfiguration(transportPayloadTypeConfiguration); - transportConfiguration.setCoapDeviceTypeConfiguration(coapDeviceTypeConfiguration); - return transportConfiguration; - } - - @Test - public void testDevices() throws Exception { - // 1 - Device savedDevice = saveDeviceOnCloudAndVerifyDeliveryToEdge(); - - // 2 - edgeImitator.expectMessageAmount(1); - doDelete("/api/edge/" + edge.getUuidId() - + "/device/" + savedDevice.getUuidId(), Device.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); - DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); - Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); - - // 3 - edgeImitator.expectMessageAmount(1); - doDelete("/api/device/" + savedDevice.getUuidId()) - .andExpect(status().isOk()); - // we should not get any message because device is not assigned to edge any more - Assert.assertFalse(edgeImitator.waitForMessages(1)); - - // 4 - edgeImitator.expectMessageAmount(1); - savedDevice = saveDevice("Edge Device 3", "Default"); - doPost("/api/edge/" + edge.getUuidId() - + "/device/" + savedDevice.getUuidId(), Device.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); - deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); - Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName()); - Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType()); - - // 5 - edgeImitator.expectMessageAmount(1); - doDelete("/api/device/" + savedDevice.getUuidId()) - .andExpect(status().isOk()); - // in this case we should get messages because device was assigned to edge - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); - deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, deviceUpdateMsg.getMsgType()); - Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); - - } - - @Test - public void testDeviceReachedMaximumAllowedOnCloud() throws Exception { - // update tenant profile configuration - loginSysAdmin(); - TenantProfile tenantProfile = doGet("/api/tenantProfile/" + savedTenant.getTenantProfileId().getId(), TenantProfile.class); - DefaultTenantProfileConfiguration profileConfiguration = - (DefaultTenantProfileConfiguration) tenantProfile.getProfileData().getConfiguration(); - profileConfiguration.setMaxDevices(1); - tenantProfile.getProfileData().setConfiguration(profileConfiguration); - doPost("/api/tenantProfile/", tenantProfile, TenantProfile.class); - - loginTenantAdmin(); - - UUID uuid = Uuids.timeBased(); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); - deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); - deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); - deviceUpdateMsgBuilder.setName("Edge Device"); - deviceUpdateMsgBuilder.setType("default"); - deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); - uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); - - edgeImitator.expectResponsesAmount(1); - - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - - Assert.assertTrue(edgeImitator.waitForResponses()); - - UplinkResponseMsg latestResponseMsg = edgeImitator.getLatestResponseMsg(); - Assert.assertTrue(latestResponseMsg.getSuccess()); - } +abstract public class BaseEdgeTest extends AbstractEdgeTest { @Test - public void testAssets() throws Exception { - // 1 - edgeImitator.expectMessageAmount(1); - Asset savedAsset = saveAsset("Edge Asset 2"); - doPost("/api/edge/" + edge.getUuidId() - + "/asset/" + savedAsset.getUuidId(), Asset.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); - AssetUpdateMsg assetUpdateMsg = (AssetUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); - Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); - Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName()); - Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType()); - - // 2 - edgeImitator.expectMessageAmount(1); - doDelete("/api/edge/" + edge.getUuidId() - + "/asset/" + savedAsset.getUuidId(), Asset.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); - assetUpdateMsg = (AssetUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); - Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); - Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); - - // 3 + public void testEdge_assignToCustomer_unassignFromCustomer() throws Exception { + // assign edge to customer edgeImitator.expectMessageAmount(1); - doDelete("/api/asset/" + savedAsset.getUuidId()) - .andExpect(status().isOk()); + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); Assert.assertFalse(edgeImitator.waitForMessages(1)); - // 4 - edgeImitator.expectMessageAmount(1); - savedAsset = saveAsset("Edge Asset 3"); - doPost("/api/edge/" + edge.getUuidId() - + "/asset/" + savedAsset.getUuidId(), Asset.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); - assetUpdateMsg = (AssetUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); - Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); - Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(assetUpdateMsg.getName(), savedAsset.getName()); - Assert.assertEquals(assetUpdateMsg.getType(), savedAsset.getType()); - - // 5 - edgeImitator.expectMessageAmount(1); - doDelete("/api/asset/" + savedAsset.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AssetUpdateMsg); - assetUpdateMsg = (AssetUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, assetUpdateMsg.getMsgType()); - Assert.assertEquals(assetUpdateMsg.getIdMSB(), savedAsset.getUuidId().getMostSignificantBits()); - Assert.assertEquals(assetUpdateMsg.getIdLSB(), savedAsset.getUuidId().getLeastSignificantBits()); - } - - @Test - public void testRuleChains() throws Exception { - // 1 + // assign edge to customer edgeImitator.expectMessageAmount(2); - RuleChain ruleChain = new RuleChain(); - ruleChain.setName("Edge Test Rule Chain"); - ruleChain.setType(RuleChainType.EDGE); - RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); - doPost("/api/edge/" + edge.getUuidId() - + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class); - createRuleChainMetadata(savedRuleChain); - Assert.assertTrue(edgeImitator.waitForMessages()); - Optional ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); - Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); - RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); - Assert.assertTrue(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType()) || - UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType())); - Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits()); - Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(ruleChainUpdateMsg.getName(), savedRuleChain.getName()); - - // 2 - testRuleChainMetadataRequestMsg(savedRuleChain.getId()); - - // 3 - edgeImitator.expectMessageAmount(1); - doDelete("/api/edge/" + edge.getUuidId() - + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); - Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); - ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType()); - Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits()); - Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); - - // 4 - edgeImitator.expectMessageAmount(1); - doDelete("/api/ruleChain/" + savedRuleChain.getUuidId()) - .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); - } - - private void testRuleChainMetadataRequestMsg(RuleChainId ruleChainId) throws Exception { - RuleChainMetadataRequestMsg.Builder ruleChainMetadataRequestMsgBuilder = RuleChainMetadataRequestMsg.newBuilder() - .setRuleChainIdMSB(ruleChainId.getId().getMostSignificantBits()) - .setRuleChainIdLSB(ruleChainId.getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(ruleChainMetadataRequestMsgBuilder); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder() - .addRuleChainMetadataRequestMsg(ruleChainMetadataRequestMsgBuilder.build()); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = (RuleChainMetadataUpdateMsg) latestMessage; - RuleChainId receivedRuleChainId = - new RuleChainId(new UUID(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB())); - Assert.assertEquals(ruleChainId, receivedRuleChainId); - } - - private void createRuleChainMetadata(RuleChain ruleChain) throws Exception { - RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); - ruleChainMetaData.setRuleChainId(ruleChain.getId()); - - ObjectMapper mapper = new ObjectMapper(); - - RuleNode ruleNode1 = new RuleNode(); - ruleNode1.setName("name1"); - ruleNode1.setType("type1"); - ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\"")); - - RuleNode ruleNode2 = new RuleNode(); - ruleNode2.setName("name2"); - ruleNode2.setType("type2"); - ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\"")); - - RuleNode ruleNode3 = new RuleNode(); - ruleNode3.setName("name3"); - ruleNode3.setType("type3"); - ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\"")); - - List ruleNodes = new ArrayList<>(); - ruleNodes.add(ruleNode1); - ruleNodes.add(ruleNode2); - ruleNodes.add(ruleNode3); - ruleChainMetaData.setFirstNodeIndex(0); - ruleChainMetaData.setNodes(ruleNodes); - - ruleChainMetaData.addConnectionInfo(0, 1, "success"); - ruleChainMetaData.addConnectionInfo(0, 2, "fail"); - ruleChainMetaData.addConnectionInfo(1, 2, "success"); - - doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); - } - - @Test - public void testDashboards() throws Exception { - // 1 - edgeImitator.expectMessageAmount(1); - Dashboard dashboard = new Dashboard(); - dashboard.setTitle("Edge Test Dashboard"); - Dashboard savedDashboard = doPost("/api/dashboard", dashboard, Dashboard.class); - doPost("/api/edge/" + edge.getUuidId() - + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); - DashboardUpdateMsg dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); - Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits()); - Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName()); - testAutoGeneratedCodeByProtobuf(dashboardUpdateMsg); - - // 2 - edgeImitator.expectMessageAmount(1); - savedDashboard.setTitle("Updated Edge Test Dashboard"); - doPost("/api/dashboard", savedDashboard, Dashboard.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); - dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); - Assert.assertEquals(dashboardUpdateMsg.getTitle(), savedDashboard.getName()); - - // 3 - edgeImitator.expectMessageAmount(1); - doDelete("/api/edge/" + edge.getUuidId() - + "/dashboard/" + savedDashboard.getUuidId(), Dashboard.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DashboardUpdateMsg); - dashboardUpdateMsg = (DashboardUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, dashboardUpdateMsg.getMsgType()); - Assert.assertEquals(dashboardUpdateMsg.getIdMSB(), savedDashboard.getUuidId().getMostSignificantBits()); - Assert.assertEquals(dashboardUpdateMsg.getIdLSB(), savedDashboard.getUuidId().getLeastSignificantBits()); - - // 4 - edgeImitator.expectMessageAmount(1); - doDelete("/api/dashboard/" + savedDashboard.getUuidId()) - .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); - } - - @Test - public void testRelations() throws Exception { - // 1 - edgeImitator.expectMessageAmount(1); - Device device = findDeviceByName("Edge Device 1"); - Asset asset = findAssetByName("Edge Asset 1"); - EntityRelation relation = new EntityRelation(); - relation.setType("test"); - relation.setFrom(device.getId()); - relation.setTo(asset.getId()); - relation.setTypeGroup(RelationTypeGroup.COMMON); - doPost("/api/relation", relation); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); - RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, relationUpdateMsg.getMsgType()); - Assert.assertEquals(relationUpdateMsg.getType(), relation.getType()); - Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getFromIdLSB(), relation.getFrom().getId().getLeastSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); - Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); - Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); - - // 2 - edgeImitator.expectMessageAmount(1); - doDelete("/api/relation?" + - "fromId=" + relation.getFrom().getId().toString() + - "&fromType=" + relation.getFrom().getEntityType().name() + - "&relationType=" + relation.getType() + - "&relationTypeGroup=" + relation.getTypeGroup().name() + - "&toId=" + relation.getTo().getId().toString() + - "&toType=" + relation.getTo().getEntityType().name()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); - relationUpdateMsg = (RelationUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, relationUpdateMsg.getMsgType()); - Assert.assertEquals(relationUpdateMsg.getType(), relation.getType()); - Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getFromIdLSB(), relation.getFrom().getId().getLeastSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); - Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits()); - Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); - Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); - } - - @Test - public void testAlarms() throws Exception { - // 1 - edgeImitator.expectMessageAmount(1); - Device device = findDeviceByName("Edge Device 1"); - Alarm alarm = new Alarm(); - alarm.setOriginator(device.getId()); - alarm.setStatus(AlarmStatus.ACTIVE_UNACK); - alarm.setType("alarm"); - alarm.setSeverity(AlarmSeverity.CRITICAL); - Alarm savedAlarm = doPost("/api/alarm", alarm, Alarm.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); - AlarmUpdateMsg alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); - Assert.assertEquals(alarmUpdateMsg.getType(), savedAlarm.getType()); - Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName()); - Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); - Assert.assertEquals(alarmUpdateMsg.getStatus(), savedAlarm.getStatus().name()); - Assert.assertEquals(alarmUpdateMsg.getSeverity(), savedAlarm.getSeverity().name()); - - // 2 - edgeImitator.expectMessageAmount(1); - doPost("/api/alarm/" + savedAlarm.getUuidId() + "/ack"); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); - alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ALARM_ACK_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); - Assert.assertEquals(alarmUpdateMsg.getType(), savedAlarm.getType()); - Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName()); - Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); - Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.ACTIVE_ACK.name()); - - // 3 - edgeImitator.expectMessageAmount(1); - doPost("/api/alarm/" + savedAlarm.getUuidId() + "/clear"); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); - alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ALARM_CLEAR_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); - Assert.assertEquals(alarmUpdateMsg.getType(), savedAlarm.getType()); - Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName()); - Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); - Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name()); - - // 4 - edgeImitator.expectMessageAmount(1); - doDelete("/api/alarm/" + savedAlarm.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof AlarmUpdateMsg); - alarmUpdateMsg = (AlarmUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, alarmUpdateMsg.getMsgType()); - Assert.assertEquals(alarmUpdateMsg.getType(), savedAlarm.getType()); - Assert.assertEquals(alarmUpdateMsg.getName(), savedAlarm.getName()); - Assert.assertEquals(alarmUpdateMsg.getOriginatorName(), device.getName()); - Assert.assertEquals(alarmUpdateMsg.getStatus(), AlarmStatus.CLEARED_ACK.name()); - } - - @Test - public void testEntityView() throws Exception { - // 1 - edgeImitator.expectMessageAmount(1); - Device device = findDeviceByName("Edge Device 1"); - EntityView entityView = new EntityView(); - entityView.setName("Edge EntityView 1"); - entityView.setType("test"); - entityView.setEntityId(device.getId()); - EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class); - doPost("/api/edge/" + edge.getUuidId() - + "/entityView/" + savedEntityView.getUuidId(), EntityView.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - verifyEntityViewUpdateMsg(savedEntityView, device); - - - // 2 - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - EntityViewsRequestMsg.Builder entityViewsRequestBuilder = EntityViewsRequestMsg.newBuilder(); - entityViewsRequestBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits()); - entityViewsRequestBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits()); - entityViewsRequestBuilder.setEntityType(device.getId().getEntityType().name()); - testAutoGeneratedCodeByProtobuf(entityViewsRequestBuilder); - uplinkMsgBuilder.addEntityViewsRequestMsg(entityViewsRequestBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - verifyEntityViewUpdateMsg(savedEntityView, device); - - - // 3 - edgeImitator.expectMessageAmount(1); - doDelete("/api/edge/" + edge.getUuidId() - + "/entityView/" + savedEntityView.getUuidId(), EntityView.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); - EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); - Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits()); - Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits()); - - - edgeImitator.expectMessageAmount(1); - doDelete("/api/entityView/" + savedEntityView.getUuidId()) - .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); - } - - private void verifyEntityViewUpdateMsg(EntityView entityView, Device device) { - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); - EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); - Assert.assertEquals(entityViewUpdateMsg.getType(), entityView.getType()); - Assert.assertEquals(entityViewUpdateMsg.getName(), entityView.getName()); - Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), entityView.getUuidId().getMostSignificantBits()); - Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), entityView.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(entityViewUpdateMsg.getEntityIdMSB(), device.getUuidId().getMostSignificantBits()); - Assert.assertEquals(entityViewUpdateMsg.getEntityIdLSB(), device.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(entityViewUpdateMsg.getEntityType().name(), device.getId().getEntityType().name()); - } - - @Test - public void testCustomerAndNewUser() throws Exception { - // 1 - edgeImitator.expectMessageAmount(1); - Customer customer = new Customer(); - customer.setTitle("Edge Customer 1"); - Customer savedCustomer = doPost("/api/customer", customer, Customer.class); doPost("/api/customer/" + savedCustomer.getUuidId() + "/edge/" + edge.getUuidId(), Edge.class); Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); - CustomerUpdateMsg customerUpdateMsg = (CustomerUpdateMsg) latestMessage; + Optional edgeConfigurationOpt = edgeImitator.findMessageByType(EdgeConfiguration.class); + Assert.assertTrue(edgeConfigurationOpt.isPresent()); + EdgeConfiguration edgeConfiguration = edgeConfigurationOpt.get(); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), edgeConfiguration.getCustomerIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), edgeConfiguration.getCustomerIdLSB()); + Optional customerUpdateOpt = edgeImitator.findMessageByType(CustomerUpdateMsg.class); + Assert.assertTrue(customerUpdateOpt.isPresent()); + CustomerUpdateMsg customerUpdateMsg = customerUpdateOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); - Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); - Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(customerUpdateMsg.getTitle(), savedCustomer.getTitle()); - testAutoGeneratedCodeByProtobuf(customerUpdateMsg); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), customerUpdateMsg.getIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), customerUpdateMsg.getIdLSB()); + Assert.assertEquals(savedCustomer.getTitle(), customerUpdateMsg.getTitle()); - // 2 - edgeImitator.expectMessageAmount(1); - User customerUser = new User(); - customerUser.setAuthority(Authority.CUSTOMER_USER); - customerUser.setTenantId(savedTenant.getId()); - customerUser.setCustomerId(savedCustomer.getId()); - customerUser.setEmail("customerUser@thingsboard.org"); - customerUser.setFirstName("John"); - customerUser.setLastName("Edwards"); - User savedUser = doPost("/api/user", customerUser, User.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof UserUpdateMsg); - UserUpdateMsg userUpdateMsg = (UserUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); - Assert.assertEquals(userUpdateMsg.getIdMSB(), savedUser.getUuidId().getMostSignificantBits()); - Assert.assertEquals(userUpdateMsg.getIdLSB(), savedUser.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(userUpdateMsg.getEmail(), savedUser.getEmail()); - testAutoGeneratedCodeByProtobuf(userUpdateMsg); - - // 3 - edgeImitator.expectMessageAmount(1); + // unassign edge from customer + edgeImitator.expectMessageAmount(2); doDelete("/api/customer/edge/" + edge.getUuidId(), Edge.class); Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof CustomerUpdateMsg); - customerUpdateMsg = (CustomerUpdateMsg) latestMessage; + edgeConfigurationOpt = edgeImitator.findMessageByType(EdgeConfiguration.class); + Assert.assertTrue(edgeConfigurationOpt.isPresent()); + edgeConfiguration = edgeConfigurationOpt.get(); + Assert.assertEquals( + new CustomerId(EntityId.NULL_UUID), + new CustomerId(new UUID(edgeConfiguration.getCustomerIdMSB(), edgeConfiguration.getCustomerIdLSB()))); + customerUpdateOpt = edgeImitator.findMessageByType(CustomerUpdateMsg.class); + Assert.assertTrue(customerUpdateOpt.isPresent()); + customerUpdateMsg = customerUpdateOpt.get(); Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, customerUpdateMsg.getMsgType()); - Assert.assertEquals(customerUpdateMsg.getIdMSB(), savedCustomer.getUuidId().getMostSignificantBits()); - Assert.assertEquals(customerUpdateMsg.getIdLSB(), savedCustomer.getUuidId().getLeastSignificantBits()); - - edgeImitator.expectMessageAmount(1); - doDelete("/api/customer/" + savedCustomer.getUuidId()) - .andExpect(status().isOk()); - Assert.assertFalse(edgeImitator.waitForMessages(1)); - } - - @Test - public void testWidgetsBundleAndWidgetType() throws Exception { - // 1 - edgeImitator.expectMessageAmount(1); - WidgetsBundle widgetsBundle = new WidgetsBundle(); - widgetsBundle.setTitle("Test Widget Bundle"); - WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); - WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType()); - Assert.assertEquals(widgetsBundleUpdateMsg.getIdMSB(), savedWidgetsBundle.getUuidId().getMostSignificantBits()); - Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(widgetsBundleUpdateMsg.getAlias(), savedWidgetsBundle.getAlias()); - Assert.assertEquals(widgetsBundleUpdateMsg.getTitle(), savedWidgetsBundle.getTitle()); - testAutoGeneratedCodeByProtobuf(widgetsBundleUpdateMsg); - - // 2 - edgeImitator.expectMessageAmount(1); - WidgetType widgetType = new WidgetType(); - widgetType.setName("Test Widget Type"); - widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); - ObjectNode descriptor = mapper.createObjectNode(); - descriptor.put("key", "value"); - widgetType.setDescriptor(descriptor); - WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); - WidgetTypeUpdateMsg widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType()); - Assert.assertEquals(widgetTypeUpdateMsg.getIdMSB(), savedWidgetType.getUuidId().getMostSignificantBits()); - Assert.assertEquals(widgetTypeUpdateMsg.getIdLSB(), savedWidgetType.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(widgetTypeUpdateMsg.getAlias(), savedWidgetType.getAlias()); - Assert.assertEquals(widgetTypeUpdateMsg.getName(), savedWidgetType.getName()); - Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor()); - - // 3 - edgeImitator.expectMessageAmount(1); - doDelete("/api/widgetType/" + savedWidgetType.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); - widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType()); - Assert.assertEquals(widgetTypeUpdateMsg.getIdMSB(), savedWidgetType.getUuidId().getMostSignificantBits()); - Assert.assertEquals(widgetTypeUpdateMsg.getIdLSB(), savedWidgetType.getUuidId().getLeastSignificantBits()); - - // 4 - edgeImitator.expectMessageAmount(1); - doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); - widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType()); - Assert.assertEquals(widgetsBundleUpdateMsg.getIdMSB(), savedWidgetsBundle.getUuidId().getMostSignificantBits()); - Assert.assertEquals(widgetsBundleUpdateMsg.getIdLSB(), savedWidgetsBundle.getUuidId().getLeastSignificantBits()); - } - - @Test - public void testTimeseries() throws Exception { - edgeImitator.expectMessageAmount(1); - Device device = findDeviceByName("Edge Device 1"); - String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; - JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); - EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); - edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityDataProto); - EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; - Assert.assertEquals(latestEntityDataMsg.getEntityIdMSB(), device.getUuidId().getMostSignificantBits()); - Assert.assertEquals(latestEntityDataMsg.getEntityIdLSB(), device.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(latestEntityDataMsg.getEntityType(), device.getId().getEntityType().name()); - Assert.assertTrue(latestEntityDataMsg.hasPostTelemetryMsg()); - - TransportProtos.PostTelemetryMsg postTelemetryMsg = latestEntityDataMsg.getPostTelemetryMsg(); - Assert.assertEquals(1, postTelemetryMsg.getTsKvListCount()); - TransportProtos.TsKvListProto tsKvListProto = postTelemetryMsg.getTsKvList(0); - Assert.assertEquals(timeseriesEntityData.get("ts").asLong(), tsKvListProto.getTs()); - Assert.assertEquals(1, tsKvListProto.getKvCount()); - TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKv(0); - Assert.assertEquals("temperature", keyValueProto.getKey()); - Assert.assertEquals(25, keyValueProto.getLongV()); - } - - @Test - public void testAttributes() throws Exception { - Device device = findDeviceByName("Edge Device 1"); - - testAttributesUpdatedMsg(device); - testPostAttributesMsg(device); - testAttributesDeleteMsg(device); - } - - private void testAttributesUpdatedMsg(Device device) throws Exception { - String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}"; - JsonNode attributesEntityData = mapper.readTree(attributesData); - EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData); - edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent1).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityDataProto); - EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; - Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); - Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); - Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); - Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope()); - Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg()); - - TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg(); - Assert.assertEquals(1, attributesUpdatedMsg.getKvCount()); - TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0); - Assert.assertEquals("key1", keyValueProto.getKey()); - Assert.assertEquals("value1", keyValueProto.getStringV()); - } - - private void testPostAttributesMsg(Device device) throws Exception { - String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}"; - JsonNode postAttributesEntityData = mapper.readTree(postAttributesData); - EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData); - edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityDataProto); - EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; - Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); - Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); - Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); - Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope()); - Assert.assertTrue(latestEntityDataMsg.hasPostAttributesMsg()); - - TransportProtos.PostAttributeMsg postAttributesMsg = latestEntityDataMsg.getPostAttributesMsg(); - Assert.assertEquals(1, postAttributesMsg.getKvCount()); - TransportProtos.KeyValueProto keyValueProto = postAttributesMsg.getKv(0); - Assert.assertEquals("key2", keyValueProto.getKey()); - Assert.assertEquals("value2", keyValueProto.getStringV()); - } - - private void testAttributesDeleteMsg(Device device) throws Exception { - String deleteAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"keys\":[\"key1\",\"key2\"]}"; - JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData); - EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData); - edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityDataProto); - EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; - Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); - Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); - Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); - - Assert.assertTrue(latestEntityDataMsg.hasAttributeDeleteMsg()); - - AttributeDeleteMsg attributeDeleteMsg = latestEntityDataMsg.getAttributeDeleteMsg(); - Assert.assertEquals(attributeDeleteMsg.getScope(), deleteAttributesEntityData.get("scope").asText()); - - Assert.assertEquals(2, attributeDeleteMsg.getAttributeNamesCount()); - Assert.assertEquals("key1", attributeDeleteMsg.getAttributeNames(0)); - Assert.assertEquals("key2", attributeDeleteMsg.getAttributeNames(1)); - } - - @Test - public void testRpcCall() throws Exception { - Device device = findDeviceByName("Edge Device 1"); - - ObjectNode body = mapper.createObjectNode(); - body.put("requestId", new Random().nextInt()); - body.put("requestUUID", Uuids.timeBased().toString()); - body.put("oneway", false); - body.put("expirationTime", System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10)); - body.put("method", "test_method"); - body.put("params", "{\"param1\":\"value1\"}"); - - EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.RPC_CALL, device.getId().getId(), EdgeEventType.DEVICE, body); - edgeImitator.expectMessageAmount(1); - edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceRpcCallMsg); - DeviceRpcCallMsg latestDeviceRpcCallMsg = (DeviceRpcCallMsg) latestMessage; - Assert.assertEquals("test_method", latestDeviceRpcCallMsg.getRequestMsg().getMethod()); - } - - @Test - public void testTimeseriesWithFailures() throws Exception { - int numberOfTimeseriesToSend = 1000; - - edgeImitator.setRandomFailuresOnTimeseriesDownlink(true); - // imitator will generate failure in 5% of cases - edgeImitator.setFailureProbability(5.0); - - edgeImitator.expectMessageAmount(numberOfTimeseriesToSend); - Device device = findDeviceByName("Edge Device 1"); - for (int idx = 1; idx <= numberOfTimeseriesToSend; idx++) { - String timeseriesData = "{\"data\":{\"idx\":" + idx + "},\"ts\":" + System.currentTimeMillis() + "}"; - JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); - EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, - device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); - edgeEventService.saveAsync(edgeEvent).get(); - clusterService.onEdgeEventUpdate(tenantId, edge.getId()); - } - - Assert.assertTrue(edgeImitator.waitForMessages(120)); - - List allTelemetryMsgs = edgeImitator.findAllMessagesByType(EntityDataProto.class); - Assert.assertEquals(numberOfTimeseriesToSend, allTelemetryMsgs.size()); - - for (int idx = 1; idx <= numberOfTimeseriesToSend; idx++) { - Assert.assertTrue(isIdxExistsInTheDownlinkList(idx, allTelemetryMsgs)); - } - - edgeImitator.setRandomFailuresOnTimeseriesDownlink(false); - } - - private boolean isIdxExistsInTheDownlinkList(int idx, List allTelemetryMsgs) { - for (EntityDataProto proto : allTelemetryMsgs) { - TransportProtos.PostTelemetryMsg postTelemetryMsg = proto.getPostTelemetryMsg(); - Assert.assertEquals(1, postTelemetryMsg.getTsKvListCount()); - TransportProtos.TsKvListProto tsKvListProto = postTelemetryMsg.getTsKvList(0); - Assert.assertEquals(1, tsKvListProto.getKvCount()); - TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKv(0); - Assert.assertEquals("idx", keyValueProto.getKey()); - if (keyValueProto.getLongV() == idx) { - return true; - } - } - return false; - } - - @Test - public void testSendDeviceToCloud() throws Exception { - UUID uuid = Uuids.timeBased(); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); - deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); - deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); - deviceUpdateMsgBuilder.setName("Edge Device 2"); - deviceUpdateMsgBuilder.setType("test"); - deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); - testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder); - uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg); - DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage; - Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB()); - Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); - - UUID newDeviceId = new UUID(latestDeviceCredentialsRequestMsg.getDeviceIdMSB(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); - - Device device = doGet("/api/device/" + newDeviceId, Device.class); - Assert.assertNotNull(device); - Assert.assertEquals("Edge Device 2", device.getName()); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), customerUpdateMsg.getIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), customerUpdateMsg.getIdLSB()); } - - @Test - public void testSendDeviceToCloudWithNameThatAlreadyExistsOnCloud() throws Exception { - String deviceOnCloudName = StringUtils.randomAlphanumeric(15); - Device deviceOnCloud = saveDevice(deviceOnCloudName, "Default"); - - UUID uuid = Uuids.timeBased(); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - DeviceUpdateMsg.Builder deviceUpdateMsgBuilder = DeviceUpdateMsg.newBuilder(); - deviceUpdateMsgBuilder.setIdMSB(uuid.getMostSignificantBits()); - deviceUpdateMsgBuilder.setIdLSB(uuid.getLeastSignificantBits()); - deviceUpdateMsgBuilder.setName(deviceOnCloudName); - deviceUpdateMsgBuilder.setType("test"); - deviceUpdateMsgBuilder.setMsgType(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE); - testAutoGeneratedCodeByProtobuf(deviceUpdateMsgBuilder); - uplinkMsgBuilder.addDeviceUpdateMsg(deviceUpdateMsgBuilder.build()); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(2); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getMessageFromTail(2); - Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); - DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage; - Assert.assertNotEquals(deviceOnCloudName, latestDeviceUpdateMsg.getName()); - Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName()); - - UUID newDeviceId = new UUID(latestDeviceUpdateMsg.getIdMSB(), latestDeviceUpdateMsg.getIdLSB()); - - Assert.assertNotEquals(deviceOnCloud.getId().getId(), newDeviceId); - - Device device = doGet("/api/device/" + newDeviceId, Device.class); - Assert.assertNotNull(device); - Assert.assertNotEquals(deviceOnCloudName, device.getName()); - - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg); - DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage; - Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB()); - Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); - - newDeviceId = new UUID(latestDeviceCredentialsRequestMsg.getDeviceIdMSB(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); - - device = doGet("/api/device/" + newDeviceId, Device.class); - Assert.assertNotNull(device); - Assert.assertNotEquals(deviceOnCloudName, device.getName()); - } - - @Test - public void testSendRelationRequestToCloud() throws Exception { - Device device = findDeviceByName("Edge Device 1"); - Asset asset = findAssetByName("Edge Asset 1"); - - EntityRelation relation = new EntityRelation(); - relation.setType("test"); - relation.setFrom(device.getId()); - relation.setTo(asset.getId()); - relation.setTypeGroup(RelationTypeGroup.COMMON); - - edgeImitator.expectMessageAmount(1); - doPost("/api/relation", relation); - Assert.assertTrue(edgeImitator.waitForMessages()); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - RelationRequestMsg.Builder relationRequestMsgBuilder = RelationRequestMsg.newBuilder(); - relationRequestMsgBuilder.setEntityIdMSB(device.getId().getId().getMostSignificantBits()); - relationRequestMsgBuilder.setEntityIdLSB(device.getId().getId().getLeastSignificantBits()); - relationRequestMsgBuilder.setEntityType(device.getId().getEntityType().name()); - testAutoGeneratedCodeByProtobuf(relationRequestMsgBuilder); - - uplinkMsgBuilder.addRelationRequestMsg(relationRequestMsgBuilder.build()); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); - RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, relationUpdateMsg.getMsgType()); - Assert.assertEquals(relation.getType(), relationUpdateMsg.getType()); - - UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); - EntityId fromEntityId = EntityIdFactory.getByTypeAndUuid(relationUpdateMsg.getFromEntityType(), fromUUID); - Assert.assertEquals(relation.getFrom(), fromEntityId); - - UUID toUUID = new UUID(relationUpdateMsg.getToIdMSB(), relationUpdateMsg.getToIdLSB()); - EntityId toEntityId = EntityIdFactory.getByTypeAndUuid(relationUpdateMsg.getToEntityType(), toUUID); - Assert.assertEquals(relation.getTo(), toEntityId); - - Assert.assertEquals(relation.getTypeGroup().name(), relationUpdateMsg.getTypeGroup()); - } - - @Test - public void testSendAlarmToCloud() throws Exception { - Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - AlarmUpdateMsg.Builder alarmUpdateMgBuilder = AlarmUpdateMsg.newBuilder(); - alarmUpdateMgBuilder.setName("alarm from edge"); - alarmUpdateMgBuilder.setStatus(AlarmStatus.ACTIVE_UNACK.name()); - alarmUpdateMgBuilder.setSeverity(AlarmSeverity.CRITICAL.name()); - alarmUpdateMgBuilder.setOriginatorName(device.getName()); - alarmUpdateMgBuilder.setOriginatorType(EntityType.DEVICE.name()); - testAutoGeneratedCodeByProtobuf(alarmUpdateMgBuilder); - uplinkMsgBuilder.addAlarmUpdateMsg(alarmUpdateMgBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - - - List alarms = doGetTypedWithPageLink("/api/alarm/{entityType}/{entityId}?", - new TypeReference>() {}, - new PageLink(100), device.getId().getEntityType().name(), device.getUuidId()) - .getData(); - Optional foundAlarm = alarms.stream().filter(alarm -> alarm.getType().equals("alarm from edge")).findAny(); - Assert.assertTrue(foundAlarm.isPresent()); - AlarmInfo alarmInfo = foundAlarm.get(); - Assert.assertEquals(device.getId(), alarmInfo.getOriginator()); - Assert.assertEquals(AlarmStatus.ACTIVE_UNACK, alarmInfo.getStatus()); - Assert.assertEquals(AlarmSeverity.CRITICAL, alarmInfo.getSeverity()); - } - - @Test - public void testSendTelemetryToCloud() throws Exception { - Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); - - edgeImitator.expectResponsesAmount(2); - - JsonObject data = new JsonObject(); - String timeseriesKey = "key"; - String timeseriesValue = "25"; - data.addProperty(timeseriesKey, timeseriesValue); - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - EntityDataProto.Builder entityDataBuilder = EntityDataProto.newBuilder(); - entityDataBuilder.setPostTelemetryMsg(JsonConverter.convertToTelemetryProto(data, System.currentTimeMillis())); - entityDataBuilder.setEntityType(device.getId().getEntityType().name()); - entityDataBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits()); - entityDataBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(entityDataBuilder); - uplinkMsgBuilder.addEntityData(entityDataBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - - JsonObject attributesData = new JsonObject(); - String attributesKey = "test_attr"; - String attributesValue = "test_value"; - attributesData.addProperty(attributesKey, attributesValue); - UplinkMsg.Builder uplinkMsgBuilder2 = UplinkMsg.newBuilder(); - EntityDataProto.Builder entityDataBuilder2 = EntityDataProto.newBuilder(); - entityDataBuilder2.setEntityType(device.getId().getEntityType().name()); - entityDataBuilder2.setEntityIdMSB(device.getId().getId().getMostSignificantBits()); - entityDataBuilder2.setEntityIdLSB(device.getId().getId().getLeastSignificantBits()); - entityDataBuilder2.setAttributesUpdatedMsg(JsonConverter.convertToAttributesProto(attributesData)); - entityDataBuilder2.setPostAttributeScope(DataConstants.SERVER_SCOPE); - testAutoGeneratedCodeByProtobuf(entityDataBuilder2); - - uplinkMsgBuilder2.addEntityData(entityDataBuilder2.build()); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder2); - - edgeImitator.sendUplinkMsg(uplinkMsgBuilder2.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - - Awaitility.await() - .atMost(2, TimeUnit.SECONDS) - .until(() -> loadDeviceTimeseries(device, timeseriesKey).containsKey(timeseriesKey)); - - Map>> timeseries = loadDeviceTimeseries(device, timeseriesKey); - Assert.assertTrue(timeseries.containsKey(timeseriesKey)); - Assert.assertEquals(1, timeseries.get(timeseriesKey).size()); - Assert.assertEquals(timeseriesValue, timeseries.get(timeseriesKey).get(0).get("value")); - - String attributeValuesUrl = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/values/attributes/" + DataConstants.SERVER_SCOPE; - List> attributes = doGetAsyncTyped(attributeValuesUrl, new TypeReference<>() {}); - - Assert.assertEquals(3, attributes.size()); - - Optional> activeAttributeOpt = getAttributeByKey("active", attributes); - Assert.assertTrue(activeAttributeOpt.isPresent()); - Map activeAttribute = activeAttributeOpt.get(); - Assert.assertEquals("true", activeAttribute.get("value")); - - Optional> customAttributeOpt = getAttributeByKey(attributesKey, attributes); - Assert.assertTrue(customAttributeOpt.isPresent()); - Map customAttribute = customAttributeOpt.get(); - Assert.assertEquals(attributesValue, customAttribute.get("value")); - - doDelete("/api/plugins/telemetry/DEVICE/" + device.getId().getId() + "/SERVER_SCOPE?keys=" + attributesKey, String.class); - } - - private Optional> getAttributeByKey(String key, List> attributes) { - return attributes.stream().filter(kv -> kv.get("key").equals(key)).findFirst(); - } - - private Map>> loadDeviceTimeseries(Device device, String timeseriesKey) throws Exception { - return doGetAsyncTyped("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/values/timeseries?keys=" + timeseriesKey, - new TypeReference<>() {}); - } - - @Test - public void testSendRelationToCloud() throws Exception { - Device device1 = saveDeviceOnCloudAndVerifyDeliveryToEdge(); - Device device2 = saveDeviceOnCloudAndVerifyDeliveryToEdge(); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - RelationUpdateMsg.Builder relationUpdateMsgBuilder = RelationUpdateMsg.newBuilder(); - relationUpdateMsgBuilder.setType("test"); - relationUpdateMsgBuilder.setTypeGroup(RelationTypeGroup.COMMON.name()); - relationUpdateMsgBuilder.setToIdMSB(device1.getId().getId().getMostSignificantBits()); - relationUpdateMsgBuilder.setToIdLSB(device1.getId().getId().getLeastSignificantBits()); - relationUpdateMsgBuilder.setToEntityType(device1.getId().getEntityType().name()); - relationUpdateMsgBuilder.setFromIdMSB(device2.getId().getId().getMostSignificantBits()); - relationUpdateMsgBuilder.setFromIdLSB(device2.getId().getId().getLeastSignificantBits()); - relationUpdateMsgBuilder.setFromEntityType(device2.getId().getEntityType().name()); - relationUpdateMsgBuilder.setAdditionalInfo("{}"); - testAutoGeneratedCodeByProtobuf(relationUpdateMsgBuilder); - uplinkMsgBuilder.addRelationUpdateMsg(relationUpdateMsgBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - - EntityRelation relation = doGet("/api/relation?" + - "&fromId=" + device2.getUuidId() + - "&fromType=" + device2.getId().getEntityType().name() + - "&relationType=" + "test" + - "&relationTypeGroup=" + RelationTypeGroup.COMMON.name() + - "&toId=" + device1.getUuidId() + - "&toType=" + device1.getId().getEntityType().name(), EntityRelation.class); - Assert.assertNotNull(relation); - } - - @Test - public void testSendDeleteDeviceOnEdgeToCloud() throws Exception { - Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); - UplinkMsg.Builder upLinkMsgBuilder = UplinkMsg.newBuilder(); - DeviceUpdateMsg.Builder deviceDeleteMsgBuilder = DeviceUpdateMsg.newBuilder(); - deviceDeleteMsgBuilder.setMsgType(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE); - deviceDeleteMsgBuilder.setIdMSB(device.getId().getId().getMostSignificantBits()); - deviceDeleteMsgBuilder.setIdLSB(device.getId().getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(deviceDeleteMsgBuilder); - - upLinkMsgBuilder.addDeviceUpdateMsg(deviceDeleteMsgBuilder.build()); - testAutoGeneratedCodeByProtobuf(upLinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.sendUplinkMsg(upLinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - device = doGet("/api/device/" + device.getUuidId(), Device.class); - Assert.assertNotNull(device); - List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", - new TypeReference>() { - }, new PageLink(100)).getData(); - Assert.assertFalse(edgeDevices.contains(device)); - } - - @Test - public void testSendRuleChainMetadataRequestToCloud() throws Exception { - RuleChainId edgeRootRuleChainId = edge.getRootRuleChainId(); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - RuleChainMetadataRequestMsg.Builder ruleChainMetadataRequestMsgBuilder = RuleChainMetadataRequestMsg.newBuilder(); - ruleChainMetadataRequestMsgBuilder.setRuleChainIdMSB(edgeRootRuleChainId.getId().getMostSignificantBits()); - ruleChainMetadataRequestMsgBuilder.setRuleChainIdLSB(edgeRootRuleChainId.getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(ruleChainMetadataRequestMsgBuilder); - uplinkMsgBuilder.addRuleChainMetadataRequestMsg(ruleChainMetadataRequestMsgBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); - RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = (RuleChainMetadataUpdateMsg) latestMessage; - Assert.assertEquals(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), edgeRootRuleChainId.getId().getMostSignificantBits()); - Assert.assertEquals(ruleChainMetadataUpdateMsg.getRuleChainIdLSB(), edgeRootRuleChainId.getId().getLeastSignificantBits()); - - testAutoGeneratedCodeByProtobuf(ruleChainMetadataUpdateMsg); - } - - @Test - public void testSendUserCredentialsRequestToCloud() throws Exception { - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder(); - userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits()); - userCredentialsRequestMsgBuilder.setUserIdLSB(tenantAdmin.getId().getId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(userCredentialsRequestMsgBuilder); - uplinkMsgBuilder.addUserCredentialsRequestMsg(userCredentialsRequestMsgBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); - UserCredentialsUpdateMsg userCredentialsUpdateMsg = (UserCredentialsUpdateMsg) latestMessage; - Assert.assertEquals(userCredentialsUpdateMsg.getUserIdMSB(), tenantAdmin.getId().getId().getMostSignificantBits()); - Assert.assertEquals(userCredentialsUpdateMsg.getUserIdLSB(), tenantAdmin.getId().getId().getLeastSignificantBits()); - - testAutoGeneratedCodeByProtobuf(userCredentialsUpdateMsg); - } - - @Test - public void testSendDeviceCredentialsRequestToCloud() throws Exception { - Device device = findDeviceByName("Edge Device 1"); - - DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getUuidId() + "/credentials", DeviceCredentials.class); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - DeviceCredentialsRequestMsg.Builder deviceCredentialsRequestMsgBuilder = DeviceCredentialsRequestMsg.newBuilder(); - deviceCredentialsRequestMsgBuilder.setDeviceIdMSB(device.getUuidId().getMostSignificantBits()); - deviceCredentialsRequestMsgBuilder.setDeviceIdLSB(device.getUuidId().getLeastSignificantBits()); - testAutoGeneratedCodeByProtobuf(deviceCredentialsRequestMsgBuilder); - uplinkMsgBuilder.addDeviceCredentialsRequestMsg(deviceCredentialsRequestMsgBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceCredentialsUpdateMsg); - DeviceCredentialsUpdateMsg deviceCredentialsUpdateMsg = (DeviceCredentialsUpdateMsg) latestMessage; - Assert.assertEquals(deviceCredentialsUpdateMsg.getDeviceIdMSB(), device.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceCredentialsUpdateMsg.getDeviceIdLSB(), device.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(deviceCredentialsUpdateMsg.getCredentialsType(), deviceCredentials.getCredentialsType().name()); - Assert.assertEquals(deviceCredentialsUpdateMsg.getCredentialsId(), deviceCredentials.getCredentialsId()); - } - - @Test - public void testSendDeviceRpcResponseToCloud() throws Exception { - Device device = findDeviceByName("Edge Device 1"); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - DeviceRpcCallMsg.Builder deviceRpcCallResponseBuilder = DeviceRpcCallMsg.newBuilder(); - deviceRpcCallResponseBuilder.setDeviceIdMSB(device.getUuidId().getMostSignificantBits()); - deviceRpcCallResponseBuilder.setDeviceIdLSB(device.getUuidId().getLeastSignificantBits()); - deviceRpcCallResponseBuilder.setOneway(true); - deviceRpcCallResponseBuilder.setRequestId(0); - deviceRpcCallResponseBuilder.setExpirationTime(System.currentTimeMillis() + TimeUnit.SECONDS.toMillis(10)); - RpcResponseMsg.Builder responseBuilder = - RpcResponseMsg.newBuilder().setResponse("{}"); - testAutoGeneratedCodeByProtobuf(responseBuilder); - - deviceRpcCallResponseBuilder.setResponseMsg(responseBuilder.build()); - testAutoGeneratedCodeByProtobuf(deviceRpcCallResponseBuilder); - - uplinkMsgBuilder.addDeviceRpcCallMsg(deviceRpcCallResponseBuilder.build()); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - } - - @Test - public void testSendDeviceCredentialsUpdateToCloud() throws Exception { - Device device = findDeviceByName("Edge Device 1"); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - DeviceCredentialsUpdateMsg.Builder deviceCredentialsUpdateMsgBuilder = DeviceCredentialsUpdateMsg.newBuilder(); - deviceCredentialsUpdateMsgBuilder.setDeviceIdMSB(device.getUuidId().getMostSignificantBits()); - deviceCredentialsUpdateMsgBuilder.setDeviceIdLSB(device.getUuidId().getLeastSignificantBits()); - deviceCredentialsUpdateMsgBuilder.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN.name()); - deviceCredentialsUpdateMsgBuilder.setCredentialsId("NEW_TOKEN"); - testAutoGeneratedCodeByProtobuf(deviceCredentialsUpdateMsgBuilder); - uplinkMsgBuilder.addDeviceCredentialsUpdateMsg(deviceCredentialsUpdateMsgBuilder.build()); - - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - } - - @Test - public void testSendAttributesRequestToCloud() throws Exception { - Device device = findDeviceByName("Edge Device 1"); - sendAttributesRequestAndVerify(device, DataConstants.SERVER_SCOPE, "{\"key1\":\"value1\"}", - "key1", "value1"); - sendAttributesRequestAndVerify(device, DataConstants.SHARED_SCOPE, "{\"key2\":\"value2\"}", - "key2", "value2"); - } - - private void sendAttributesRequestAndVerify(Device device, String scope, String attributesDataStr, String expectedKey, - String expectedValue) throws Exception { - JsonNode attributesData = mapper.readTree(attributesDataStr); - - doPost("/api/plugins/telemetry/DEVICE/" + device.getUuidId() + "/attributes/" + scope, - attributesData); - - // Wait before device attributes saved to database before requesting them from edge - Awaitility.await() - .atMost(10, TimeUnit.SECONDS) - .until(() -> { - String urlTemplate = "/api/plugins/telemetry/DEVICE/" + device.getId() + "/keys/attributes/" + scope; - List actualKeys = doGetAsyncTyped(urlTemplate, new TypeReference<>() {}); - return actualKeys != null && !actualKeys.isEmpty() && actualKeys.contains(expectedKey); - }); - - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - AttributesRequestMsg.Builder attributesRequestMsgBuilder = AttributesRequestMsg.newBuilder(); - attributesRequestMsgBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits()); - attributesRequestMsgBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits()); - attributesRequestMsgBuilder.setEntityType(EntityType.DEVICE.name()); - attributesRequestMsgBuilder.setScope(scope); - testAutoGeneratedCodeByProtobuf(attributesRequestMsgBuilder); - uplinkMsgBuilder.addAttributesRequestMsg(attributesRequestMsgBuilder.build()); - testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); - - edgeImitator.expectResponsesAmount(1); - edgeImitator.expectMessageAmount(1); - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof EntityDataProto); - EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; - Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); - Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); - Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); - Assert.assertEquals(scope, latestEntityDataMsg.getPostAttributeScope()); - Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg()); - - TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg(); - - boolean found = false; - for (TransportProtos.KeyValueProto keyValueProto : attributesUpdatedMsg.getKvList()) { - if (keyValueProto.getKey().equals(expectedKey)) { - Assert.assertEquals(expectedKey, keyValueProto.getKey()); - Assert.assertEquals(expectedValue, keyValueProto.getStringV()); - found = true; - } - } - Assert.assertTrue("Expected key and value must be found", found); - } - - @Test - public void testOtaPackages_usesUrl() throws Exception { - // 1 - SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); - firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId()); - firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle("My firmware #1"); - firmwareInfo.setVersion("v1.0"); - firmwareInfo.setTag("My firmware #1 v1.0"); - firmwareInfo.setUsesUrl(true); - firmwareInfo.setUrl("http://localhost:8080/v1/package"); - firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); - - edgeImitator.expectMessageAmount(1); - OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); - OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); - Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); - Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); - Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB()); - Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB()); - Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType())); - Assert.assertEquals("My firmware #1", otaPackageUpdateMsg.getTitle()); - Assert.assertEquals("v1.0", otaPackageUpdateMsg.getVersion()); - Assert.assertEquals("My firmware #1 v1.0", otaPackageUpdateMsg.getTag()); - Assert.assertEquals("http://localhost:8080/v1/package", otaPackageUpdateMsg.getUrl()); - Assert.assertFalse(otaPackageUpdateMsg.hasData()); - Assert.assertFalse(otaPackageUpdateMsg.hasFileName()); - Assert.assertFalse(otaPackageUpdateMsg.hasContentType()); - Assert.assertFalse(otaPackageUpdateMsg.hasChecksumAlgorithm()); - Assert.assertFalse(otaPackageUpdateMsg.hasChecksum()); - Assert.assertFalse(otaPackageUpdateMsg.hasDataSize()); - - // 2 - edgeImitator.expectMessageAmount(1); - doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); - otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); - Assert.assertEquals(otaPackageUpdateMsg.getIdMSB(), savedFirmwareInfo.getUuidId().getMostSignificantBits()); - Assert.assertEquals(otaPackageUpdateMsg.getIdLSB(), savedFirmwareInfo.getUuidId().getLeastSignificantBits()); - } - - @Test - public void testOtaPackages_hasData() throws Exception { - // 1 - SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); - firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId()); - firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle("My firmware #2"); - firmwareInfo.setVersion("v2.0"); - firmwareInfo.setTag("My firmware #2 v2.0"); - firmwareInfo.setUsesUrl(false); - firmwareInfo.setHasData(false); - firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); - - edgeImitator.expectMessageAmount(1); - - OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); - MockMultipartFile testData = new MockMultipartFile("file", "firmware.bin", "image/png", ByteBuffer.wrap(new byte[]{1, 3, 5}).array()); - savedFirmwareInfo = saveData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksumAlgorithm={checksumAlgorithm}", testData, ChecksumAlgorithm.SHA256.name()); - - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); - OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); - Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); - Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); - Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB()); - Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB()); - Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType())); - Assert.assertEquals("My firmware #2", otaPackageUpdateMsg.getTitle()); - Assert.assertEquals("v2.0", otaPackageUpdateMsg.getVersion()); - Assert.assertEquals("My firmware #2 v2.0", otaPackageUpdateMsg.getTag()); - Assert.assertFalse(otaPackageUpdateMsg.hasUrl()); - Assert.assertEquals("firmware.bin", otaPackageUpdateMsg.getFileName()); - Assert.assertEquals("image/png", otaPackageUpdateMsg.getContentType()); - Assert.assertEquals(ChecksumAlgorithm.SHA256.name(), otaPackageUpdateMsg.getChecksumAlgorithm()); - Assert.assertEquals("62467691cf583d4fa78b18fafaf9801f505e0ef03baf0603fd4b0cd004cd1e75", otaPackageUpdateMsg.getChecksum()); - Assert.assertEquals(3L, otaPackageUpdateMsg.getDataSize()); - Assert.assertEquals(ByteString.copyFrom(new byte[]{1, 3, 5}), otaPackageUpdateMsg.getData()); - - // 2 - edgeImitator.expectMessageAmount(1); - doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); - otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); - Assert.assertEquals(otaPackageUpdateMsg.getIdMSB(), savedFirmwareInfo.getUuidId().getMostSignificantBits()); - Assert.assertEquals(otaPackageUpdateMsg.getIdLSB(), savedFirmwareInfo.getUuidId().getLeastSignificantBits()); - } - - @Test - public void testQueues() throws Exception { - loginSysAdmin(); - - // 1 - Queue queue = new Queue(); - queue.setName("EdgeMain"); - queue.setTopic("tb_rule_engine.EdgeMain"); - queue.setPollInterval(25); - queue.setPartitions(10); - queue.setConsumerPerPartition(false); - queue.setPackProcessingTimeout(2000); - SubmitStrategy submitStrategy = new SubmitStrategy(); - submitStrategy.setType(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR); - queue.setSubmitStrategy(submitStrategy); - ProcessingStrategy processingStrategy = new ProcessingStrategy(); - processingStrategy.setType(ProcessingStrategyType.RETRY_ALL); - processingStrategy.setRetries(3); - processingStrategy.setFailurePercentage(0.7); - processingStrategy.setPauseBetweenRetries(3); - processingStrategy.setMaxPauseBetweenRetries(5); - queue.setProcessingStrategy(processingStrategy); - - edgeImitator.expectMessageAmount(1); - Queue savedQueue = doPost("/api/queues?serviceType=" + ServiceType.TB_RULE_ENGINE.name(), queue, Queue.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); - QueueUpdateMsg queueUpdateMsg = (QueueUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); - Assert.assertEquals(savedQueue.getUuidId().getMostSignificantBits(), queueUpdateMsg.getIdMSB()); - Assert.assertEquals(savedQueue.getUuidId().getLeastSignificantBits(), queueUpdateMsg.getIdLSB()); - Assert.assertEquals(savedQueue.getTenantId().getId().getMostSignificantBits(), queueUpdateMsg.getTenantIdMSB()); - Assert.assertEquals(savedQueue.getTenantId().getId().getLeastSignificantBits(), queueUpdateMsg.getTenantIdLSB()); - Assert.assertEquals("EdgeMain", queueUpdateMsg.getName()); - Assert.assertEquals("tb_rule_engine.EdgeMain", queueUpdateMsg.getTopic()); - Assert.assertEquals(25, queueUpdateMsg.getPollInterval()); - Assert.assertEquals(10, queueUpdateMsg.getPartitions()); - Assert.assertFalse(queueUpdateMsg.getConsumerPerPartition()); - Assert.assertEquals(2000, queueUpdateMsg.getPackProcessingTimeout()); - Assert.assertEquals(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR.name(), queueUpdateMsg.getSubmitStrategy().getType()); - Assert.assertEquals(0, queueUpdateMsg.getSubmitStrategy().getBatchSize()); - Assert.assertEquals(ProcessingStrategyType.RETRY_ALL.name(), queueUpdateMsg.getProcessingStrategy().getType()); - Assert.assertEquals(3, queueUpdateMsg.getProcessingStrategy().getRetries()); - Assert.assertEquals(0.7, queueUpdateMsg.getProcessingStrategy().getFailurePercentage(), 1); - Assert.assertEquals(3, queueUpdateMsg.getProcessingStrategy().getPauseBetweenRetries()); - Assert.assertEquals(5, queueUpdateMsg.getProcessingStrategy().getMaxPauseBetweenRetries()); - - // 2 - edgeImitator.expectMessageAmount(1); - savedQueue.setPollInterval(50); - savedQueue = doPost("/api/queues?serviceType=" + ServiceType.TB_RULE_ENGINE.name(), savedQueue, Queue.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); - queueUpdateMsg = (QueueUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); - Assert.assertEquals(50, queueUpdateMsg.getPollInterval()); - - // 3 - edgeImitator.expectMessageAmount(1); - doDelete("/api/queues/" + savedQueue.getUuidId()) - .andExpect(status().isOk()); - Assert.assertTrue(edgeImitator.waitForMessages()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); - queueUpdateMsg = (QueueUpdateMsg) latestMessage; - Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); - Assert.assertEquals(queueUpdateMsg.getIdMSB(), savedQueue.getUuidId().getMostSignificantBits()); - Assert.assertEquals(queueUpdateMsg.getIdLSB(), savedQueue.getUuidId().getLeastSignificantBits()); - } - - @Test - public void sendUpdateSharedAttributeToCloudAndValidateDeviceSubscription() throws Exception { - Device device = saveDeviceOnCloudAndVerifyDeliveryToEdge(); - - DeviceCredentials deviceCredentials = doGet("/api/device/" + device.getUuidId() + "/credentials", DeviceCredentials.class); - - MqttTestClient client = new MqttTestClient(); - client.connectAndWait(deviceCredentials.getCredentialsId()); - MqttTestCallback onUpdateCallback = new MqttTestCallback(); - client.setCallback(onUpdateCallback); - client.subscribeAndWait("v1/devices/me/attributes", MqttQoS.AT_MOST_ONCE); - - edgeImitator.expectResponsesAmount(1); - - JsonObject attributesData = new JsonObject(); - String attrKey = "sharedAttrName"; - String attrValue = "sharedAttrValue"; - attributesData.addProperty(attrKey, attrValue); - UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); - EntityDataProto.Builder entityDataBuilder = EntityDataProto.newBuilder(); - entityDataBuilder.setEntityType(device.getId().getEntityType().name()); - entityDataBuilder.setEntityIdMSB(device.getId().getId().getMostSignificantBits()); - entityDataBuilder.setEntityIdLSB(device.getId().getId().getLeastSignificantBits()); - entityDataBuilder.setAttributesUpdatedMsg(JsonConverter.convertToAttributesProto(attributesData)); - entityDataBuilder.setPostAttributeScope(DataConstants.SHARED_SCOPE); - uplinkMsgBuilder.addEntityData(entityDataBuilder.build()); - - edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); - Assert.assertTrue(edgeImitator.waitForResponses()); - - Assert.assertTrue(onUpdateCallback.getSubscribeLatch().await(5, TimeUnit.SECONDS)); - - assertEquals(JacksonUtil.OBJECT_MAPPER.createObjectNode().put(attrKey, attrValue), - JacksonUtil.fromBytes(onUpdateCallback.getPayloadBytes())); - } - - // Utility methods - - private Device saveDeviceOnCloudAndVerifyDeliveryToEdge() throws Exception { - edgeImitator.expectMessageAmount(1); - OtaPackageInfo firmwareOtaPackageInfo = saveOtaPackageInfo(thermostatDeviceProfile.getId()); - Assert.assertTrue(edgeImitator.waitForMessages()); - - Device savedDevice = saveDevice(StringUtils.randomAlphanumeric(15), thermostatDeviceProfile.getName()); - savedDevice.setFirmwareId(firmwareOtaPackageInfo.getId()); - - DeviceData deviceData = new DeviceData(); - deviceData.setConfiguration(new DefaultDeviceConfiguration()); - MqttDeviceTransportConfiguration transportConfiguration = new MqttDeviceTransportConfiguration(); - transportConfiguration.getProperties().put("topic", "tb_rule_engine.thermostat"); - deviceData.setTransportConfiguration(transportConfiguration); - savedDevice.setDeviceData(deviceData); - - savedDevice = doPost("/api/device", savedDevice, Device.class); - - // wait until device UPDATED event is sent to edge notification service - // to avoid edge notification service to send device UPDATED event before ASSIGNED_TO_EDGE - Thread.sleep(500); - - edgeImitator.expectMessageAmount(1); - doPost("/api/edge/" + edge.getUuidId() - + "/device/" + savedDevice.getUuidId(), Device.class); - Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); - DeviceUpdateMsg deviceUpdateMsg = (DeviceUpdateMsg) latestMessage; - Assert.assertEquals(deviceUpdateMsg.getIdMSB(), savedDevice.getUuidId().getMostSignificantBits()); - Assert.assertEquals(deviceUpdateMsg.getIdLSB(), savedDevice.getUuidId().getLeastSignificantBits()); - Assert.assertEquals(deviceUpdateMsg.getName(), savedDevice.getName()); - Assert.assertEquals(deviceUpdateMsg.getType(), savedDevice.getType()); - Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getMostSignificantBits(), deviceUpdateMsg.getFirmwareIdMSB()); - Assert.assertEquals(firmwareOtaPackageInfo.getUuidId().getLeastSignificantBits(), deviceUpdateMsg.getFirmwareIdLSB()); - Optional deviceDataOpt = - dataDecodingEncodingService.decode(deviceUpdateMsg.getDeviceDataBytes().toByteArray()); - Assert.assertTrue(deviceDataOpt.isPresent()); - deviceData = deviceDataOpt.get(); - Assert.assertTrue(deviceData.getTransportConfiguration() instanceof MqttDeviceTransportConfiguration); - MqttDeviceTransportConfiguration mqttDeviceTransportConfiguration = - (MqttDeviceTransportConfiguration) deviceData.getTransportConfiguration(); - Assert.assertEquals("tb_rule_engine.thermostat", mqttDeviceTransportConfiguration.getProperties().get("topic")); - return savedDevice; - } - - private Device findDeviceByName(String deviceName) throws Exception { - List edgeDevices = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/devices?", - new TypeReference>() { - }, new PageLink(100)).getData(); - Optional foundDevice = edgeDevices.stream().filter(d -> d.getName().equals(deviceName)).findAny(); - Assert.assertTrue(foundDevice.isPresent()); - Device device = foundDevice.get(); - Assert.assertEquals(deviceName, device.getName()); - return device; - } - - private Asset findAssetByName(String assetName) throws Exception { - List edgeAssets = doGetTypedWithPageLink("/api/edge/" + edge.getUuidId() + "/assets?", - new TypeReference>() { - }, new PageLink(100)).getData(); - - Assert.assertEquals(1, edgeAssets.size()); - Asset asset = edgeAssets.get(0); - Assert.assertEquals(assetName, asset.getName()); - return asset; - } - - private Device saveDevice(String deviceName, String type) { - Device device = new Device(); - device.setName(deviceName); - device.setType(type); - return doPost("/api/device", device, Device.class); - } - - private Asset saveAsset(String assetName) throws Exception { - Asset asset = new Asset(); - asset.setName(assetName); - asset.setType("test"); - return doPost("/api/asset", asset, Asset.class); - } - - private OtaPackageInfo saveOtaPackageInfo(DeviceProfileId deviceProfileId) { - SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); - firmwareInfo.setDeviceProfileId(deviceProfileId); - firmwareInfo.setType(FIRMWARE); - firmwareInfo.setTitle("Firmware Edge " + StringUtils.randomAlphanumeric(3)); - firmwareInfo.setVersion("v1.0"); - firmwareInfo.setTag("My firmware #1 v1.0"); - firmwareInfo.setUsesUrl(true); - firmwareInfo.setUrl("http://localhost:8080/v1/package"); - firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); - firmwareInfo.setChecksumAlgorithm(ChecksumAlgorithm.SHA256); - return doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); - } - - private EdgeEvent constructEdgeEvent(TenantId tenantId, EdgeId edgeId, EdgeEventActionType edgeEventAction, UUID entityId, EdgeEventType edgeEventType, JsonNode entityBody) { - EdgeEvent edgeEvent = new EdgeEvent(); - edgeEvent.setEdgeId(edgeId); - edgeEvent.setTenantId(tenantId); - edgeEvent.setAction(edgeEventAction); - edgeEvent.setEntityId(entityId); - edgeEvent.setType(edgeEventType); - edgeEvent.setBody(entityBody); - return edgeEvent; - } - - private void testAutoGeneratedCodeByProtobuf(MessageLite.Builder builder) throws InvalidProtocolBufferException { - MessageLite source = builder.build(); - - testAutoGeneratedCodeByProtobuf(source); - - MessageLite target = source.getParserForType().parseFrom(source.toByteArray()); - builder.clear().mergeFrom(target); - } - - private void testAutoGeneratedCodeByProtobuf(MessageLite source) throws InvalidProtocolBufferException { - MessageLite target = source.getParserForType().parseFrom(source.toByteArray()); - Assert.assertEquals(source, target); - Assert.assertEquals(source.hashCode(), target.hashCode()); - } - - private OtaPackageInfo saveData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { - MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); - postRequest.file(content); - setJwtToken(postRequest); - return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); - } - } diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseEntityViewEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseEntityViewEdgeTest.java new file mode 100644 index 0000000000..1c0e7887de --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseEntityViewEdgeTest.java @@ -0,0 +1,176 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.gen.edge.v1.EntityViewUpdateMsg; +import org.thingsboard.server.gen.edge.v1.EntityViewsRequestMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UplinkMsg; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseEntityViewEdgeTest extends AbstractEdgeTest { + + @Test + public void testEntityViews() throws Exception { + // create entity view and assign to edge + edgeImitator.expectMessageAmount(1); + Device device = findDeviceByName("Edge Device 1"); + EntityView entityView = new EntityView(); + entityView.setName("Edge EntityView 1"); + entityView.setType("test"); + entityView.setEntityId(device.getId()); + EntityView savedEntityView = doPost("/api/entityView", entityView, EntityView.class); + doPost("/api/edge/" + edge.getUuidId() + + "/entityView/" + savedEntityView.getUuidId(), EntityView.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + verifyEntityViewUpdateMsg(savedEntityView, device); + + // update entity view + edgeImitator.expectMessageAmount(1); + savedEntityView.setName("Edge EntityView 1 Updated"); + savedEntityView = doPost("/api/entityView", savedEntityView, EntityView.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); + EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); + Assert.assertEquals(savedEntityView.getName(), entityViewUpdateMsg.getName()); + + // request entity view(s) for device + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + EntityViewsRequestMsg.Builder entityViewsRequestBuilder = EntityViewsRequestMsg.newBuilder(); + entityViewsRequestBuilder.setEntityIdMSB(device.getUuidId().getMostSignificantBits()); + entityViewsRequestBuilder.setEntityIdLSB(device.getUuidId().getLeastSignificantBits()); + entityViewsRequestBuilder.setEntityType(device.getId().getEntityType().name()); + testAutoGeneratedCodeByProtobuf(entityViewsRequestBuilder); + uplinkMsgBuilder.addEntityViewsRequestMsg(entityViewsRequestBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + verifyEntityViewUpdateMsg(savedEntityView, device); + + // unassign entity view from edge + edgeImitator.expectMessageAmount(1); + doDelete("/api/edge/" + edge.getUuidId() + + "/entityView/" + savedEntityView.getUuidId(), EntityView.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); + entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); + Assert.assertEquals(entityViewUpdateMsg.getIdMSB(), savedEntityView.getUuidId().getMostSignificantBits()); + Assert.assertEquals(entityViewUpdateMsg.getIdLSB(), savedEntityView.getUuidId().getLeastSignificantBits()); + + // delete entity view - no messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/entityView/" + savedEntityView.getUuidId()) + .andExpect(status().isOk()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + + // create entity view #2 and assign to edge + edgeImitator.expectMessageAmount(1); + entityView = new EntityView(); + entityView.setName("Edge EntityView 2"); + entityView.setType("test"); + entityView.setEntityId(device.getId()); + savedEntityView = doPost("/api/entityView", entityView, EntityView.class); + doPost("/api/edge/" + edge.getUuidId() + + "/entityView/" + savedEntityView.getUuidId(), EntityView.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + verifyEntityViewUpdateMsg(savedEntityView, device); + + // assign entity view #2 to customer + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + edgeImitator.expectMessageAmount(2); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + edgeImitator.expectMessageAmount(1); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/entityView/" + savedEntityView.getUuidId(), EntityView.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); + entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomer.getUuidId().getMostSignificantBits(), entityViewUpdateMsg.getCustomerIdMSB()); + Assert.assertEquals(savedCustomer.getUuidId().getLeastSignificantBits(), entityViewUpdateMsg.getCustomerIdLSB()); + + // unassign entity view #2 from customer + edgeImitator.expectMessageAmount(1); + doDelete("/api/customer/entityView/" + savedEntityView.getUuidId(), EntityView.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); + entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); + Assert.assertEquals( + new CustomerId(EntityId.NULL_UUID), + new CustomerId(new UUID(entityViewUpdateMsg.getCustomerIdMSB(), entityViewUpdateMsg.getCustomerIdLSB()))); + + // delete entity view #2 - messages expected + edgeImitator.expectMessageAmount(1); + doDelete("/api/entityView/" + savedEntityView.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); + entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); + Assert.assertEquals(savedEntityView.getUuidId().getMostSignificantBits(), entityViewUpdateMsg.getIdMSB()); + Assert.assertEquals(savedEntityView.getUuidId().getLeastSignificantBits(), entityViewUpdateMsg.getIdLSB()); + + } + + private void verifyEntityViewUpdateMsg(EntityView entityView, Device device) throws InvalidProtocolBufferException { + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityViewUpdateMsg); + EntityViewUpdateMsg entityViewUpdateMsg = (EntityViewUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, entityViewUpdateMsg.getMsgType()); + Assert.assertEquals(entityView.getType(), entityViewUpdateMsg.getType()); + Assert.assertEquals(entityView.getName(), entityViewUpdateMsg.getName()); + Assert.assertEquals(entityView.getUuidId().getMostSignificantBits(), entityViewUpdateMsg.getIdMSB()); + Assert.assertEquals(entityView.getUuidId().getLeastSignificantBits(), entityViewUpdateMsg.getIdLSB()); + Assert.assertEquals(device.getUuidId().getMostSignificantBits(), entityViewUpdateMsg.getEntityIdMSB()); + Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), entityViewUpdateMsg.getEntityIdLSB()); + Assert.assertEquals(device.getId().getEntityType().name(), entityViewUpdateMsg.getEntityType().name()); + testAutoGeneratedCodeByProtobuf(entityViewUpdateMsg); + } + + + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseOtaPackageEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseOtaPackageEdgeTest.java new file mode 100644 index 0000000000..90a6a3afcd --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseOtaPackageEdgeTest.java @@ -0,0 +1,151 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import com.google.protobuf.ByteString; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.request.MockMultipartHttpServletRequestBuilder; +import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.OtaPackageInfo; +import org.thingsboard.server.common.data.SaveOtaPackageInfoRequest; +import org.thingsboard.server.common.data.ota.ChecksumAlgorithm; +import org.thingsboard.server.common.data.ota.OtaPackageType; +import org.thingsboard.server.gen.edge.v1.OtaPackageUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +import java.nio.ByteBuffer; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.thingsboard.server.common.data.ota.OtaPackageType.FIRMWARE; + +abstract public class BaseOtaPackageEdgeTest extends AbstractEdgeTest { + + @Test + public void testOtaPackages_usesUrl() throws Exception { + // create ota package + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); + firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("My firmware #1"); + firmwareInfo.setVersion("v1.0"); + firmwareInfo.setTag("My firmware #1 v1.0"); + firmwareInfo.setUsesUrl(true); + firmwareInfo.setUrl("http://localhost:8080/v1/package"); + firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + + edgeImitator.expectMessageAmount(1); + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB()); + Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType())); + Assert.assertEquals("My firmware #1", otaPackageUpdateMsg.getTitle()); + Assert.assertEquals("v1.0", otaPackageUpdateMsg.getVersion()); + Assert.assertEquals("My firmware #1 v1.0", otaPackageUpdateMsg.getTag()); + Assert.assertEquals("http://localhost:8080/v1/package", otaPackageUpdateMsg.getUrl()); + Assert.assertFalse(otaPackageUpdateMsg.hasData()); + Assert.assertFalse(otaPackageUpdateMsg.hasFileName()); + Assert.assertFalse(otaPackageUpdateMsg.hasContentType()); + Assert.assertFalse(otaPackageUpdateMsg.hasChecksumAlgorithm()); + Assert.assertFalse(otaPackageUpdateMsg.hasChecksum()); + Assert.assertFalse(otaPackageUpdateMsg.hasDataSize()); + + // delete ota package + edgeImitator.expectMessageAmount(1); + doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); + } + + @Test + public void testOtaPackages_hasData() throws Exception { + // create ota package + SaveOtaPackageInfoRequest firmwareInfo = new SaveOtaPackageInfoRequest(); + firmwareInfo.setDeviceProfileId(thermostatDeviceProfile.getId()); + firmwareInfo.setType(FIRMWARE); + firmwareInfo.setTitle("My firmware #2"); + firmwareInfo.setVersion("v2.0"); + firmwareInfo.setTag("My firmware #2 v2.0"); + firmwareInfo.setUsesUrl(false); + firmwareInfo.setHasData(false); + firmwareInfo.setAdditionalInfo(JacksonUtil.newObjectNode()); + + edgeImitator.expectMessageAmount(1); + + OtaPackageInfo savedFirmwareInfo = doPost("/api/otaPackage", firmwareInfo, OtaPackageInfo.class); + MockMultipartFile testData = new MockMultipartFile("file", "firmware.bin", "image/png", ByteBuffer.wrap(new byte[]{1, 3, 5}).array()); + savedFirmwareInfo = saveData("/api/otaPackage/" + savedFirmwareInfo.getId().getId().toString() + "?checksumAlgorithm={checksumAlgorithm}", testData, ChecksumAlgorithm.SHA256.name()); + + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + OtaPackageUpdateMsg otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdMSB()); + Assert.assertEquals(thermostatDeviceProfile.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getDeviceProfileIdLSB()); + Assert.assertEquals(FIRMWARE, OtaPackageType.valueOf(otaPackageUpdateMsg.getType())); + Assert.assertEquals("My firmware #2", otaPackageUpdateMsg.getTitle()); + Assert.assertEquals("v2.0", otaPackageUpdateMsg.getVersion()); + Assert.assertEquals("My firmware #2 v2.0", otaPackageUpdateMsg.getTag()); + Assert.assertFalse(otaPackageUpdateMsg.hasUrl()); + Assert.assertEquals("firmware.bin", otaPackageUpdateMsg.getFileName()); + Assert.assertEquals("image/png", otaPackageUpdateMsg.getContentType()); + Assert.assertEquals(ChecksumAlgorithm.SHA256.name(), otaPackageUpdateMsg.getChecksumAlgorithm()); + Assert.assertEquals("62467691cf583d4fa78b18fafaf9801f505e0ef03baf0603fd4b0cd004cd1e75", otaPackageUpdateMsg.getChecksum()); + Assert.assertEquals(3L, otaPackageUpdateMsg.getDataSize()); + Assert.assertEquals(ByteString.copyFrom(new byte[]{1, 3, 5}), otaPackageUpdateMsg.getData()); + + // delete ota package + edgeImitator.expectMessageAmount(1); + doDelete("/api/otaPackage/" + savedFirmwareInfo.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof OtaPackageUpdateMsg); + otaPackageUpdateMsg = (OtaPackageUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, otaPackageUpdateMsg.getMsgType()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getMostSignificantBits(), otaPackageUpdateMsg.getIdMSB()); + Assert.assertEquals(savedFirmwareInfo.getUuidId().getLeastSignificantBits(), otaPackageUpdateMsg.getIdLSB()); + } + + private OtaPackageInfo saveData(String urlTemplate, MockMultipartFile content, String... params) throws Exception { + MockMultipartHttpServletRequestBuilder postRequest = MockMvcRequestBuilders.multipart(urlTemplate, params); + postRequest.file(content); + setJwtToken(postRequest); + return readResponse(mockMvc.perform(postRequest).andExpect(status().isOk()), OtaPackageInfo.class); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseQueueEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseQueueEdgeTest.java new file mode 100644 index 0000000000..daa55b8fe7 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseQueueEdgeTest.java @@ -0,0 +1,107 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.queue.ProcessingStrategy; +import org.thingsboard.server.common.data.queue.ProcessingStrategyType; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.queue.SubmitStrategy; +import org.thingsboard.server.common.data.queue.SubmitStrategyType; +import org.thingsboard.server.common.msg.queue.ServiceType; +import org.thingsboard.server.gen.edge.v1.QueueUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseQueueEdgeTest extends AbstractEdgeTest { + + @Test + public void testQueues() throws Exception { + loginSysAdmin(); + + // create queue + Queue queue = new Queue(); + queue.setName("EdgeMain"); + queue.setTopic("tb_rule_engine.EdgeMain"); + queue.setPollInterval(25); + queue.setPartitions(10); + queue.setConsumerPerPartition(false); + queue.setPackProcessingTimeout(2000); + SubmitStrategy submitStrategy = new SubmitStrategy(); + submitStrategy.setType(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR); + queue.setSubmitStrategy(submitStrategy); + ProcessingStrategy processingStrategy = new ProcessingStrategy(); + processingStrategy.setType(ProcessingStrategyType.RETRY_ALL); + processingStrategy.setRetries(3); + processingStrategy.setFailurePercentage(0.7); + processingStrategy.setPauseBetweenRetries(3); + processingStrategy.setMaxPauseBetweenRetries(5); + queue.setProcessingStrategy(processingStrategy); + + edgeImitator.expectMessageAmount(1); + Queue savedQueue = doPost("/api/queues?serviceType=" + ServiceType.TB_RULE_ENGINE.name(), queue, Queue.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); + QueueUpdateMsg queueUpdateMsg = (QueueUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); + Assert.assertEquals(savedQueue.getUuidId().getMostSignificantBits(), queueUpdateMsg.getIdMSB()); + Assert.assertEquals(savedQueue.getUuidId().getLeastSignificantBits(), queueUpdateMsg.getIdLSB()); + Assert.assertEquals(savedQueue.getTenantId().getId().getMostSignificantBits(), queueUpdateMsg.getTenantIdMSB()); + Assert.assertEquals(savedQueue.getTenantId().getId().getLeastSignificantBits(), queueUpdateMsg.getTenantIdLSB()); + Assert.assertEquals("EdgeMain", queueUpdateMsg.getName()); + Assert.assertEquals("tb_rule_engine.EdgeMain", queueUpdateMsg.getTopic()); + Assert.assertEquals(25, queueUpdateMsg.getPollInterval()); + Assert.assertEquals(10, queueUpdateMsg.getPartitions()); + Assert.assertFalse(queueUpdateMsg.getConsumerPerPartition()); + Assert.assertEquals(2000, queueUpdateMsg.getPackProcessingTimeout()); + Assert.assertEquals(SubmitStrategyType.SEQUENTIAL_BY_ORIGINATOR.name(), queueUpdateMsg.getSubmitStrategy().getType()); + Assert.assertEquals(0, queueUpdateMsg.getSubmitStrategy().getBatchSize()); + Assert.assertEquals(ProcessingStrategyType.RETRY_ALL.name(), queueUpdateMsg.getProcessingStrategy().getType()); + Assert.assertEquals(3, queueUpdateMsg.getProcessingStrategy().getRetries()); + Assert.assertEquals(0.7, queueUpdateMsg.getProcessingStrategy().getFailurePercentage(), 1); + Assert.assertEquals(3, queueUpdateMsg.getProcessingStrategy().getPauseBetweenRetries()); + Assert.assertEquals(5, queueUpdateMsg.getProcessingStrategy().getMaxPauseBetweenRetries()); + + // update queue + edgeImitator.expectMessageAmount(1); + savedQueue.setPollInterval(50); + savedQueue = doPost("/api/queues?serviceType=" + ServiceType.TB_RULE_ENGINE.name(), savedQueue, Queue.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); + queueUpdateMsg = (QueueUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); + Assert.assertEquals(50, queueUpdateMsg.getPollInterval()); + + // delete queue + edgeImitator.expectMessageAmount(1); + doDelete("/api/queues/" + savedQueue.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof QueueUpdateMsg); + queueUpdateMsg = (QueueUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, queueUpdateMsg.getMsgType()); + Assert.assertEquals(savedQueue.getUuidId().getMostSignificantBits(), queueUpdateMsg.getIdMSB()); + Assert.assertEquals(savedQueue.getUuidId().getLeastSignificantBits(), queueUpdateMsg.getIdLSB()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseRelationEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseRelationEdgeTest.java new file mode 100644 index 0000000000..6a6474e5a3 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseRelationEdgeTest.java @@ -0,0 +1,172 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.gen.edge.v1.RelationRequestMsg; +import org.thingsboard.server.gen.edge.v1.RelationUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UplinkMsg; + +import java.util.UUID; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseRelationEdgeTest extends AbstractEdgeTest { + + + @Test + public void testRelations() throws Exception { + // create relation + Device device = findDeviceByName("Edge Device 1"); + Asset asset = findAssetByName("Edge Asset 1"); + EntityRelation relation = new EntityRelation(); + relation.setType("test"); + relation.setFrom(device.getId()); + relation.setTo(asset.getId()); + relation.setTypeGroup(RelationTypeGroup.COMMON); + edgeImitator.expectMessageAmount(1); + doPost("/api/relation", relation); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); + RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, relationUpdateMsg.getMsgType()); + Assert.assertEquals(relationUpdateMsg.getType(), relation.getType()); + Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getFromIdLSB(), relation.getFrom().getId().getLeastSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); + Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); + Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); + + // delete relation + edgeImitator.expectMessageAmount(1); + doDelete("/api/relation?" + + "fromId=" + relation.getFrom().getId().toString() + + "&fromType=" + relation.getFrom().getEntityType().name() + + "&relationType=" + relation.getType() + + "&relationTypeGroup=" + relation.getTypeGroup().name() + + "&toId=" + relation.getTo().getId().toString() + + "&toType=" + relation.getTo().getEntityType().name()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); + relationUpdateMsg = (RelationUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, relationUpdateMsg.getMsgType()); + Assert.assertEquals(relationUpdateMsg.getType(), relation.getType()); + Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getFromIdLSB(), relation.getFrom().getId().getLeastSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); + Assert.assertEquals(relationUpdateMsg.getFromIdMSB(), relation.getFrom().getId().getMostSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getToIdLSB(), relation.getTo().getId().getLeastSignificantBits()); + Assert.assertEquals(relationUpdateMsg.getToEntityType(), relation.getTo().getEntityType().name()); + Assert.assertEquals(relationUpdateMsg.getTypeGroup(), relation.getTypeGroup().name()); + } + + @Test + public void testSendRelationToCloud() throws Exception { + Device device1 = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + Device device2 = saveDeviceOnCloudAndVerifyDeliveryToEdge(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + RelationUpdateMsg.Builder relationUpdateMsgBuilder = RelationUpdateMsg.newBuilder(); + relationUpdateMsgBuilder.setType("test"); + relationUpdateMsgBuilder.setTypeGroup(RelationTypeGroup.COMMON.name()); + relationUpdateMsgBuilder.setToIdMSB(device1.getId().getId().getMostSignificantBits()); + relationUpdateMsgBuilder.setToIdLSB(device1.getId().getId().getLeastSignificantBits()); + relationUpdateMsgBuilder.setToEntityType(device1.getId().getEntityType().name()); + relationUpdateMsgBuilder.setFromIdMSB(device2.getId().getId().getMostSignificantBits()); + relationUpdateMsgBuilder.setFromIdLSB(device2.getId().getId().getLeastSignificantBits()); + relationUpdateMsgBuilder.setFromEntityType(device2.getId().getEntityType().name()); + relationUpdateMsgBuilder.setAdditionalInfo("{}"); + testAutoGeneratedCodeByProtobuf(relationUpdateMsgBuilder); + uplinkMsgBuilder.addRelationUpdateMsg(relationUpdateMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + + EntityRelation relation = doGet("/api/relation?" + + "&fromId=" + device2.getUuidId() + + "&fromType=" + device2.getId().getEntityType().name() + + "&relationType=" + "test" + + "&relationTypeGroup=" + RelationTypeGroup.COMMON.name() + + "&toId=" + device1.getUuidId() + + "&toType=" + device1.getId().getEntityType().name(), EntityRelation.class); + Assert.assertNotNull(relation); + } + + @Test + public void testSendRelationRequestToCloud() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + Asset asset = findAssetByName("Edge Asset 1"); + + EntityRelation relation = new EntityRelation(); + relation.setType("test"); + relation.setFrom(device.getId()); + relation.setTo(asset.getId()); + relation.setTypeGroup(RelationTypeGroup.COMMON); + + edgeImitator.expectMessageAmount(1); + doPost("/api/relation", relation); + Assert.assertTrue(edgeImitator.waitForMessages()); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + RelationRequestMsg.Builder relationRequestMsgBuilder = RelationRequestMsg.newBuilder(); + relationRequestMsgBuilder.setEntityIdMSB(device.getId().getId().getMostSignificantBits()); + relationRequestMsgBuilder.setEntityIdLSB(device.getId().getId().getLeastSignificantBits()); + relationRequestMsgBuilder.setEntityType(device.getId().getEntityType().name()); + testAutoGeneratedCodeByProtobuf(relationRequestMsgBuilder); + + uplinkMsgBuilder.addRelationRequestMsg(relationRequestMsgBuilder.build()); + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof RelationUpdateMsg); + RelationUpdateMsg relationUpdateMsg = (RelationUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, relationUpdateMsg.getMsgType()); + Assert.assertEquals(relation.getType(), relationUpdateMsg.getType()); + + UUID fromUUID = new UUID(relationUpdateMsg.getFromIdMSB(), relationUpdateMsg.getFromIdLSB()); + EntityId fromEntityId = EntityIdFactory.getByTypeAndUuid(relationUpdateMsg.getFromEntityType(), fromUUID); + Assert.assertEquals(relation.getFrom(), fromEntityId); + + UUID toUUID = new UUID(relationUpdateMsg.getToIdMSB(), relationUpdateMsg.getToIdLSB()); + EntityId toEntityId = EntityIdFactory.getByTypeAndUuid(relationUpdateMsg.getToEntityType(), toUUID); + Assert.assertEquals(relation.getTo(), toEntityId); + + Assert.assertEquals(relation.getTypeGroup().name(), relationUpdateMsg.getTypeGroup()); + } +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java new file mode 100644 index 0000000000..712beeb019 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseRuleChainEdgeTest.java @@ -0,0 +1,168 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.rule.RuleChainType; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataRequestMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainMetadataUpdateMsg; +import org.thingsboard.server.gen.edge.v1.RuleChainUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UplinkMsg; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseRuleChainEdgeTest extends AbstractEdgeTest { + + @Test + public void testRuleChains() throws Exception { + // create rule chain + edgeImitator.expectMessageAmount(2); + RuleChain ruleChain = new RuleChain(); + ruleChain.setName("Edge Test Rule Chain"); + ruleChain.setType(RuleChainType.EDGE); + RuleChain savedRuleChain = doPost("/api/ruleChain", ruleChain, RuleChain.class); + doPost("/api/edge/" + edge.getUuidId() + + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class); + createRuleChainMetadata(savedRuleChain); + Assert.assertTrue(edgeImitator.waitForMessages()); + Optional ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); + Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); + RuleChainUpdateMsg ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); + Assert.assertTrue(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType()) || + UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE.equals(ruleChainUpdateMsg.getMsgType())); + Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits()); + Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(ruleChainUpdateMsg.getName(), savedRuleChain.getName()); + + testRuleChainMetadataRequestMsg(savedRuleChain.getId()); + + // unassign rule chain from edge + edgeImitator.expectMessageAmount(1); + doDelete("/api/edge/" + edge.getUuidId() + + "/ruleChain/" + savedRuleChain.getUuidId(), RuleChain.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + ruleChainUpdateMsgOpt = edgeImitator.findMessageByType(RuleChainUpdateMsg.class); + Assert.assertTrue(ruleChainUpdateMsgOpt.isPresent()); + ruleChainUpdateMsg = ruleChainUpdateMsgOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, ruleChainUpdateMsg.getMsgType()); + Assert.assertEquals(ruleChainUpdateMsg.getIdMSB(), savedRuleChain.getUuidId().getMostSignificantBits()); + Assert.assertEquals(ruleChainUpdateMsg.getIdLSB(), savedRuleChain.getUuidId().getLeastSignificantBits()); + + // delete rule chain + edgeImitator.expectMessageAmount(1); + doDelete("/api/ruleChain/" + savedRuleChain.getUuidId()) + .andExpect(status().isOk()); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + } + + @Test + public void testSendRuleChainMetadataRequestToCloud() throws Exception { + RuleChainId edgeRootRuleChainId = edge.getRootRuleChainId(); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + RuleChainMetadataRequestMsg.Builder ruleChainMetadataRequestMsgBuilder = RuleChainMetadataRequestMsg.newBuilder(); + ruleChainMetadataRequestMsgBuilder.setRuleChainIdMSB(edgeRootRuleChainId.getId().getMostSignificantBits()); + ruleChainMetadataRequestMsgBuilder.setRuleChainIdLSB(edgeRootRuleChainId.getId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(ruleChainMetadataRequestMsgBuilder); + uplinkMsgBuilder.addRuleChainMetadataRequestMsg(ruleChainMetadataRequestMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = (RuleChainMetadataUpdateMsg) latestMessage; + Assert.assertEquals(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), edgeRootRuleChainId.getId().getMostSignificantBits()); + Assert.assertEquals(ruleChainMetadataUpdateMsg.getRuleChainIdLSB(), edgeRootRuleChainId.getId().getLeastSignificantBits()); + + testAutoGeneratedCodeByProtobuf(ruleChainMetadataUpdateMsg); + } + + private void testRuleChainMetadataRequestMsg(RuleChainId ruleChainId) throws Exception { + RuleChainMetadataRequestMsg.Builder ruleChainMetadataRequestMsgBuilder = RuleChainMetadataRequestMsg.newBuilder() + .setRuleChainIdMSB(ruleChainId.getId().getMostSignificantBits()) + .setRuleChainIdLSB(ruleChainId.getId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(ruleChainMetadataRequestMsgBuilder); + + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder() + .addRuleChainMetadataRequestMsg(ruleChainMetadataRequestMsgBuilder.build()); + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof RuleChainMetadataUpdateMsg); + RuleChainMetadataUpdateMsg ruleChainMetadataUpdateMsg = (RuleChainMetadataUpdateMsg) latestMessage; + RuleChainId receivedRuleChainId = + new RuleChainId(new UUID(ruleChainMetadataUpdateMsg.getRuleChainIdMSB(), ruleChainMetadataUpdateMsg.getRuleChainIdLSB())); + Assert.assertEquals(ruleChainId, receivedRuleChainId); + } + + private void createRuleChainMetadata(RuleChain ruleChain) throws Exception { + RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); + ruleChainMetaData.setRuleChainId(ruleChain.getId()); + + RuleNode ruleNode1 = new RuleNode(); + ruleNode1.setName("name1"); + ruleNode1.setType("type1"); + ruleNode1.setConfiguration(mapper.readTree("\"key1\": \"val1\"")); + + RuleNode ruleNode2 = new RuleNode(); + ruleNode2.setName("name2"); + ruleNode2.setType("type2"); + ruleNode2.setConfiguration(mapper.readTree("\"key2\": \"val2\"")); + + RuleNode ruleNode3 = new RuleNode(); + ruleNode3.setName("name3"); + ruleNode3.setType("type3"); + ruleNode3.setConfiguration(mapper.readTree("\"key3\": \"val3\"")); + + List ruleNodes = new ArrayList<>(); + ruleNodes.add(ruleNode1); + ruleNodes.add(ruleNode2); + ruleNodes.add(ruleNode3); + ruleChainMetaData.setFirstNodeIndex(0); + ruleChainMetaData.setNodes(ruleNodes); + + ruleChainMetaData.addConnectionInfo(0, 1, "success"); + ruleChainMetaData.addConnectionInfo(0, 2, "fail"); + ruleChainMetaData.addConnectionInfo(1, 2, "success"); + + doPost("/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java new file mode 100644 index 0000000000..25280de666 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java @@ -0,0 +1,229 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.fasterxml.jackson.databind.JsonNode; +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.edge.EdgeEvent; +import org.thingsboard.server.common.data.edge.EdgeEventActionType; +import org.thingsboard.server.common.data.edge.EdgeEventType; +import org.thingsboard.server.gen.edge.v1.AttributeDeleteMsg; +import org.thingsboard.server.gen.edge.v1.DeviceUpdateMsg; +import org.thingsboard.server.gen.edge.v1.EntityDataProto; +import org.thingsboard.server.gen.transport.TransportProtos; + +import java.util.List; + +abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest { + + @Test + public void testTimeseriesWithFailures() throws Exception { + int numberOfTimeseriesToSend = 1000; + + Device device = findDeviceByName("Edge Device 1"); + + edgeImitator.setRandomFailuresOnTimeseriesDownlink(true); + // imitator will generate failure in 5% of cases + edgeImitator.setFailureProbability(5.0); + edgeImitator.expectMessageAmount(numberOfTimeseriesToSend); + for (int idx = 1; idx <= numberOfTimeseriesToSend; idx++) { + String timeseriesData = "{\"data\":{\"idx\":" + idx + "},\"ts\":" + System.currentTimeMillis() + "}"; + JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); + EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, + device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); + edgeEventService.saveAsync(edgeEvent).get(); + clusterService.onEdgeEventUpdate(tenantId, edge.getId()); + } + + Assert.assertTrue(edgeImitator.waitForMessages(120)); + + List allTelemetryMsgs = edgeImitator.findAllMessagesByType(EntityDataProto.class); + Assert.assertEquals(numberOfTimeseriesToSend, allTelemetryMsgs.size()); + + for (int idx = 1; idx <= numberOfTimeseriesToSend; idx++) { + Assert.assertTrue(isIdxExistsInTheDownlinkList(idx, allTelemetryMsgs)); + } + + edgeImitator.setRandomFailuresOnTimeseriesDownlink(false); + } + + @Test + public void testAttributes() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + + testAttributesUpdatedMsg(device); + testPostAttributesMsg(device); + testAttributesDeleteMsg(device); + } + + private void testAttributesUpdatedMsg(Device device) throws Exception { + String attributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key1\":\"value1\"}}"; + JsonNode attributesEntityData = mapper.readTree(attributesData); + EdgeEvent edgeEvent1 = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, attributesEntityData); + edgeImitator.expectMessageAmount(1); + edgeEventService.saveAsync(edgeEvent1).get(); + clusterService.onEdgeEventUpdate(tenantId, edge.getId()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityDataProto); + EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; + Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); + Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); + Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); + Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope()); + Assert.assertTrue(latestEntityDataMsg.hasAttributesUpdatedMsg()); + + TransportProtos.PostAttributeMsg attributesUpdatedMsg = latestEntityDataMsg.getAttributesUpdatedMsg(); + Assert.assertEquals(1, attributesUpdatedMsg.getKvCount()); + TransportProtos.KeyValueProto keyValueProto = attributesUpdatedMsg.getKv(0); + Assert.assertEquals("key1", keyValueProto.getKey()); + Assert.assertEquals("value1", keyValueProto.getStringV()); + } + + private void testPostAttributesMsg(Device device) throws Exception { + String postAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"kv\":{\"key2\":\"value2\"}}"; + JsonNode postAttributesEntityData = mapper.readTree(postAttributesData); + EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.POST_ATTRIBUTES, device.getId().getId(), EdgeEventType.DEVICE, postAttributesEntityData); + edgeImitator.expectMessageAmount(1); + edgeEventService.saveAsync(edgeEvent).get(); + clusterService.onEdgeEventUpdate(tenantId, edge.getId()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityDataProto); + EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; + Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); + Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); + Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); + Assert.assertEquals("SERVER_SCOPE", latestEntityDataMsg.getPostAttributeScope()); + Assert.assertTrue(latestEntityDataMsg.hasPostAttributesMsg()); + + TransportProtos.PostAttributeMsg postAttributesMsg = latestEntityDataMsg.getPostAttributesMsg(); + Assert.assertEquals(1, postAttributesMsg.getKvCount()); + TransportProtos.KeyValueProto keyValueProto = postAttributesMsg.getKv(0); + Assert.assertEquals("key2", keyValueProto.getKey()); + Assert.assertEquals("value2", keyValueProto.getStringV()); + } + + private void testAttributesDeleteMsg(Device device) throws Exception { + String deleteAttributesData = "{\"scope\":\"SERVER_SCOPE\",\"keys\":[\"key1\",\"key2\"]}"; + JsonNode deleteAttributesEntityData = mapper.readTree(deleteAttributesData); + EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.ATTRIBUTES_DELETED, device.getId().getId(), EdgeEventType.DEVICE, deleteAttributesEntityData); + edgeImitator.expectMessageAmount(1); + edgeEventService.saveAsync(edgeEvent).get(); + clusterService.onEdgeEventUpdate(tenantId, edge.getId()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityDataProto); + EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; + Assert.assertEquals(device.getUuidId().getMostSignificantBits(), latestEntityDataMsg.getEntityIdMSB()); + Assert.assertEquals(device.getUuidId().getLeastSignificantBits(), latestEntityDataMsg.getEntityIdLSB()); + Assert.assertEquals(device.getId().getEntityType().name(), latestEntityDataMsg.getEntityType()); + + Assert.assertTrue(latestEntityDataMsg.hasAttributeDeleteMsg()); + + AttributeDeleteMsg attributeDeleteMsg = latestEntityDataMsg.getAttributeDeleteMsg(); + Assert.assertEquals(attributeDeleteMsg.getScope(), deleteAttributesEntityData.get("scope").asText()); + + Assert.assertEquals(2, attributeDeleteMsg.getAttributeNamesCount()); + Assert.assertEquals("key1", attributeDeleteMsg.getAttributeNames(0)); + Assert.assertEquals("key2", attributeDeleteMsg.getAttributeNames(1)); + } + + @Test + public void testTimeseries() throws Exception { + Device device = findDeviceByName("Edge Device 1"); + String timeseriesData = "{\"data\":{\"temperature\":25},\"ts\":" + System.currentTimeMillis() + "}"; + JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); + edgeImitator.expectMessageAmount(1); + EdgeEvent edgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); + edgeEventService.saveAsync(edgeEvent).get(); + clusterService.onEdgeEventUpdate(tenantId, edge.getId()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof EntityDataProto); + EntityDataProto latestEntityDataMsg = (EntityDataProto) latestMessage; + Assert.assertEquals(latestEntityDataMsg.getEntityIdMSB(), device.getUuidId().getMostSignificantBits()); + Assert.assertEquals(latestEntityDataMsg.getEntityIdLSB(), device.getUuidId().getLeastSignificantBits()); + Assert.assertEquals(latestEntityDataMsg.getEntityType(), device.getId().getEntityType().name()); + Assert.assertTrue(latestEntityDataMsg.hasPostTelemetryMsg()); + + TransportProtos.PostTelemetryMsg postTelemetryMsg = latestEntityDataMsg.getPostTelemetryMsg(); + Assert.assertEquals(1, postTelemetryMsg.getTsKvListCount()); + TransportProtos.TsKvListProto tsKvListProto = postTelemetryMsg.getTsKvList(0); + Assert.assertEquals(timeseriesEntityData.get("ts").asLong(), tsKvListProto.getTs()); + Assert.assertEquals(1, tsKvListProto.getKvCount()); + TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKv(0); + Assert.assertEquals("temperature", keyValueProto.getKey()); + Assert.assertEquals(25, keyValueProto.getLongV()); + } + + private boolean isIdxExistsInTheDownlinkList(int idx, List allTelemetryMsgs) { + for (EntityDataProto proto : allTelemetryMsgs) { + TransportProtos.PostTelemetryMsg postTelemetryMsg = proto.getPostTelemetryMsg(); + Assert.assertEquals(1, postTelemetryMsg.getTsKvListCount()); + TransportProtos.TsKvListProto tsKvListProto = postTelemetryMsg.getTsKvList(0); + Assert.assertEquals(1, tsKvListProto.getKvCount()); + TransportProtos.KeyValueProto keyValueProto = tsKvListProto.getKv(0); + Assert.assertEquals("idx", keyValueProto.getKey()); + if (keyValueProto.getLongV() == idx) { + return true; + } + } + return false; + } + + @Test + public void testTimeseriesDeliveryFailuresForever_deliverOnlyDeviceUpdateMsgs() throws Exception { + int numberOfMsgsToSend = 100; + + Device device = findDeviceByName("Edge Device 1"); + + edgeImitator.setRandomFailuresOnTimeseriesDownlink(true); + // imitator will generate failure in 100% of timeseries cases + edgeImitator.setFailureProbability(100); + edgeImitator.expectMessageAmount(numberOfMsgsToSend); + for (int idx = 1; idx <= numberOfMsgsToSend; idx++) { + String timeseriesData = "{\"data\":{\"idx\":" + idx + "},\"ts\":" + System.currentTimeMillis() + "}"; + JsonNode timeseriesEntityData = mapper.readTree(timeseriesData); + EdgeEvent failedEdgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.TIMESERIES_UPDATED, + device.getId().getId(), EdgeEventType.DEVICE, timeseriesEntityData); + edgeEventService.saveAsync(failedEdgeEvent).get(); + + EdgeEvent successEdgeEvent = constructEdgeEvent(tenantId, edge.getId(), EdgeEventActionType.UPDATED, + device.getId().getId(), EdgeEventType.DEVICE, null); + edgeEventService.saveAsync(successEdgeEvent).get(); + + clusterService.onEdgeEventUpdate(tenantId, edge.getId()); + } + + Assert.assertTrue(edgeImitator.waitForMessages(120)); + + List allTelemetryMsgs = edgeImitator.findAllMessagesByType(EntityDataProto.class); + Assert.assertTrue(allTelemetryMsgs.isEmpty()); + + List deviceUpdateMsgs = edgeImitator.findAllMessagesByType(DeviceUpdateMsg.class); + Assert.assertEquals(numberOfMsgsToSend, deviceUpdateMsgs.size()); + + edgeImitator.setRandomFailuresOnTimeseriesDownlink(false); + } +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseUserEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseUserEdgeTest.java new file mode 100644 index 0000000000..61e2976574 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseUserEdgeTest.java @@ -0,0 +1,237 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.security.Authority; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.UplinkMsg; +import org.thingsboard.server.gen.edge.v1.UserCredentialsRequestMsg; +import org.thingsboard.server.gen.edge.v1.UserCredentialsUpdateMsg; +import org.thingsboard.server.gen.edge.v1.UserUpdateMsg; +import org.thingsboard.server.service.security.model.ChangePasswordRequest; + +import java.util.Optional; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseUserEdgeTest extends AbstractEdgeTest { + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @Test + public void testCreateUpdateDeleteTenantUser() throws Exception { + // create user + edgeImitator.expectMessageAmount(2); + User newTenantAdmin = new User(); + newTenantAdmin.setAuthority(Authority.TENANT_ADMIN); + newTenantAdmin.setTenantId(savedTenant.getId()); + newTenantAdmin.setEmail("tenantAdmin@thingsboard.org"); + newTenantAdmin.setFirstName("Boris"); + newTenantAdmin.setLastName("Johnson"); + User savedTenantAdmin = createUser(newTenantAdmin, "tenant"); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 2 messages - user update msg and user credentials update msg + Optional latestMessageOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); + Assert.assertTrue(latestMessageOpt.isPresent()); + UserUpdateMsg userUpdateMsg = latestMessageOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); + Assert.assertEquals(savedTenantAdmin.getUuidId().getMostSignificantBits(), userUpdateMsg.getIdMSB()); + Assert.assertEquals(savedTenantAdmin.getUuidId().getLeastSignificantBits(), userUpdateMsg.getIdLSB()); + Assert.assertEquals(savedTenantAdmin.getAuthority().name(), userUpdateMsg.getAuthority()); + Assert.assertEquals(savedTenantAdmin.getEmail(), userUpdateMsg.getEmail()); + Assert.assertEquals(savedTenantAdmin.getFirstName(), userUpdateMsg.getFirstName()); + Assert.assertEquals(savedTenantAdmin.getLastName(), userUpdateMsg.getLastName()); + Optional userCredentialsUpdateMsgOpt = edgeImitator.findMessageByType(UserCredentialsUpdateMsg.class); + Assert.assertTrue(userCredentialsUpdateMsgOpt.isPresent()); + + // update user + edgeImitator.expectMessageAmount(1); + savedTenantAdmin.setLastName("Borisov"); + savedTenantAdmin = doPost("/api/user", savedTenantAdmin, User.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserUpdateMsg); + userUpdateMsg = (UserUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); + Assert.assertEquals(savedTenantAdmin.getLastName(), userUpdateMsg.getLastName()); + + // update user credentials + edgeImitator.expectMessageAmount(1); + login(savedTenantAdmin.getEmail(), "tenant"); + ChangePasswordRequest changePasswordRequest = new ChangePasswordRequest(); + changePasswordRequest.setCurrentPassword("tenant"); + changePasswordRequest.setNewPassword("newTenant"); + doPost("/api/auth/changePassword", changePasswordRequest); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); + UserCredentialsUpdateMsg userCredentialsUpdateMsg = (UserCredentialsUpdateMsg) latestMessage; + Assert.assertEquals(savedTenantAdmin.getUuidId().getMostSignificantBits(), userCredentialsUpdateMsg.getUserIdMSB()); + Assert.assertEquals(savedTenantAdmin.getUuidId().getLeastSignificantBits(), userCredentialsUpdateMsg.getUserIdLSB()); + Assert.assertTrue(passwordEncoder.matches(changePasswordRequest.getNewPassword(), userCredentialsUpdateMsg.getPassword())); + + // delete user + edgeImitator.expectMessageAmount(1); + login(tenantAdmin.getEmail(), "testPassword1"); + doDelete("/api/user/" + savedTenantAdmin.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserUpdateMsg); + userUpdateMsg = (UserUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, userUpdateMsg.getMsgType()); + Assert.assertEquals(savedTenantAdmin.getUuidId().getMostSignificantBits(), userUpdateMsg.getIdMSB()); + Assert.assertEquals(savedTenantAdmin.getUuidId().getLeastSignificantBits(), userUpdateMsg.getIdLSB()); + } + + @Test + public void testCreateUpdateDeleteCustomerUser() throws Exception { + // create customer + edgeImitator.expectMessageAmount(1); + Customer customer = new Customer(); + customer.setTitle("Edge Customer"); + Customer savedCustomer = doPost("/api/customer", customer, Customer.class); + Assert.assertFalse(edgeImitator.waitForMessages(1)); + + // assign edge to customer + edgeImitator.expectMessageAmount(2); + doPost("/api/customer/" + savedCustomer.getUuidId() + + "/edge/" + edge.getUuidId(), Edge.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + + // create user + edgeImitator.expectMessageAmount(2); + User customerUser = new User(); + customerUser.setAuthority(Authority.CUSTOMER_USER); + customerUser.setTenantId(savedTenant.getId()); + customerUser.setCustomerId(savedCustomer.getId()); + customerUser.setEmail("customerUser@thingsboard.org"); + customerUser.setFirstName("John"); + customerUser.setLastName("Edwards"); + User savedCustomerUser = createUser(customerUser, "customer"); + Assert.assertTrue(edgeImitator.waitForMessages()); // wait 2 messages - user update msg and user credentials update msg + Optional latestMessageOpt = edgeImitator.findMessageByType(UserUpdateMsg.class); + Assert.assertTrue(latestMessageOpt.isPresent()); + UserUpdateMsg userUpdateMsg = latestMessageOpt.get(); + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomerUser.getUuidId().getMostSignificantBits(), userUpdateMsg.getIdMSB()); + Assert.assertEquals(savedCustomerUser.getUuidId().getLeastSignificantBits(), userUpdateMsg.getIdLSB()); + Assert.assertEquals(savedCustomerUser.getCustomerId().getId().getMostSignificantBits(), userUpdateMsg.getCustomerIdMSB()); + Assert.assertEquals(savedCustomerUser.getCustomerId().getId().getLeastSignificantBits(), userUpdateMsg.getCustomerIdLSB()); + Assert.assertEquals(savedCustomerUser.getAuthority().name(), userUpdateMsg.getAuthority()); + Assert.assertEquals(savedCustomerUser.getEmail(), userUpdateMsg.getEmail()); + Assert.assertEquals(savedCustomerUser.getFirstName(), userUpdateMsg.getFirstName()); + Assert.assertEquals(savedCustomerUser.getLastName(), userUpdateMsg.getLastName()); + + // update user + edgeImitator.expectMessageAmount(1); + savedCustomerUser.setLastName("Addams"); + savedCustomerUser = doPost("/api/user", savedCustomerUser, User.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserUpdateMsg); + userUpdateMsg = (UserUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, userUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomerUser.getLastName(), userUpdateMsg.getLastName()); + + // update user credentials + edgeImitator.expectMessageAmount(1); + login(savedCustomerUser.getEmail(), "customer"); + ChangePasswordRequest changePasswordRequest = new ChangePasswordRequest(); + changePasswordRequest.setCurrentPassword("customer"); + changePasswordRequest.setNewPassword("newCustomer"); + doPost("/api/auth/changePassword", changePasswordRequest); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); + UserCredentialsUpdateMsg userCredentialsUpdateMsg = (UserCredentialsUpdateMsg) latestMessage; + Assert.assertEquals(savedCustomerUser.getUuidId().getMostSignificantBits(), userCredentialsUpdateMsg.getUserIdMSB()); + Assert.assertEquals(savedCustomerUser.getUuidId().getLeastSignificantBits(), userCredentialsUpdateMsg.getUserIdLSB()); + Assert.assertTrue(passwordEncoder.matches(changePasswordRequest.getNewPassword(), userCredentialsUpdateMsg.getPassword())); + + // delete user + edgeImitator.expectMessageAmount(1); + login(tenantAdmin.getEmail(), "testPassword1"); + doDelete("/api/user/" + savedCustomerUser.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserUpdateMsg); + userUpdateMsg = (UserUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, userUpdateMsg.getMsgType()); + Assert.assertEquals(savedCustomerUser.getUuidId().getMostSignificantBits(), userUpdateMsg.getIdMSB()); + Assert.assertEquals(savedCustomerUser.getUuidId().getLeastSignificantBits(), userUpdateMsg.getIdLSB()); + + } + + @Test + public void testSendUserCredentialsRequestToCloud() throws Exception { + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder(); + userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits()); + userCredentialsRequestMsgBuilder.setUserIdLSB(tenantAdmin.getId().getId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(userCredentialsRequestMsgBuilder); + uplinkMsgBuilder.addUserCredentialsRequestMsg(userCredentialsRequestMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); + UserCredentialsUpdateMsg userCredentialsUpdateMsg = (UserCredentialsUpdateMsg) latestMessage; + Assert.assertEquals(tenantAdmin.getId().getId().getMostSignificantBits(), userCredentialsUpdateMsg.getUserIdMSB()); + Assert.assertEquals(tenantAdmin.getId().getId().getLeastSignificantBits(), userCredentialsUpdateMsg.getUserIdLSB()); + } + + @Test + public void sendUserCredentialsRequest() throws Exception { + UplinkMsg.Builder uplinkMsgBuilder = UplinkMsg.newBuilder(); + UserCredentialsRequestMsg.Builder userCredentialsRequestMsgBuilder = UserCredentialsRequestMsg.newBuilder(); + userCredentialsRequestMsgBuilder.setUserIdMSB(tenantAdmin.getId().getId().getMostSignificantBits()); + userCredentialsRequestMsgBuilder.setUserIdLSB(tenantAdmin.getId().getId().getLeastSignificantBits()); + testAutoGeneratedCodeByProtobuf(userCredentialsRequestMsgBuilder); + uplinkMsgBuilder.addUserCredentialsRequestMsg(userCredentialsRequestMsgBuilder.build()); + + testAutoGeneratedCodeByProtobuf(uplinkMsgBuilder); + + edgeImitator.expectResponsesAmount(1); + edgeImitator.expectMessageAmount(1); + edgeImitator.sendUplinkMsg(uplinkMsgBuilder.build()); + Assert.assertTrue(edgeImitator.waitForResponses()); + Assert.assertTrue(edgeImitator.waitForMessages()); + + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof UserCredentialsUpdateMsg); + UserCredentialsUpdateMsg userCredentialsUpdateMsg = (UserCredentialsUpdateMsg) latestMessage; + Assert.assertEquals(userCredentialsUpdateMsg.getUserIdMSB(), tenantAdmin.getId().getId().getMostSignificantBits()); + Assert.assertEquals(userCredentialsUpdateMsg.getUserIdLSB(), tenantAdmin.getId().getId().getLeastSignificantBits()); + + testAutoGeneratedCodeByProtobuf(userCredentialsUpdateMsg); + } +} diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseWidgetEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseWidgetEdgeTest.java new file mode 100644 index 0000000000..297cd8ca82 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/BaseWidgetEdgeTest.java @@ -0,0 +1,118 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.protobuf.AbstractMessage; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.gen.edge.v1.UpdateMsgType; +import org.thingsboard.server.gen.edge.v1.WidgetTypeUpdateMsg; +import org.thingsboard.server.gen.edge.v1.WidgetsBundleUpdateMsg; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +abstract public class BaseWidgetEdgeTest extends AbstractEdgeTest { + + @Test + public void testWidgetsBundleAndWidgetType() throws Exception { + // create widget bundle + edgeImitator.expectMessageAmount(1); + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTitle("Test Widget Bundle"); + WidgetsBundle savedWidgetsBundle = doPost("/api/widgetsBundle", widgetsBundle, WidgetsBundle.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + AbstractMessage latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); + WidgetsBundleUpdateMsg widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType()); + Assert.assertEquals(savedWidgetsBundle.getUuidId().getMostSignificantBits(), widgetsBundleUpdateMsg.getIdMSB()); + Assert.assertEquals(savedWidgetsBundle.getUuidId().getLeastSignificantBits(), widgetsBundleUpdateMsg.getIdLSB()); + Assert.assertEquals(savedWidgetsBundle.getAlias(), widgetsBundleUpdateMsg.getAlias()); + Assert.assertEquals(savedWidgetsBundle.getTitle(), widgetsBundleUpdateMsg.getTitle()); + testAutoGeneratedCodeByProtobuf(widgetsBundleUpdateMsg); + + // create widget type + edgeImitator.expectMessageAmount(1); + WidgetType widgetType = new WidgetType(); + widgetType.setName("Test Widget Type"); + widgetType.setBundleAlias(savedWidgetsBundle.getAlias()); + ObjectNode descriptor = mapper.createObjectNode(); + descriptor.put("key", "value"); + widgetType.setDescriptor(descriptor); + WidgetType savedWidgetType = doPost("/api/widgetType", widgetType, WidgetType.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); + WidgetTypeUpdateMsg widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_CREATED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType()); + Assert.assertEquals(savedWidgetType.getUuidId().getMostSignificantBits(), widgetTypeUpdateMsg.getIdMSB()); + Assert.assertEquals(savedWidgetType.getUuidId().getLeastSignificantBits(), widgetTypeUpdateMsg.getIdLSB()); + Assert.assertEquals(savedWidgetType.getAlias(), widgetTypeUpdateMsg.getAlias()); + Assert.assertEquals(savedWidgetType.getName(), widgetTypeUpdateMsg.getName()); + Assert.assertEquals(JacksonUtil.toJsonNode(widgetTypeUpdateMsg.getDescriptorJson()), savedWidgetType.getDescriptor()); + + // update widget bundle + edgeImitator.expectMessageAmount(1); + savedWidgetsBundle.setTitle("Test Widget Bundle - Updated"); + savedWidgetsBundle = doPost("/api/widgetsBundle", savedWidgetsBundle, WidgetsBundle.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); + widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType()); + Assert.assertEquals(savedWidgetsBundle.getTitle(), widgetsBundleUpdateMsg.getTitle()); + + // update widget type + edgeImitator.expectMessageAmount(1); + savedWidgetType.setName("Test Widget Type - Updated"); + savedWidgetType = doPost("/api/widgetType", savedWidgetType, WidgetType.class); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); + widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_UPDATED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType()); + Assert.assertEquals(savedWidgetType.getName(), widgetTypeUpdateMsg.getName()); + + // delete widget type + edgeImitator.expectMessageAmount(1); + doDelete("/api/widgetType/" + savedWidgetType.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof WidgetTypeUpdateMsg); + widgetTypeUpdateMsg = (WidgetTypeUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, widgetTypeUpdateMsg.getMsgType()); + Assert.assertEquals(savedWidgetType.getUuidId().getMostSignificantBits(), widgetTypeUpdateMsg.getIdMSB()); + Assert.assertEquals(savedWidgetType.getUuidId().getLeastSignificantBits(), widgetTypeUpdateMsg.getIdLSB()); + + // delete widget bundle + edgeImitator.expectMessageAmount(1); + doDelete("/api/widgetsBundle/" + savedWidgetsBundle.getUuidId()) + .andExpect(status().isOk()); + Assert.assertTrue(edgeImitator.waitForMessages()); + latestMessage = edgeImitator.getLatestMessage(); + Assert.assertTrue(latestMessage instanceof WidgetsBundleUpdateMsg); + widgetsBundleUpdateMsg = (WidgetsBundleUpdateMsg) latestMessage; + Assert.assertEquals(UpdateMsgType.ENTITY_DELETED_RPC_MESSAGE, widgetsBundleUpdateMsg.getMsgType()); + Assert.assertEquals(savedWidgetsBundle.getUuidId().getMostSignificantBits(), widgetsBundleUpdateMsg.getIdMSB()); + Assert.assertEquals(savedWidgetsBundle.getUuidId().getLeastSignificantBits(), widgetsBundleUpdateMsg.getIdLSB()); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java index aacea38b12..668f702a87 100644 --- a/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java +++ b/application/src/test/java/org/thingsboard/server/edge/imitator/EdgeImitator.java @@ -106,6 +106,7 @@ public class EdgeImitator { this.routingSecret = routingSecret; setEdgeCredentials("rpcHost", host); setEdgeCredentials("rpcPort", port); + setEdgeCredentials("timeoutSecs", 3); setEdgeCredentials("keepAliveTimeSec", 300); } @@ -151,22 +152,18 @@ public class EdgeImitator { Futures.addCallback(future, new FutureCallback<>() { @Override public void onSuccess(@Nullable List result) { - if (connected) { - DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder() - .setDownlinkMsgId(downlinkMsg.getDownlinkMsgId()) - .setSuccess(true).build(); - edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg); - } + DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder() + .setDownlinkMsgId(downlinkMsg.getDownlinkMsgId()) + .setSuccess(true).build(); + edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg); } @Override public void onFailure(Throwable t) { - if (connected) { - DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder() - .setDownlinkMsgId(downlinkMsg.getDownlinkMsgId()) - .setSuccess(false).setErrorMsg(t.getMessage()).build(); - edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg); - } + DownlinkResponseMsg downlinkResponseMsg = DownlinkResponseMsg.newBuilder() + .setDownlinkMsgId(downlinkMsg.getDownlinkMsgId()) + .setSuccess(false).setErrorMsg(t.getMessage()).build(); + edgeRpcClient.sendDownlinkResponseMsg(downlinkResponseMsg); } }, MoreExecutors.directExecutor()); } @@ -295,6 +292,10 @@ public class EdgeImitator { result.add(saveDownlinkMsg(queueUpdateMsg)); } } + if (downlinkMsg.hasEdgeConfiguration()) { + result.add(saveDownlinkMsg(downlinkMsg.getEdgeConfiguration())); + } + return Futures.allAsList(result); } diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeResponse.java b/application/src/test/java/org/thingsboard/server/edge/sql/AlarmEdgeSqlTest.java similarity index 72% rename from application/src/main/java/org/thingsboard/server/service/script/JsInvokeResponse.java rename to application/src/test/java/org/thingsboard/server/edge/sql/AlarmEdgeSqlTest.java index 020c4a3a02..e7c6e1e84a 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeResponse.java +++ b/application/src/test/java/org/thingsboard/server/edge/sql/AlarmEdgeSqlTest.java @@ -13,17 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.script; +package org.thingsboard.server.edge.sql; -import java.util.List; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseAlarmEdgeTest; -/** - * Created by ashvayka on 25.09.18. - */ -public class JsInvokeResponse { - - private String scriptId; - private String scriptBody; - private List args; +@DaoSqlTest +public class AlarmEdgeSqlTest extends BaseAlarmEdgeTest { } diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/AssetEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/AssetEdgeSqlTest.java new file mode 100644 index 0000000000..bbf2449a03 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/AssetEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseAssetEdgeTest; + +@DaoSqlTest +public class AssetEdgeSqlTest extends BaseAssetEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/AssetProfileEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/AssetProfileEdgeSqlTest.java new file mode 100644 index 0000000000..5a6fd34fe1 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/AssetProfileEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseAssetProfileEdgeTest; + +@DaoSqlTest +public class AssetProfileEdgeSqlTest extends BaseAssetProfileEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/CustomerEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/CustomerEdgeSqlTest.java new file mode 100644 index 0000000000..73bb27f976 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/CustomerEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseCustomerEdgeTest; + +@DaoSqlTest +public class CustomerEdgeSqlTest extends BaseCustomerEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/DashboardEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/DashboardEdgeSqlTest.java new file mode 100644 index 0000000000..fe6915b355 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/DashboardEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseDashboardEdgeTest; + +@DaoSqlTest +public class DashboardEdgeSqlTest extends BaseDashboardEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/DeviceEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/DeviceEdgeSqlTest.java new file mode 100644 index 0000000000..a82e5d5fe6 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/DeviceEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseDeviceEdgeTest; + +@DaoSqlTest +public class DeviceEdgeSqlTest extends BaseDeviceEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/DeviceProfileEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/DeviceProfileEdgeSqlTest.java new file mode 100644 index 0000000000..a870660413 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/DeviceProfileEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseDeviceProfileEdgeTest; + +@DaoSqlTest +public class DeviceProfileEdgeSqlTest extends BaseDeviceProfileEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/EdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/EdgeSqlTest.java index 8f6c26271b..e1f39e3c6b 100644 --- a/application/src/test/java/org/thingsboard/server/edge/sql/EdgeSqlTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/sql/EdgeSqlTest.java @@ -20,4 +20,5 @@ import org.thingsboard.server.edge.BaseEdgeTest; @DaoSqlTest public class EdgeSqlTest extends BaseEdgeTest { + } diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/EntityViewEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/EntityViewEdgeSqlTest.java new file mode 100644 index 0000000000..a77aed7c37 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/EntityViewEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseEntityViewEdgeTest; + +@DaoSqlTest +public class EntityViewEdgeSqlTest extends BaseEntityViewEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/OtaPackageEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/OtaPackageEdgeSqlTest.java new file mode 100644 index 0000000000..17dcd444f7 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/OtaPackageEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseOtaPackageEdgeTest; + +@DaoSqlTest +public class OtaPackageEdgeSqlTest extends BaseOtaPackageEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/QueueEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/QueueEdgeSqlTest.java new file mode 100644 index 0000000000..28a7f51458 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/QueueEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseQueueEdgeTest; + +@DaoSqlTest +public class QueueEdgeSqlTest extends BaseQueueEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/RelationEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/RelationEdgeSqlTest.java new file mode 100644 index 0000000000..75fceacd45 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/RelationEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseRelationEdgeTest; + +@DaoSqlTest +public class RelationEdgeSqlTest extends BaseRelationEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/RuleChainEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/RuleChainEdgeSqlTest.java new file mode 100644 index 0000000000..34921eb806 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/RuleChainEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseRuleChainEdgeTest; + +@DaoSqlTest +public class RuleChainEdgeSqlTest extends BaseRuleChainEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/TelemetryEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/TelemetryEdgeSqlTest.java new file mode 100644 index 0000000000..e88df0b8bf --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/TelemetryEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseTelemetryEdgeTest; + +@DaoSqlTest +public class TelemetryEdgeSqlTest extends BaseTelemetryEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/UserEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/UserEdgeSqlTest.java new file mode 100644 index 0000000000..0b84f76aba --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/UserEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseUserEdgeTest; + +@DaoSqlTest +public class UserEdgeSqlTest extends BaseUserEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/edge/sql/WidgetEdgeSqlTest.java b/application/src/test/java/org/thingsboard/server/edge/sql/WidgetEdgeSqlTest.java new file mode 100644 index 0000000000..6714d19438 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/edge/sql/WidgetEdgeSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.edge.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.edge.BaseWidgetEdgeTest; + +@DaoSqlTest +public class WidgetEdgeSqlTest extends BaseWidgetEdgeTest { + +} diff --git a/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java index 88aa45c74c..c3e7e6dcef 100644 --- a/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/queue/discovery/HashPartitionServiceTest.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.queue.discovery; -import com.datastax.driver.core.utils.UUIDs; import com.datastax.oss.driver.api.core.uuid.Uuids; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; @@ -136,7 +135,7 @@ public class HashPartitionServiceTest { Random random = new Random(); long ts = new SimpleDateFormat("dd-MM-yyyy").parse("06-12-2016").getTime() - TimeUnit.DAYS.toMillis(tenantCount); for (int tenantIndex = 0; tenantIndex < tenantCount; tenantIndex++) { - TenantId tenantId = new TenantId(UUIDs.startOf(ts)); + TenantId tenantId = new TenantId(Uuids.startOf(ts)); ts += TimeUnit.DAYS.toMillis(1) + random.nextInt(1000); for (int queueIndex = 0; queueIndex < queueCount; queueIndex++) { QueueKey queueKey = new QueueKey(ServiceType.TB_RULE_ENGINE, "queue" + queueIndex, tenantId); diff --git a/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java new file mode 100644 index 0000000000..dba6546fc0 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/entitiy/alarm/DefaultTbAlarmServiceTest.java @@ -0,0 +1,115 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.entitiy.alarm; + +import com.google.common.util.concurrent.Futures; +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.boot.test.mock.mockito.SpyBean; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmStatus; +import org.thingsboard.server.common.data.exception.ThingsboardException; +import org.thingsboard.server.dao.alarm.AlarmService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.service.entitiy.TbNotificationEntityService; +import org.thingsboard.server.service.executors.DbCallbackExecutorService; +import org.thingsboard.server.service.sync.vc.EntitiesVersionControlService; +import org.thingsboard.server.service.telemetry.AlarmSubscriptionService; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Slf4j +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = DefaultTbAlarmService.class) +@TestPropertySource(properties = { + "server.log_controller_error_stack_trace=false" +}) +public class DefaultTbAlarmServiceTest { + + @MockBean + protected DbCallbackExecutorService dbExecutor; + @MockBean + protected TbNotificationEntityService notificationEntityService; + @MockBean + protected EdgeService edgeService; + @MockBean + protected AlarmService alarmService; + @MockBean + protected AlarmSubscriptionService alarmSubscriptionService; + @MockBean + protected CustomerService customerService; + @MockBean + protected TbClusterService tbClusterService; + @MockBean + private EntitiesVersionControlService vcService; + + @SpyBean + DefaultTbAlarmService service; + + @Test + public void testSave() throws ThingsboardException { + var alarm = new Alarm(); + when(alarmSubscriptionService.createOrUpdateAlarm(alarm)).thenReturn(alarm); + service.save(alarm, new User()); + + verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); + verify(alarmSubscriptionService, times(1)).createOrUpdateAlarm(eq(alarm)); + } + + @Test + public void testAck() { + var alarm = new Alarm(); + alarm.setStatus(AlarmStatus.ACTIVE_UNACK); + when(alarmSubscriptionService.ackAlarm(any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true)); + service.ack(alarm, new User()); + + verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); + verify(alarmSubscriptionService, times(1)).ackAlarm(any(), any(), anyLong()); + } + + @Test + public void testClear() { + var alarm = new Alarm(); + alarm.setStatus(AlarmStatus.ACTIVE_ACK); + when(alarmSubscriptionService.clearAlarm(any(), any(), any(), anyLong())).thenReturn(Futures.immediateFuture(true)); + service.clear(alarm, new User()); + + verify(notificationEntityService, times(1)).notifyCreateOrUpdateAlarm(any(), any(), any()); + verify(alarmSubscriptionService, times(1)).clearAlarm(any(), any(), any(), anyLong()); + } + + @Test + public void testDelete() { + service.delete(new Alarm(), new User()); + + verify(notificationEntityService, times(1)).notifyDeleteAlarm(any(), any(), any(), any(), any(), any(), anyString()); + verify(alarmSubscriptionService, times(1)).deleteAlarm(any(), any()); + } +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java b/application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java index 1d91f50131..9b3daa0ac5 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java +++ b/application/src/test/java/org/thingsboard/server/service/script/MockJsInvokeService.java @@ -20,6 +20,8 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; +import org.thingsboard.script.api.ScriptType; +import org.thingsboard.script.api.js.JsInvokeService; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; @@ -31,13 +33,13 @@ import java.util.UUID; public class MockJsInvokeService implements JsInvokeService { @Override - public ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames) { + public ListenableFuture eval(TenantId tenantId, ScriptType scriptType, String scriptBody, String... argNames) { log.warn("eval {} {} {} {}", tenantId, scriptType, scriptBody, argNames); return Futures.immediateFuture(UUID.randomUUID()); } @Override - public ListenableFuture invokeFunction(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args) { + public ListenableFuture invokeScript(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args) { log.warn("invokeFunction {} {} {} {}", tenantId, customerId, scriptId, args); return Futures.immediateFuture("{}"); } diff --git a/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java new file mode 100644 index 0000000000..a40a087514 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java @@ -0,0 +1,128 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.service.script; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.script.api.ScriptType; +import org.thingsboard.script.api.mvel.MvelInvokeService; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +@DaoSqlTest +@TestPropertySource(properties = { + "mvel.max_script_body_size=100", + "mvel.max_total_args_size=50", + "mvel.max_result_size=50", + "mvel.max_errors=2", +}) +class MvelInvokeServiceTest extends AbstractControllerTest { + + @Autowired + private MvelInvokeService invokeService; + + @Value("${mvel.max_errors}") + private int maxJsErrors; + + @Test + void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException { + int iterations = 100000; + UUID scriptId = evalScript("return msg.temperature > 20"); + // warmup + ObjectNode msg = JacksonUtil.newObjectNode(); + for (int i = 0; i < 100; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long startTs = System.currentTimeMillis(); + for (int i = 0; i < iterations; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long duration = System.currentTimeMillis() - startTs; + System.out.println(iterations + " invocations took: " + duration + "ms"); + Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(1)); + } + + @Test + void givenTooBigScriptForEval_thenReturnError() { + String hugeScript = "var a = 'qwertyqwertywertyqwabababerqwertyqwertywertyqwabababerqwertyqwertywertyqwabababerqwertyqwertywertyqwabababerqwertyqwertywertyqwabababer'; return {a: a};"; + + assertThatThrownBy(() -> { + evalScript(hugeScript); + }).hasMessageContaining("body exceeds maximum allowed size"); + } + + @Test + void givenTooBigScriptInputArgs_thenReturnErrorAndReportScriptExecutionError() throws Exception { + String script = "return { msg: msg };"; + String hugeMsg = "{\"input\":\"123456781234349\"}"; + UUID scriptId = evalScript(script); + + for (int i = 0; i < maxJsErrors; i++) { + assertThatThrownBy(() -> { + invokeScript(scriptId, hugeMsg); + }).hasMessageContaining("input arguments exceed maximum"); + } + assertThatScriptIsBlocked(scriptId); + } + + @Test + void whenScriptInvocationResultIsTooBig_thenReturnErrorAndReportScriptExecutionError() throws Exception { + String script = "var s = 'a'; for(int i=0; i<50; i++){ s +='a';} return { s: s};"; + UUID scriptId = evalScript(script); + + for (int i = 0; i < maxJsErrors; i++) { + assertThatThrownBy(() -> { + invokeScript(scriptId, "{}"); + }).hasMessageContaining("result exceeds maximum allowed size"); + } + assertThatScriptIsBlocked(scriptId); + } + + private void assertThatScriptIsBlocked(UUID scriptId) { + assertThatThrownBy(() -> { + invokeScript(scriptId, "{}"); + }).hasMessageContaining("invocation is blocked due to maximum error"); + } + + private UUID evalScript(String script) throws ExecutionException, InterruptedException { + return invokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, script, "msg", "metadata", "msgType").get(); + } + + private String invokeScript(UUID scriptId, String str) throws ExecutionException, InterruptedException { + var msg = JacksonUtil.fromString(str, Map.class); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/service/script/LocalJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java similarity index 65% rename from application/src/test/java/org/thingsboard/server/service/script/LocalJsInvokeServiceTest.java rename to application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 039a69c01f..6703252749 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/LocalJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java @@ -15,16 +15,22 @@ */ package org.thingsboard.server.service.script; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.test.context.TestPropertySource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.script.api.ScriptType; +import org.thingsboard.script.api.js.NashornJsInvokeService; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; import java.util.UUID; import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -35,14 +41,38 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; "js.max_result_size=50", "js.local.max_errors=2" }) -class LocalJsInvokeServiceTest extends AbstractControllerTest { +class NashornJsInvokeServiceTest extends AbstractControllerTest { @Autowired - private NashornJsInvokeService jsInvokeService; + private NashornJsInvokeService invokeService; @Value("${js.local.max_errors}") private int maxJsErrors; + @Test + void givenSimpleScriptTestPerformance() throws ExecutionException, InterruptedException { + int iterations = 1000; + UUID scriptId = evalScript("return msg.temperature > 20"); + // warmup + ObjectNode msg = JacksonUtil.newObjectNode(); + for (int i = 0; i < 100; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long startTs = System.currentTimeMillis(); + for (int i = 0; i < iterations; i++) { + msg.put("temperature", i); + boolean expected = i > 20; + boolean result = Boolean.valueOf(invokeScript(scriptId, JacksonUtil.toString(msg))); + Assert.assertEquals(expected, result); + } + long duration = System.currentTimeMillis() - startTs; + System.out.println(iterations + " invocations took: " + duration + "ms"); + Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(3)); + } + @Test void givenTooBigScriptForEval_thenReturnError() { String hugeScript = "var a = 'qwertyqwertywertyqwabababer'; return {a: a};"; @@ -86,11 +116,11 @@ class LocalJsInvokeServiceTest extends AbstractControllerTest { } private UUID evalScript(String script) throws ExecutionException, InterruptedException { - return jsInvokeService.eval(TenantId.SYS_TENANT_ID, JsScriptType.RULE_NODE_SCRIPT, script).get(); + return invokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, script).get(); } private String invokeScript(UUID scriptId, String msg) throws ExecutionException, InterruptedException { - return jsInvokeService.invokeFunction(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get(); + return invokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, msg, "{}", "POST_TELEMETRY_REQUEST").get().toString(); } } diff --git a/application/src/test/java/org/thingsboard/server/service/script/RemoteJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/RemoteJsInvokeServiceTest.java index 30b9b0b2c0..636af5432e 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/RemoteJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/RemoteJsInvokeServiceTest.java @@ -21,19 +21,21 @@ import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.mockito.ArgumentCaptor; +import org.thingsboard.script.api.ScriptType; import org.thingsboard.server.common.data.ApiUsageState; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsRequest; import org.thingsboard.server.gen.js.JsInvokeProtos.RemoteJsResponse; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; -import org.thingsboard.server.service.apiusage.TbApiUsageStateService; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.UUID; @@ -57,13 +59,13 @@ class RemoteJsInvokeServiceTest { @BeforeEach public void beforeEach() { - TbApiUsageStateService apiUsageStateService = mock(TbApiUsageStateService.class); + TbApiUsageStateClient apiUsageStateClient = mock(TbApiUsageStateClient.class); ApiUsageState apiUsageState = mock(ApiUsageState.class); when(apiUsageState.isJsExecEnabled()).thenReturn(true); - when(apiUsageStateService.getApiUsageState(any())).thenReturn(apiUsageState); - TbApiUsageClient apiUsageClient = mock(TbApiUsageClient.class); + when(apiUsageStateClient.getApiUsageState(any())).thenReturn(apiUsageState); + TbApiUsageReportClient apiUsageReportClient = mock(TbApiUsageReportClient.class); - remoteJsInvokeService = new RemoteJsInvokeService(apiUsageStateService, apiUsageClient); + remoteJsInvokeService = new RemoteJsInvokeService(Optional.of(apiUsageStateClient), Optional.of(apiUsageReportClient)); jsRequestTemplate = mock(TbQueueRequestTemplate.class); remoteJsInvokeService.requestTemplate = jsRequestTemplate; } @@ -77,7 +79,7 @@ class RemoteJsInvokeServiceTest { public void whenInvokingFunction_thenDoNotSendScriptBody() throws Exception { mockJsEvalResponse(); String scriptBody = "return { a: 'b'};"; - UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, JsScriptType.RULE_NODE_SCRIPT, scriptBody).get(); + UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get(); reset(jsRequestTemplate); String expectedInvocationResult = "scriptInvocationResult"; @@ -90,7 +92,7 @@ class RemoteJsInvokeServiceTest { .when(jsRequestTemplate).send(any()); ArgumentCaptor> jsRequestCaptor = ArgumentCaptor.forClass(TbProtoJsQueueMsg.class); - Object invocationResult = remoteJsInvokeService.invokeFunction(TenantId.SYS_TENANT_ID, null, scriptId, "{}").get(); + Object invocationResult = remoteJsInvokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, "{}").get(); verify(jsRequestTemplate).send(jsRequestCaptor.capture()); JsInvokeProtos.JsInvokeRequest jsInvokeRequestMade = jsRequestCaptor.getValue().getValue().getInvokeRequest(); @@ -103,7 +105,7 @@ class RemoteJsInvokeServiceTest { public void whenInvokingFunctionAndRemoteJsExecutorRemovedScript_thenHandleNotFoundErrorAndMakeInvokeRequestWithScriptBody() throws Exception { mockJsEvalResponse(); String scriptBody = "return { a: 'b'};"; - UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, JsScriptType.RULE_NODE_SCRIPT, scriptBody).get(); + UUID scriptId = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get(); reset(jsRequestTemplate); doReturn(Futures.immediateFuture(new TbProtoJsQueueMsg<>(UUID.randomUUID(), RemoteJsResponse.newBuilder() @@ -128,7 +130,7 @@ class RemoteJsInvokeServiceTest { })); ArgumentCaptor> jsRequestsCaptor = ArgumentCaptor.forClass(TbProtoJsQueueMsg.class); - Object invocationResult = remoteJsInvokeService.invokeFunction(TenantId.SYS_TENANT_ID, null, scriptId, "{}").get(); + Object invocationResult = remoteJsInvokeService.invokeScript(TenantId.SYS_TENANT_ID, null, scriptId, "{}").get(); verify(jsRequestTemplate, times(2)).send(jsRequestsCaptor.capture()); List> jsInvokeRequestsMade = jsRequestsCaptor.getAllValues(); @@ -159,19 +161,19 @@ class RemoteJsInvokeServiceTest { Set scriptHashes = new HashSet<>(); String tenant1Script1Hash = null; for (int i = 0; i < 3; i++) { - UUID scriptUuid = remoteJsInvokeService.eval(tenantId1, JsScriptType.RULE_NODE_SCRIPT, scriptBody1).get(); + UUID scriptUuid = remoteJsInvokeService.eval(tenantId1, ScriptType.RULE_NODE_SCRIPT, scriptBody1).get(); tenant1Script1Hash = getScriptHash(scriptUuid); scriptHashes.add(tenant1Script1Hash); } assertThat(scriptHashes).as("Unique scripts ids").size().isOne(); TenantId tenantId2 = TenantId.fromUUID(UUID.randomUUID()); - UUID scriptUuid = remoteJsInvokeService.eval(tenantId2, JsScriptType.RULE_NODE_SCRIPT, scriptBody1).get(); + UUID scriptUuid = remoteJsInvokeService.eval(tenantId2, ScriptType.RULE_NODE_SCRIPT, scriptBody1).get(); String tenant2Script1Id = getScriptHash(scriptUuid); assertThat(tenant2Script1Id).isNotEqualTo(tenant1Script1Hash); String scriptBody2 = scriptBody1 + ";;"; - scriptUuid = remoteJsInvokeService.eval(tenantId2, JsScriptType.RULE_NODE_SCRIPT, scriptBody2).get(); + scriptUuid = remoteJsInvokeService.eval(tenantId2, ScriptType.RULE_NODE_SCRIPT, scriptBody2).get(); String tenant2Script2Id = getScriptHash(scriptUuid); assertThat(tenant2Script2Id).isNotEqualTo(tenant2Script1Id); } @@ -180,8 +182,8 @@ class RemoteJsInvokeServiceTest { public void whenReleasingScript_thenCheckForHashUsages() throws Exception { mockJsEvalResponse(); String scriptBody = "return { a: 'b'};"; - UUID scriptId1 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, JsScriptType.RULE_NODE_SCRIPT, scriptBody).get(); - UUID scriptId2 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, JsScriptType.RULE_NODE_SCRIPT, scriptBody).get(); + UUID scriptId1 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get(); + UUID scriptId2 = remoteJsInvokeService.eval(TenantId.SYS_TENANT_ID, ScriptType.RULE_NODE_SCRIPT, scriptBody).get(); String scriptHash = getScriptHash(scriptId1); assertThat(scriptHash).isEqualTo(getScriptHash(scriptId2)); reset(jsRequestTemplate); @@ -203,7 +205,7 @@ class RemoteJsInvokeServiceTest { } private String getScriptHash(UUID scriptUuid) { - return remoteJsInvokeService.scriptIdToNameAndHashMap.get(scriptUuid).getSecond(); + return remoteJsInvokeService.getScriptHash(scriptUuid); } private void mockJsEvalResponse() { diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java index f804d4dcfd..c84253b92c 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/TokenOutdatingTest.java @@ -15,19 +15,29 @@ */ package org.thingsboard.server.service.security.auth; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.springframework.cache.concurrent.ConcurrentMapCacheManager; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootContextLoader; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.context.ApplicationEventPublisher; +import org.springframework.context.annotation.ComponentScan; import org.springframework.security.authentication.CredentialsExpiredException; -import org.thingsboard.server.common.data.CacheConstants; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; import org.thingsboard.server.common.data.User; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; +import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; +import org.thingsboard.server.common.data.security.event.UserSessionInvalidationEvent; import org.thingsboard.server.common.data.security.model.JwtToken; -import org.thingsboard.server.config.JwtSettings; import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.service.DaoSqlTest; import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.security.auth.jwt.JwtAuthenticationProvider; import org.thingsboard.server.service.security.auth.jwt.RefreshTokenAuthenticationProvider; @@ -39,13 +49,9 @@ import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; import java.util.UUID; -import static java.util.concurrent.TimeUnit.DAYS; -import static java.util.concurrent.TimeUnit.MINUTES; import static java.util.concurrent.TimeUnit.SECONDS; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -53,31 +59,37 @@ import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; +@ActiveProfiles("test") +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = TokenOutdatingTest.class, loader = SpringBootContextLoader.class) +@DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS) +@ComponentScan({"org.thingsboard.server"}) +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@DaoSqlTest +@TestPropertySource(properties = { + "security.jwt.tokenIssuer=test.io", + "security.jwt.tokenSigningKey=secret", + "security.jwt.tokenExpirationTime=600", + "security.jwt.refreshTokenExpTime=15", + // explicitly set the wrong value to check that it is NOT used. + "cache.specs.userSessionsInvalidation.timeToLiveInMinutes=2" +}) public class TokenOutdatingTest { private JwtAuthenticationProvider accessTokenAuthenticationProvider; private RefreshTokenAuthenticationProvider refreshTokenAuthenticationProvider; + @Autowired private TokenOutdatingService tokenOutdatingService; - private ConcurrentMapCacheManager cacheManager; + @Autowired + private ApplicationEventPublisher eventPublisher; + @Autowired private JwtTokenFactory tokenFactory; - private JwtSettings jwtSettings; + private SecurityUser securityUser; - private UserId userId; - - @BeforeEach + @Before public void setUp() { - jwtSettings = new JwtSettings(); - jwtSettings.setTokenIssuer("test.io"); - jwtSettings.setTokenExpirationTime((int) MINUTES.toSeconds(10)); - jwtSettings.setRefreshTokenExpTime((int) DAYS.toSeconds(7)); - jwtSettings.setTokenSigningKey("secret"); - tokenFactory = new JwtTokenFactory(jwtSettings); - - cacheManager = new ConcurrentMapCacheManager(); - tokenOutdatingService = new TokenOutdatingService(cacheManager, tokenFactory, jwtSettings); - tokenOutdatingService.initCache(); - - userId = new UserId(UUID.randomUUID()); + UserId userId = new UserId(UUID.randomUUID()); + securityUser = createMockSecurityUser(userId); UserService userService = mock(UserService.class); @@ -97,28 +109,29 @@ public class TokenOutdatingTest { @Test public void testOutdateOldUserTokens() throws Exception { - JwtToken jwtToken = createAccessJwtToken(userId); + JwtToken jwtToken = tokenFactory.createAccessJwtToken(securityUser); - SECONDS.sleep(1); // need to wait before outdating so that outdatage time is strictly after token issue time - tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); - assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId)); + // Token outdatage time is rounded to 1 sec. Need to wait before outdating so that outdatage time is strictly after token issue time + SECONDS.sleep(1); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); + assertTrue(tokenOutdatingService.isOutdated(jwtToken, securityUser.getId())); SECONDS.sleep(1); - JwtToken newJwtToken = tokenFactory.createAccessJwtToken(createMockSecurityUser(userId)); - assertFalse(tokenOutdatingService.isOutdated(newJwtToken, userId)); + JwtToken newJwtToken = tokenFactory.createAccessJwtToken(securityUser); + assertFalse(tokenOutdatingService.isOutdated(newJwtToken, securityUser.getId())); } @Test public void testAuthenticateWithOutdatedAccessToken() throws InterruptedException { - RawAccessJwtToken accessJwtToken = getRawJwtToken(createAccessJwtToken(userId)); + RawAccessJwtToken accessJwtToken = getRawJwtToken(tokenFactory.createAccessJwtToken(securityUser)); assertDoesNotThrow(() -> { accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken)); }); SECONDS.sleep(1); - tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); assertThrows(JwtExpiredTokenException.class, () -> { accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(accessJwtToken)); @@ -127,49 +140,94 @@ public class TokenOutdatingTest { @Test public void testAuthenticateWithOutdatedRefreshToken() throws InterruptedException { - RawAccessJwtToken refreshJwtToken = getRawJwtToken(createRefreshJwtToken(userId)); + RawAccessJwtToken refreshJwtToken = getRawJwtToken(tokenFactory.createRefreshToken(securityUser)); assertDoesNotThrow(() -> { refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken)); }); SECONDS.sleep(1); - tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); assertThrows(CredentialsExpiredException.class, () -> { refreshTokenAuthenticationProvider.authenticate(new RefreshAuthenticationToken(refreshJwtToken)); }); } + // This test takes too long to run and is basically testing the cache logic +// @Test +// public void testTokensOutdatageTimeRemovalFromCache() throws Exception { +// JwtToken jwtToken = tokenFactory.createAccessJwtToken(securityUser); +// +// SECONDS.sleep(1); +// eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); +// +// SECONDS.sleep(1); +// +// assertTrue(tokenOutdatingService.isOutdated(jwtToken, securityUser.getId())); +// +// SECONDS.sleep(30); // refreshTokenExpTime/2 +// +// assertTrue(tokenOutdatingService.isOutdated(jwtToken, securityUser.getId())); +// +// SECONDS.sleep(30 + 1); // refreshTokenExpTime/2 + 1 +// +// assertFalse(tokenOutdatingService.isOutdated(jwtToken, securityUser.getId())); +// } + @Test - public void testTokensOutdatageTimeRemovalFromCache() throws Exception { - JwtToken jwtToken = createAccessJwtToken(userId); + public void testOnlyOneTokenExpired() throws InterruptedException { + JwtToken jwtToken = tokenFactory.createAccessJwtToken(securityUser); - SECONDS.sleep(1); - tokenOutdatingService.onUserAuthDataChanged(new UserAuthDataChangedEvent(userId)); + SecurityUser anotherSecurityUser = new SecurityUser(securityUser, securityUser.isEnabled(), securityUser.getUserPrincipal()); + JwtToken anotherJwtToken = tokenFactory.createAccessJwtToken(anotherSecurityUser); - int refreshTokenExpirationTime = 3; - jwtSettings.setRefreshTokenExpTime(refreshTokenExpirationTime); + assertDoesNotThrow(() -> { + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(getRawJwtToken(jwtToken))); + }); - SECONDS.sleep(refreshTokenExpirationTime - 2); + SECONDS.sleep(1); - assertTrue(tokenOutdatingService.isOutdated(jwtToken, userId)); - assertNotNull(cacheManager.getCache(CacheConstants.USERS_UPDATE_TIME_CACHE).get(userId.getId().toString())); + eventPublisher.publishEvent(new UserSessionInvalidationEvent(securityUser.getSessionId())); - SECONDS.sleep(3); + assertThrows(JwtExpiredTokenException.class, () -> { + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(getRawJwtToken(jwtToken))); + }); - assertFalse(tokenOutdatingService.isOutdated(jwtToken, userId)); - assertNull(cacheManager.getCache(CacheConstants.USERS_UPDATE_TIME_CACHE).get(userId.getId().toString())); + assertDoesNotThrow(() -> { + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(getRawJwtToken(anotherJwtToken))); + }); } - private JwtToken createAccessJwtToken(UserId userId) { - return tokenFactory.createAccessJwtToken(createMockSecurityUser(userId)); - } + @Test + public void testResetAllSessions() throws InterruptedException { + JwtToken jwtToken = tokenFactory.createAccessJwtToken(securityUser); + + SecurityUser anotherSecurityUser = new SecurityUser(securityUser, securityUser.isEnabled(), securityUser.getUserPrincipal()); + JwtToken anotherJwtToken = tokenFactory.createAccessJwtToken(anotherSecurityUser); - private JwtToken createRefreshJwtToken(UserId userId) { - return tokenFactory.createRefreshToken(createMockSecurityUser(userId)); + assertDoesNotThrow(() -> { + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(getRawJwtToken(jwtToken))); + }); + + assertDoesNotThrow(() -> { + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(getRawJwtToken(anotherJwtToken))); + }); + + SECONDS.sleep(1); + + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(securityUser.getId())); + + assertThrows(JwtExpiredTokenException.class, () -> { + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(getRawJwtToken(jwtToken))); + }); + + assertThrows(JwtExpiredTokenException.class, () -> { + accessTokenAuthenticationProvider.authenticate(new JwtAuthenticationToken(getRawJwtToken(anotherJwtToken))); + }); } + private RawAccessJwtToken getRawJwtToken(JwtToken token) { return new RawAccessJwtToken(token.getToken()); } @@ -180,6 +238,7 @@ public class TokenOutdatingTest { securityUser.setUserPrincipal(new UserPrincipal(UserPrincipal.Type.USER_NAME, securityUser.getEmail())); securityUser.setAuthority(Authority.CUSTOMER_USER); securityUser.setId(userId); + securityUser.setSessionId(UUID.randomUUID().toString()); return securityUser; } } diff --git a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java index d5d74f2512..868889a6cc 100644 --- a/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/state/DefaultDeviceStateServiceTest.java @@ -15,27 +15,37 @@ */ package org.thingsboard.server.service.state; +import org.junit.Assert; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.server.cluster.TbClusterService; +import org.thingsboard.server.common.data.DeviceIdInfo; import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.query.EntityData; +import org.thingsboard.server.common.data.query.EntityKeyType; +import org.thingsboard.server.common.data.query.TsValue; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.device.DeviceService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.queue.discovery.PartitionService; -import org.thingsboard.server.cluster.TbClusterService; import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; +import java.util.Map; +import java.util.UUID; + import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; +import static org.thingsboard.server.service.state.DefaultDeviceStateService.INACTIVITY_TIMEOUT; @RunWith(MockitoJUnitRunner.class) public class DefaultDeviceStateServiceTest { @@ -83,4 +93,40 @@ public class DefaultDeviceStateServiceTest { Mockito.verify(service, times(1)).fetchDeviceStateDataUsingEntityDataQuery(deviceId); } + @Test + public void givenPersistToTelemetryAndDefaultInactivityTimeoutFetched_whenTransformingToDeviceStateData_thenTryGetInactivityFromAttribute() { + var defaultInactivityTimeoutInSec = 60L; + var latest = + Map.of( + EntityKeyType.TIME_SERIES, Map.of(INACTIVITY_TIMEOUT, new TsValue(0, Long.toString(defaultInactivityTimeoutInSec * 1000))), + EntityKeyType.SERVER_ATTRIBUTE, Map.of(INACTIVITY_TIMEOUT, new TsValue(0, Long.toString(5000L))) + ); + + process(latest, defaultInactivityTimeoutInSec); + } + + @Test + public void givenPersistToTelemetryAndNoInactivityTimeoutFetchedFromTimeSeries_whenTransformingToDeviceStateData_thenTryGetInactivityFromAttribute() { + var defaultInactivityTimeoutInSec = 60L; + var latest = + Map.of( + EntityKeyType.SERVER_ATTRIBUTE, Map.of(INACTIVITY_TIMEOUT, new TsValue(0, Long.toString(5000L))) + ); + + process(latest, defaultInactivityTimeoutInSec); + } + + private void process(Map> latest, long defaultInactivityTimeoutInSec) { + service.setDefaultInactivityTimeoutInSec(defaultInactivityTimeoutInSec); + service.setDefaultInactivityTimeoutMs(defaultInactivityTimeoutInSec * 1000); + service.setPersistToTelemetry(true); + + var deviceUuid = UUID.randomUUID(); + var deviceId = new DeviceId(deviceUuid); + + DeviceStateData deviceStateData = service.toDeviceStateData(new EntityData(deviceId, latest, Map.of()), new DeviceIdInfo(TenantId.SYS_TENANT_ID.getId(), UUID.randomUUID(), deviceUuid)); + + Assert.assertEquals(5000L, deviceStateData.getState().getInactivityTimeout()); + } + } \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/system/BaseRestApiLimitsTest.java b/application/src/test/java/org/thingsboard/server/system/BaseRestApiLimitsTest.java new file mode 100644 index 0000000000..2f7efae923 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/system/BaseRestApiLimitsTest.java @@ -0,0 +1,196 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.system; + +import com.fasterxml.jackson.core.type.TypeReference; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.Uninterruptibles; +import lombok.extern.slf4j.Slf4j; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.springframework.test.web.servlet.ResultActions; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileConfiguration; +import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; +import org.thingsboard.server.controller.AbstractControllerTest; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * @author Illia Barkov + */ + +@Slf4j +public abstract class BaseRestApiLimitsTest extends AbstractControllerTest { + + private static int MESSAGES_LIMIT = 10; + private static int TIME_FOR_LIMIT = 5; + + TenantProfile tenantProfile; + + ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newFixedThreadPool(10)); + + @Before + public void before() throws Exception { + loginSysAdmin(); + tenantProfile = getDefaultTenantProfile(); + resetTokens(); + } + + @After + public void after() throws Exception { + resetTokens(); + loginSysAdmin(); + saveTenantProfileWitConfiguration(tenantProfile, new DefaultTenantProfileConfiguration()); + resetTokens(); + service.shutdown(); + } + + @Test + public void testCustomerRestApiLimits() throws Exception { + loginSysAdmin(); + + String customerRestLimit = MESSAGES_LIMIT + ":" + TIME_FOR_LIMIT; + + DefaultTenantProfileConfiguration configurationWithCustomerRestLimits = createTenantProfileConfigurationWithRestLimits(null, customerRestLimit); + + saveTenantProfileWitConfiguration(tenantProfile, configurationWithCustomerRestLimits); + + resetTokens(); + + loginCustomerUser(); + + for (int i = 0; i < MESSAGES_LIMIT; i++) { + doGet("/api/device/types").andExpect(status().isOk()); + } + doGet("/api/device/types").andExpect(status().is4xxClientError()); + } + + @Test + public void testTenantRestApiLimits() throws Exception { + loginSysAdmin(); + + String tenantRestLimit = MESSAGES_LIMIT + ":" + TIME_FOR_LIMIT; + + DefaultTenantProfileConfiguration configurationWithTenantRestLimits = createTenantProfileConfigurationWithRestLimits(tenantRestLimit, null); + + saveTenantProfileWitConfiguration(tenantProfile, configurationWithTenantRestLimits); + + resetTokens(); + + loginCustomerUser(); + + for (int i = 0; i < MESSAGES_LIMIT; i++) { + doGet("/api/device/types").andExpect(status().isOk()); + } + doGet("/api/device/types").andExpect(status().is4xxClientError()); + } + + @Test + public void testCustomerRestApiLimitsWithAsyncMethod() throws Exception { + loginSysAdmin(); + + String tenantRestLimit = MESSAGES_LIMIT + ":" + TIME_FOR_LIMIT; + + DefaultTenantProfileConfiguration configurationWithTenantRestLimits = createTenantProfileConfigurationWithRestLimits(tenantRestLimit, null); + + saveTenantProfileWitConfiguration(tenantProfile, configurationWithTenantRestLimits); + + resetTokens(); + + loginTenantAdmin(); + + List> attributesRequests = new ArrayList<>(); + + doGet("/api/plugins/telemetry/" + tenantId.getEntityType() + "/" + tenantId.getId().toString() + "/values/attributes").andExpect(status().isOk()); + Thread.sleep(TimeUnit.SECONDS.toMillis(TIME_FOR_LIMIT)); // Wait to initialization for bucket4j + + for (int i = 0; i < MESSAGES_LIMIT; i++) { + attributesRequests.add(service.submit(() -> doGet("/api/plugins/telemetry/" + tenantId.getEntityType() + "/" + tenantId.getId().toString() + "/values/attributes"))); + } + + List lists = blockForResponses(attributesRequests); + + for (ResultActions resultActions : lists) { + resultActions.andExpect(status().isOk()); + } + + doGet("/api/plugins/telemetry/" + tenantId.getEntityType() + "/" + tenantId.getId().toString() + "/values/attributes").andExpect(status().is4xxClientError()); + } + + private TenantProfile getDefaultTenantProfile() throws Exception { + + PageLink pageLink = new PageLink(17); + PageData pageData = doGetTypedWithPageLink("/api/tenantProfiles?", + new TypeReference<>(){}, pageLink); + Assert.assertFalse(pageData.hasNext()); + Assert.assertEquals(1, pageData.getTotalElements()); + List tenantProfiles = new ArrayList<>(pageData.getData()); + + Optional optionalDefaultProfile = tenantProfiles.stream().filter(TenantProfile::isDefault).reduce((a, b) -> null); + Assert.assertTrue(optionalDefaultProfile.isPresent()); + + return optionalDefaultProfile.get(); + } + + List blockForResponses(List> futures) throws ExecutionException { + ListenableFuture> futureOfList = Futures.allAsList(futures); + List responses; + try { + responses = futureOfList.get(20, TimeUnit.SECONDS); + } catch (TimeoutException | InterruptedException | ExecutionException e) { + responses = new ArrayList<>(); + for (ListenableFuture future : futures) { + if (future.isDone()) { + responses.add(Uninterruptibles.getUninterruptibly(future)); + } + } + } + return responses; + } + + private DefaultTenantProfileConfiguration createTenantProfileConfigurationWithRestLimits(String tenantLimits, String customerLimits) { + DefaultTenantProfileConfiguration.DefaultTenantProfileConfigurationBuilder builder = DefaultTenantProfileConfiguration.builder(); + builder.tenantServerRestLimitsConfiguration(tenantLimits); + builder.customerServerRestLimitsConfiguration(customerLimits); + return builder.build(); + + } + + private void saveTenantProfileWitConfiguration(TenantProfile tenantProfile, TenantProfileConfiguration tenantProfileConfiguration) { + TenantProfileData tenantProfileData = tenantProfile.getProfileData(); + tenantProfileData.setConfiguration(tenantProfileConfiguration); + TenantProfile savedTenantProfile = doPost("/api/tenantProfile", tenantProfile, TenantProfile.class); + Assert.assertNotNull(savedTenantProfile); + } + +} diff --git a/application/src/test/java/org/thingsboard/server/system/sql/RestApiLimitsSqlTest.java b/application/src/test/java/org/thingsboard/server/system/sql/RestApiLimitsSqlTest.java new file mode 100644 index 0000000000..5aa84b6d26 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/system/sql/RestApiLimitsSqlTest.java @@ -0,0 +1,24 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.system.sql; + +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.system.BaseRestApiLimitsTest; + + +@DaoSqlTest +public class RestApiLimitsSqlTest extends BaseRestApiLimitsTest { +} diff --git a/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java index 255057cc2f..caaf5be6de 100644 --- a/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java +++ b/application/src/test/java/org/thingsboard/server/transport/TransportNoSqlTestSuite.java @@ -35,6 +35,7 @@ public class TransportNoSqlTestSuite { public static CustomCassandraCQLUnit cassandraUnit = new CustomCassandraCQLUnit( Arrays.asList( + new ClassPathCQLDataSet("cassandra/schema-keyspace.cql", false, false), new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), new ClassPathCQLDataSet("cassandra/schema-ts-latest.cql", false, false) ), diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java index cd9ed239a5..7617f2b826 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/attributes/AbstractCoapAttributesIntegrationTest.java @@ -27,6 +27,7 @@ import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; import org.thingsboard.server.common.data.device.profile.DefaultCoapDeviceTypeConfiguration; @@ -124,7 +125,6 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap } private byte[] getAttributesProtoPayloadBytes() { - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; @@ -134,8 +134,8 @@ public abstract class AbstractCoapAttributesIntegrationTest extends AbstractCoap TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); - DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema()); + DynamicSchema attributesSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java index 2d034d3b41..41001427a5 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/rpc/AbstractCoapServerSideRpcIntegrationTest.java @@ -28,6 +28,7 @@ import org.eclipse.californium.core.CoapResponse; import org.eclipse.californium.core.coap.CoAP; import org.eclipse.californium.core.coap.MediaTypeRegistry; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; @@ -158,8 +159,8 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC protected void processOnLoadProtoResponse(CoapResponse response, CoapTestClient client, Integer observe, CountDownLatch latch) { ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); - ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA); - DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); + ProtoFileElement rpcRequestProtoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema()); + DynamicSchema rpcRequestProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcRequestProtoFileElement, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); byte[] requestPayload = response.getPayload(); DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg"); @@ -168,8 +169,8 @@ public abstract class AbstractCoapServerSideRpcIntegrationTest extends AbstractC DynamicMessage dynamicMessage = DynamicMessage.parseFrom(rpcRequestMsgDescriptor, requestPayload); Descriptors.FieldDescriptor requestIdDescriptor = rpcRequestMsgDescriptor.findFieldByName("requestId"); int requestId = (int) dynamicMessage.getField(requestIdDescriptor); - ProtoFileElement rpcResponseProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA); - DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); + ProtoFileElement rpcResponseProtoSchemaFile = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema()); + DynamicSchema rpcResponseProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcResponseProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg"); Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType(); DynamicMessage rpcResponseMsg = rpcResponseBuilder diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesProtoIntegrationTest.java index 539f62f614..609a5fe672 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/attributes/CoapAttributesProtoIntegrationTest.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; @@ -55,18 +56,7 @@ public class CoapAttributesProtoIntegrationTest extends CoapAttributesIntegratio @Test public void testPushAttributes() throws Exception { - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); - CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; - CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); - assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); - DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); - assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); - DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); - + DynamicSchema attributesSchema = getDynamicSchema(); DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); assertNotNull(nestedJsonObjectBuilderDescriptor); @@ -98,18 +88,7 @@ public class CoapAttributesProtoIntegrationTest extends CoapAttributesIntegratio @Test public void testPushAttributesWithExplicitPresenceProtoKeys() throws Exception { - DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); - assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); - CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; - CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); - assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); - DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; - TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); - assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); - ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); - DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); - + DynamicSchema attributesSchema = getDynamicSchema(); DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); assertNotNull(nestedJsonObjectBuilderDescriptor); @@ -135,4 +114,19 @@ public class CoapAttributesProtoIntegrationTest extends CoapAttributesIntegratio processAttributesTest(Arrays.asList("key1", "key5"), postAttributesMsg.toByteArray(), true); } + private DynamicSchema getDynamicSchema() { + DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); + assertTrue(transportConfiguration instanceof CoapDeviceProfileTransportConfiguration); + CoapDeviceProfileTransportConfiguration coapTransportConfiguration = (CoapDeviceProfileTransportConfiguration) transportConfiguration; + CoapDeviceTypeConfiguration coapDeviceTypeConfiguration = coapTransportConfiguration.getCoapDeviceTypeConfiguration(); + assertTrue(coapDeviceTypeConfiguration instanceof DefaultCoapDeviceTypeConfiguration); + DefaultCoapDeviceTypeConfiguration defaultCoapDeviceTypeConfiguration = (DefaultCoapDeviceTypeConfiguration) coapDeviceTypeConfiguration; + TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); + assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); + ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; + String deviceAttributesProtoSchema = protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema(); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(deviceAttributesProtoSchema); + return DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + } + } diff --git a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java index fb6d8aa0da..536155f1cf 100644 --- a/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/coap/telemetry/timeseries/AbstractCoapTimeseriesProtoIntegrationTest.java @@ -23,6 +23,8 @@ import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.CoapDeviceType; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.CoapDeviceTypeConfiguration; @@ -63,8 +65,9 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); - DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema(); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(deviceTelemetryProtoSchema); + DynamicSchema telemetrySchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.TELEMETRY_PROTO_SCHEMA); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); @@ -138,8 +141,9 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); - DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema(); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(deviceTelemetryProtoSchema); + DynamicSchema telemetrySchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.TELEMETRY_PROTO_SCHEMA); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); @@ -198,8 +202,9 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); - DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema(); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(deviceTelemetryProtoSchema); + DynamicSchema telemetrySchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.TELEMETRY_PROTO_SCHEMA); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); @@ -271,8 +276,9 @@ public abstract class AbstractCoapTimeseriesProtoIntegrationTest extends Abstrac TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = defaultCoapDeviceTypeConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(schemaStr); - DynamicSchema telemetrySchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema(); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(deviceTelemetryProtoSchema); + DynamicSchema telemetrySchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.TELEMETRY_PROTO_SCHEMA); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java index 6d030aca9f..56d9ef5e9e 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/attributes/AbstractMqttAttributesIntegrationTest.java @@ -25,6 +25,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.test.context.TestPropertySource; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; @@ -494,8 +495,8 @@ public abstract class AbstractMqttAttributesIntegrationTest extends AbstractMqtt TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(ATTRIBUTES_SCHEMA_STR); - DynamicSchema attributesSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema()); + DynamicSchema attributesSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); DynamicMessage.Builder nestedJsonObjectBuilder = attributesSchema.newMessageBuilder("PostAttributes.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index 067af192e4..2cdfb76645 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -29,6 +29,7 @@ import org.eclipse.paho.client.mqttv3.MqttException; import org.eclipse.paho.client.mqttv3.MqttMessage; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.DeviceTransportType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; @@ -382,8 +383,8 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM protected byte[] processProtoMessageArrived(String requestTopic, MqttMessage mqttMessage) throws MqttException, InvalidProtocolBufferException { if (requestTopic.startsWith(BASE_DEVICE_API_TOPIC) || requestTopic.startsWith(BASE_DEVICE_API_TOPIC_V2)) { ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = getProtoTransportPayloadConfiguration(); - ProtoFileElement rpcRequestProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(RPC_REQUEST_PROTO_SCHEMA); - DynamicSchema rpcRequestProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(rpcRequestProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); + ProtoFileElement rpcRequestProtoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcRequestProtoSchema()); + DynamicSchema rpcRequestProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcRequestProtoFileElement, ProtoTransportPayloadConfiguration.RPC_REQUEST_PROTO_SCHEMA); byte[] requestPayload = mqttMessage.getPayload(); DynamicMessage.Builder rpcRequestMsg = rpcRequestProtoSchema.newMessageBuilder("RpcRequestMsg"); @@ -395,8 +396,8 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractM for (Descriptors.FieldDescriptor fieldDescriptor: fields) { assertTrue(dynamicMessage.hasField(fieldDescriptor)); } - ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_RPC_RESPONSE_PROTO_SCHEMA); - DynamicSchema rpcResponseProtoSchema = protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); + ProtoFileElement rpcResponseProtoFileElement = DynamicProtoUtils.getProtoFileElement(protoTransportPayloadConfiguration.getDeviceRpcResponseProtoSchema()); + DynamicSchema rpcResponseProtoSchema = DynamicProtoUtils.getDynamicSchema(rpcResponseProtoFileElement, ProtoTransportPayloadConfiguration.RPC_RESPONSE_PROTO_SCHEMA); DynamicMessage.Builder rpcResponseBuilder = rpcResponseProtoSchema.newMessageBuilder("RpcResponseMsg"); Descriptors.Descriptor rpcResponseMsgDescriptor = rpcResponseBuilder.getDescriptorForType(); diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java index 4904ed7a5d..7b2fa1143d 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/attributes/MqttAttributesProtoIntegrationTest.java @@ -22,6 +22,7 @@ import com.squareup.wire.schema.internal.parser.ProtoFileElement; import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; @@ -176,8 +177,9 @@ public class MqttAttributesProtoIntegrationTest extends MqttAttributesIntegratio TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchemaFile = protoTransportPayloadConfiguration.getTransportProtoSchema(DEVICE_ATTRIBUTES_PROTO_SCHEMA); - return protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchemaFile, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); + String deviceAttributesProtoSchema = protoTransportPayloadConfiguration.getDeviceAttributesProtoSchema(); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(deviceAttributesProtoSchema); + return DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.ATTRIBUTES_PROTO_SCHEMA); } private DynamicMessage getDefaultDynamicMessage() { diff --git a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java index 95de5f0069..9c92e3f4f0 100644 --- a/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/transport/mqtt/telemetry/timeseries/AbstractMqttTimeseriesProtoIntegrationTest.java @@ -23,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.junit.Before; import org.junit.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.TransportPayloadType; import org.thingsboard.server.common.data.device.profile.DeviceProfileTransportConfiguration; import org.thingsboard.server.common.data.device.profile.MqttDeviceProfileTransportConfiguration; @@ -117,7 +118,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac .telemetryProtoSchema(schemaStr) .build(); processBeforeTest(configProperties); - DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); + DynamicSchema telemetrySchema = getDynamicSchema(); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); @@ -167,7 +168,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac .telemetryTopicFilter(POST_DATA_TELEMETRY_TOPIC) .build(); processBeforeTest(configProperties); - DynamicSchema telemetrySchema = getDynamicSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = getDynamicSchema(); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); @@ -230,7 +231,7 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac .telemetryProtoSchema(schemaStr) .build(); processBeforeTest(configProperties); - DynamicSchema telemetrySchema = getDynamicSchema(schemaStr); + DynamicSchema telemetrySchema = getDynamicSchema(); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); @@ -458,19 +459,20 @@ public abstract class AbstractMqttTimeseriesProtoIntegrationTest extends Abstrac assertFalse(callback.isPubAckReceived()); } - private DynamicSchema getDynamicSchema(String deviceTelemetryProtoSchema) { + private DynamicSchema getDynamicSchema() { DeviceProfileTransportConfiguration transportConfiguration = deviceProfile.getProfileData().getTransportConfiguration(); assertTrue(transportConfiguration instanceof MqttDeviceProfileTransportConfiguration); MqttDeviceProfileTransportConfiguration mqttTransportConfiguration = (MqttDeviceProfileTransportConfiguration) transportConfiguration; TransportPayloadTypeConfiguration transportPayloadTypeConfiguration = mqttTransportConfiguration.getTransportPayloadTypeConfiguration(); assertTrue(transportPayloadTypeConfiguration instanceof ProtoTransportPayloadConfiguration); ProtoTransportPayloadConfiguration protoTransportPayloadConfiguration = (ProtoTransportPayloadConfiguration) transportPayloadTypeConfiguration; - ProtoFileElement transportProtoSchema = protoTransportPayloadConfiguration.getTransportProtoSchema(deviceTelemetryProtoSchema); - return protoTransportPayloadConfiguration.getDynamicSchema(transportProtoSchema, "telemetrySchema"); + String deviceTelemetryProtoSchema = protoTransportPayloadConfiguration.getDeviceTelemetryProtoSchema(); + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(deviceTelemetryProtoSchema); + return DynamicProtoUtils.getDynamicSchema(protoFileElement, ProtoTransportPayloadConfiguration.TELEMETRY_PROTO_SCHEMA); } private DynamicMessage getDefaultDynamicMessage() { - DynamicSchema telemetrySchema = getDynamicSchema(DEVICE_TELEMETRY_PROTO_SCHEMA); + DynamicSchema telemetrySchema = getDynamicSchema(); DynamicMessage.Builder nestedJsonObjectBuilder = telemetrySchema.newMessageBuilder("PostTelemetry.JsonObject.NestedJsonObject"); Descriptors.Descriptor nestedJsonObjectBuilderDescriptor = nestedJsonObjectBuilder.getDescriptorForType(); diff --git a/application/src/test/resources/application-test.properties b/application/src/test/resources/application-test.properties index eacd1733d3..e4a36c151d 100644 --- a/application/src/test/resources/application-test.properties +++ b/application/src/test/resources/application-test.properties @@ -59,4 +59,7 @@ queue.rule-engine.queues[2].processing-strategy.max-pause-between-retries=0 usage.stats.report.enabled=false sql.audit_logs.partition_size=24 -sql.ttl.audit_logs.ttl=2592000 \ No newline at end of file +sql.ttl.audit_logs.ttl=2592000 + +sql.edge_events.partition_size=168 +sql.ttl.edge_events.edge_event_ttl=2592000 diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java b/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java index 594bfff146..6910325add 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/CacheSpecsMap.java @@ -17,9 +17,12 @@ package org.thingsboard.server.cache; import lombok.Data; import lombok.Getter; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.context.annotation.Configuration; +import org.thingsboard.server.common.data.CacheConstants; +import javax.annotation.PostConstruct; import java.util.Map; @Configuration @@ -27,7 +30,20 @@ import java.util.Map; @Data public class CacheSpecsMap { + @Value("${security.jwt.refreshTokenExpTime:604800}") + private int refreshTokenExpTime; + @Getter private Map specs; + @PostConstruct + public void replaceTheJWTTokenRefreshExpTime() { + if (specs != null) { + var cacheSpecs = specs.get(CacheConstants.USERS_SESSION_INVALIDATION_CACHE); + if (cacheSpecs != null) { + cacheSpecs.setTimeToLiveInMinutes((refreshTokenExpTime / 60) + 1); + } + } + } + } diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java b/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java index 9cb139e26a..198404de2e 100644 --- a/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java +++ b/common/cache/src/main/java/org/thingsboard/server/cache/TbCaffeineCacheConfiguration.java @@ -20,6 +20,7 @@ import com.github.benmanes.caffeine.cache.RemovalCause; import com.github.benmanes.caffeine.cache.Ticker; import com.github.benmanes.caffeine.cache.Weigher; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.cache.CacheManager; import org.springframework.cache.annotation.EnableCaching; @@ -71,6 +72,7 @@ public class TbCaffeineCacheConfiguration { } private CaffeineCache buildCache(String name, CacheSpecs cacheSpec) { + final Caffeine caffeineBuilder = Caffeine.newBuilder() .weigher(collectionSafeWeigher()) diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationCaffeineCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationCaffeineCache.java new file mode 100644 index 0000000000..c052627099 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationCaffeineCache.java @@ -0,0 +1,34 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.cache.usersUpdateTime; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.cache.CacheManager; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CaffeineTbTransactionalCache; +import org.thingsboard.server.common.data.CacheConstants; + + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "caffeine", matchIfMissing = true) +@Service("UsersSessionInvalidation") +public class UsersSessionInvalidationCaffeineCache extends CaffeineTbTransactionalCache { + + @Autowired + public UsersSessionInvalidationCaffeineCache(CacheManager cacheManager) { + super(cacheManager, CacheConstants.USERS_SESSION_INVALIDATION_CACHE); + } +} diff --git a/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java new file mode 100644 index 0000000000..61d1d21516 --- /dev/null +++ b/common/cache/src/main/java/org/thingsboard/server/cache/usersUpdateTime/UsersSessionInvalidationRedisCache.java @@ -0,0 +1,36 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.cache.usersUpdateTime; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.stereotype.Service; +import org.thingsboard.server.cache.CacheSpecsMap; +import org.thingsboard.server.cache.RedisTbTransactionalCache; +import org.thingsboard.server.cache.TBRedisCacheConfiguration; +import org.thingsboard.server.cache.TbFSTRedisSerializer; +import org.thingsboard.server.common.data.CacheConstants; + +@ConditionalOnProperty(prefix = "cache", value = "type", havingValue = "redis") +@Service("UsersSessionInvalidation") +public class UsersSessionInvalidationRedisCache extends RedisTbTransactionalCache { + + @Autowired + public UsersSessionInvalidationRedisCache(TBRedisCacheConfiguration configuration, CacheSpecsMap cacheSpecsMap, RedisConnectionFactory connectionFactory) { + super(CacheConstants.USERS_SESSION_INVALIDATION_CACHE, cacheSpecsMap, connectionFactory, configuration, new TbFSTRedisSerializer<>()); + } +} diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java index 02cd1a8348..b8041aa3fc 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java @@ -48,6 +48,8 @@ public interface AlarmService { ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTs); + Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); + ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java index 6586f42d23..f3f76aba9f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/AbstractCassandraCluster.java @@ -24,11 +24,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.core.env.Environment; import org.springframework.core.env.Profiles; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.dao.cassandra.guava.GuavaSession; import org.thingsboard.server.dao.cassandra.guava.GuavaSessionBuilder; import org.thingsboard.server.dao.cassandra.guava.GuavaSessionUtils; import javax.annotation.PreDestroy; +import java.nio.file.Paths; @Slf4j public abstract class AbstractCassandraCluster { @@ -40,6 +42,13 @@ public abstract class AbstractCassandraCluster { @Value("${cassandra.local_datacenter:datacenter1}") private String localDatacenter; + @Value("${cassandra.cloud.secure_connect_bundle_path:}") + private String cloudSecureConnectBundlePath; + @Value("${cassandra.cloud.client_id:}") + private String cloudClientId; + @Value("${cassandra.cloud.client_secret:}") + private String cloudClientSecret; + @Autowired private CassandraDriverOptions driverOptions; @@ -86,7 +95,14 @@ public abstract class AbstractCassandraCluster { this.sessionBuilder.withKeyspace(this.keyspaceName); } this.sessionBuilder.withLocalDatacenter(localDatacenter); + + if (StringUtils.isNotBlank(cloudSecureConnectBundlePath)) { + this.sessionBuilder.withCloudSecureConnectBundle(Paths.get(cloudSecureConnectBundlePath)); + this.sessionBuilder.withAuthCredentials(cloudClientId, cloudClientSecret); + } + session = sessionBuilder.build(); + if (this.metrics && this.jmx) { MetricRegistry registry = session.getMetrics().orElseThrow( diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaDriverContext.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaDriverContext.java index b5472d78a2..f193b0a7fe 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaDriverContext.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaDriverContext.java @@ -40,26 +40,8 @@ import java.util.function.Predicate; */ public class GuavaDriverContext extends DefaultDriverContext { - public GuavaDriverContext( - DriverConfigLoader configLoader, - List> typeCodecs, - NodeStateListener nodeStateListener, - SchemaChangeListener schemaChangeListener, - RequestTracker requestTracker, - Map localDatacenters, - Map> nodeFilters, - ClassLoader classLoader) { - super( - configLoader, - ProgrammaticArguments.builder() - .addTypeCodecs(typeCodecs.toArray(new TypeCodec[0])) - .withNodeStateListener(nodeStateListener) - .withSchemaChangeListener(schemaChangeListener) - .withRequestTracker(requestTracker) - .withLocalDatacenters(localDatacenters) - .withNodeFilters(nodeFilters) - .withClassLoader(classLoader) - .build()); + public GuavaDriverContext(DriverConfigLoader configLoader, ProgrammaticArguments programmaticArguments) { + super(configLoader, programmaticArguments); } @Override diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java index c95c653d64..60ff71a8bd 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/cassandra/guava/GuavaSessionBuilder.java @@ -25,18 +25,8 @@ import edu.umd.cs.findbugs.annotations.NonNull; public class GuavaSessionBuilder extends SessionBuilder { @Override - protected DriverContext buildContext( - DriverConfigLoader configLoader, - ProgrammaticArguments programmaticArguments) { - return new GuavaDriverContext( - configLoader, - programmaticArguments.getTypeCodecs(), - programmaticArguments.getNodeStateListener(), - programmaticArguments.getSchemaChangeListener(), - programmaticArguments.getRequestTracker(), - programmaticArguments.getLocalDatacenters(), - programmaticArguments.getNodeFilters(), - programmaticArguments.getClassLoader()); + protected DriverContext buildContext(DriverConfigLoader configLoader, ProgrammaticArguments programmaticArguments) { + return new GuavaDriverContext(configLoader, programmaticArguments); } @Override diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java index c0c72552f6..b3372bf30c 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/edge/EdgeService.java @@ -82,6 +82,8 @@ public interface EdgeService { PageData findEdgesByTenantIdAndEntityId(TenantId tenantId, EntityId ruleChainId, PageLink pageLink); + List findAllRelatedEdgeIds(TenantId tenantId, EntityId entityId); + PageData findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink); String findMissingToRelatedRuleChains(TenantId tenantId, EdgeId edgeId, String tbRuleChainInputNodeClassName); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java index 93da1ecd6c..76891e950f 100644 --- a/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/user/UserService.java @@ -33,6 +33,8 @@ public interface UserService { User findUserByEmail(TenantId tenantId, String email); + User findUserByTenantIdAndEmail(TenantId tenantId, String email); + User saveUser(User user); UserCredentials findUserCredentialsByUserId(TenantId tenantId, UserId userId); diff --git a/common/dao-api/src/main/java/org/thingsboard/server/dao/util/NoSqlAnyDaoNonCloud.java b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/NoSqlAnyDaoNonCloud.java new file mode 100644 index 0000000000..0ec01c8b0a --- /dev/null +++ b/common/dao-api/src/main/java/org/thingsboard/server/dao/util/NoSqlAnyDaoNonCloud.java @@ -0,0 +1,27 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.util; + +import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +@Retention(RetentionPolicy.RUNTIME) +@ConditionalOnExpression("('${database.ts.type}'=='cassandra' || '${database.ts_latest.type}'=='cassandra') " + + "&& ('${cassandra.cloud.secure_connect_bundle_path}' == null || '${cassandra.cloud.secure_connect_bundle_path}'.isBlank() )") +public @interface NoSqlAnyDaoNonCloud { +} diff --git a/common/data/pom.xml b/common/data/pom.xml index decbba55aa..fd927c13d8 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -108,6 +108,10 @@ de.ruedigermoeller fst + + com.google.protobuf + protobuf-java-util + diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java index 0c5ed80cd1..c6fbe3be5f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/CacheConstants.java @@ -32,7 +32,7 @@ public class CacheConstants { public static final String ASSET_PROFILE_CACHE = "assetProfiles"; public static final String ATTRIBUTES_CACHE = "attributes"; - public static final String USERS_UPDATE_TIME_CACHE = "usersUpdateTime"; + public static final String USERS_SESSION_INVALIDATION_CACHE = "userSessionsInvalidation"; public static final String OTA_PACKAGE_CACHE = "otaPackages"; public static final String OTA_PACKAGE_DATA_CACHE = "otaPackagesData"; public static final String REPOSITORY_SETTINGS_CACHE = "repositorySettings"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java index 85b9e681a1..69c36697d3 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DataConstants.java @@ -40,6 +40,8 @@ public class DataConstants { public static final String EXPIRATION_TIME = "expirationTime"; public static final String ADDITIONAL_INFO = "additionalInfo"; public static final String RETRIES = "retries"; + public static final String EDGE_ID = "edgeId"; + public static final String DEVICE_ID = "deviceId"; public static final String COAP_TRANSPORT_NAME = "COAP"; public static final String LWM2M_TRANSPORT_NAME = "LWM2M"; public static final String MQTT_TRANSPORT_NAME = "MQTT"; diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/DynamicProtoUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/DynamicProtoUtils.java new file mode 100644 index 0000000000..8951222ebd --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/DynamicProtoUtils.java @@ -0,0 +1,301 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.github.os72.protobuf.dynamic.EnumDefinition; +import com.github.os72.protobuf.dynamic.MessageDefinition; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.google.protobuf.InvalidProtocolBufferException; +import com.google.protobuf.util.JsonFormat; +import com.squareup.wire.Syntax; +import com.squareup.wire.schema.Field; +import com.squareup.wire.schema.Location; +import com.squareup.wire.schema.internal.parser.EnumConstantElement; +import com.squareup.wire.schema.internal.parser.EnumElement; +import com.squareup.wire.schema.internal.parser.FieldElement; +import com.squareup.wire.schema.internal.parser.MessageElement; +import com.squareup.wire.schema.internal.parser.OneOfElement; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import com.squareup.wire.schema.internal.parser.ProtoParser; +import com.squareup.wire.schema.internal.parser.TypeElement; +import lombok.extern.slf4j.Slf4j; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +@Slf4j +public class DynamicProtoUtils { + + public static final Location LOCATION = new Location("", "", -1, -1); + public static final String PROTO_3_SYNTAX = "proto3"; + + public static Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { + try { + DynamicMessage.Builder builder = getDynamicMessageBuilder(protoSchema, schemaName); + return builder.getDescriptorForType(); + } catch (Exception e) { + log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); + return null; + } + } + + public static DynamicMessage.Builder getDynamicMessageBuilder(String protoSchema, String schemaName) { + ProtoFileElement protoFileElement = getProtoFileElement(protoSchema); + DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); + String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() + .map(MessageElement::getName).reduce((previous, last) -> last).get(); + return dynamicSchema.newMessageBuilder(lastMsgName); + } + + public static DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { + DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); + schemaBuilder.setName(schemaName); + schemaBuilder.setSyntax(PROTO_3_SYNTAX); + schemaBuilder.setPackage(StringUtils.isNotEmpty(protoFileElement.getPackageName()) ? + protoFileElement.getPackageName() : schemaName.toLowerCase()); + List types = protoFileElement.getTypes(); + List messageTypes = getMessageTypes(types); + + if (!messageTypes.isEmpty()) { + List enumTypes = getEnumElements(types); + if (!enumTypes.isEmpty()) { + enumTypes.forEach(enumElement -> { + EnumDefinition enumDefinition = getEnumDefinition(enumElement); + schemaBuilder.addEnumDefinition(enumDefinition); + }); + } + List messageDefinitions = getMessageDefinitions(messageTypes); + messageDefinitions.forEach(schemaBuilder::addMessageDefinition); + try { + return schemaBuilder.build(); + } catch (Descriptors.DescriptorValidationException e) { + throw new RuntimeException("Failed to create dynamic schema due to: " + e.getMessage()); + } + } else { + throw new RuntimeException("Failed to get Dynamic Schema! Message types is empty for schema:" + schemaName); + } + } + + public static ProtoFileElement getProtoFileElement(String protoSchema) { + return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); + } + + public static String dynamicMsgToJson(Descriptors.Descriptor descriptor, byte[] payload) throws InvalidProtocolBufferException { + DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, payload); + return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + } + + public static DynamicMessage jsonToDynamicMessage(DynamicMessage.Builder builder, String payload) throws InvalidProtocolBufferException { + JsonFormat.parser().ignoringUnknownFields().merge(payload, builder); + return builder.build(); + } + + private static List getMessageTypes(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof MessageElement) + .map(typeElement -> (MessageElement) typeElement) + .collect(Collectors.toList()); + } + + private static List getEnumElements(List types) { + return types.stream() + .filter(typeElement -> typeElement instanceof EnumElement) + .map(typeElement -> (EnumElement) typeElement) + .collect(Collectors.toList()); + } + + private static List getMessageDefinitions(List messageElementsList) { + if (!messageElementsList.isEmpty()) { + List messageDefinitions = new ArrayList<>(); + messageElementsList.forEach(messageElement -> { + MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); + + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + nestedEnumTypes.forEach(enumElement -> { + EnumDefinition nestedEnumDefinition = getEnumDefinition(enumElement); + messageDefinitionBuilder.addEnumDefinition(nestedEnumDefinition); + }); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + List nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); + nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); + } + List messageElementFields = messageElement.getFields(); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + for (OneOfElement oneOfelement : oneOfs) { + MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); + addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); + } + } + if (!messageElementFields.isEmpty()) { + addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); + } + messageDefinitions.add(messageDefinitionBuilder.build()); + }); + return messageDefinitions; + } else { + return Collections.emptyList(); + } + } + + private static EnumDefinition getEnumDefinition(EnumElement enumElement) { + List enumElementTypeConstants = enumElement.getConstants(); + EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); + if (!enumElementTypeConstants.isEmpty()) { + enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); + } + return enumDefinitionBuilder.build(); + } + + + private static void addMessageFieldsToTheMessageDefinition(List messageElementFields, MessageDefinition.Builder messageDefinitionBuilder) { + messageElementFields.forEach(fieldElement -> { + String labelStr = null; + if (fieldElement.getLabel() != null) { + labelStr = fieldElement.getLabel().name().toLowerCase(); + } + messageDefinitionBuilder.addField( + labelStr, + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag()); + }); + } + + private static void addMessageFieldsToTheOneOfDefinition(List oneOfsElementFields, MessageDefinition.OneofBuilder oneofBuilder) { + oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( + fieldElement.getType(), + fieldElement.getName(), + fieldElement.getTag())); + oneofBuilder.msgDefBuilder(); + } + + // validation + + public static void validateProtoSchema(String schema, String schemaName, String exceptionPrefix) throws IllegalArgumentException { + ProtoParser schemaParser = new ProtoParser(LOCATION, schema.toCharArray()); + ProtoFileElement protoFileElement; + try { + protoFileElement = schemaParser.readProtoFile(); + } catch (Exception e) { + throw new IllegalArgumentException(exceptionPrefix + " failed to parse " + schemaName + " due to: " + e.getMessage()); + } + checkProtoFileSyntax(schemaName, protoFileElement); + checkProtoFileCommonSettings(schemaName, protoFileElement.getOptions().isEmpty(), " Schema options don't support!", exceptionPrefix); + checkProtoFileCommonSettings(schemaName, protoFileElement.getPublicImports().isEmpty(), " Schema public imports don't support!", exceptionPrefix); + checkProtoFileCommonSettings(schemaName, protoFileElement.getImports().isEmpty(), " Schema imports don't support!", exceptionPrefix); + checkProtoFileCommonSettings(schemaName, protoFileElement.getExtendDeclarations().isEmpty(), " Schema extend declarations don't support!", exceptionPrefix); + checkTypeElements(schemaName, protoFileElement, exceptionPrefix); + } + + private static void checkProtoFileSyntax(String schemaName, ProtoFileElement protoFileElement) { + if (protoFileElement.getSyntax() == null || !protoFileElement.getSyntax().equals(Syntax.PROTO_3)) { + throw new IllegalArgumentException("[Transport Configuration] invalid schema syntax: " + protoFileElement.getSyntax() + + " for " + schemaName + " provided! Only " + Syntax.PROTO_3 + " allowed!"); + } + } + + private static void checkProtoFileCommonSettings(String schemaName, boolean isEmptySettings, String invalidSettingsMessage, String exceptionPrefix) { + if (!isEmptySettings) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + invalidSettingsMessage); + } + } + + private static void checkTypeElements(String schemaName, ProtoFileElement protoFileElement, String exceptionPrefix) { + List types = protoFileElement.getTypes(); + if (!types.isEmpty()) { + if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " At least one Message definition should exists!"); + } else { + checkEnumElements(schemaName, getEnumElements(types), exceptionPrefix); + checkMessageElements(schemaName, getMessageTypes(types), exceptionPrefix); + } + } else { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Type elements is empty!"); + } + } + + private static void checkFieldElements(String schemaName, List fieldElements, String exceptionPrefix) { + if (!fieldElements.isEmpty()) { + boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { + Field.Label label = fieldElement.getLabel(); + return label != null && label.equals(Field.Label.REQUIRED); + }); + if (hasRequiredLabel) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Required labels are not supported!"); + } + boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); + if (hasDefaultValue) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Default values are not supported!"); + } + } + } + + private static void checkEnumElements(String schemaName, List enumTypes, String exceptionPrefix) { + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Nested types in Enum definitions are not supported!"); + } + if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { + throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName, exceptionPrefix) + " Enum definitions options are not supported!"); + } + } + + private static void checkMessageElements(String schemaName, List messageElementsList, String exceptionPrefix) { + if (!messageElementsList.isEmpty()) { + messageElementsList.forEach(messageElement -> { + checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), + " Message definition groups don't support!", exceptionPrefix); + checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), + " Message definition options don't support!", exceptionPrefix); + checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), + " Message definition extensions don't support!", exceptionPrefix); + checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), + " Message definition reserved elements don't support!", exceptionPrefix); + checkFieldElements(schemaName, messageElement.getFields(), exceptionPrefix); + List oneOfs = messageElement.getOneOfs(); + if (!oneOfs.isEmpty()) { + oneOfs.forEach(oneOfElement -> { + checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), + " OneOf definition groups don't support!", exceptionPrefix); + checkFieldElements(schemaName, oneOfElement.getFields(), exceptionPrefix); + }); + } + List nestedTypes = messageElement.getNestedTypes(); + if (!nestedTypes.isEmpty()) { + List nestedEnumTypes = getEnumElements(nestedTypes); + if (!nestedEnumTypes.isEmpty()) { + checkEnumElements(schemaName, nestedEnumTypes, exceptionPrefix); + } + List nestedMessageTypes = getMessageTypes(nestedTypes); + checkMessageElements(schemaName, nestedMessageTypes, exceptionPrefix); + } + }); + } + } + + public static String invalidSchemaProvidedMessage(String schemaName, String exceptionPrefix) { + return exceptionPrefix + " invalid " + schemaName + " provided!"; + } + +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java index 9acbb15646..fbc70aa372 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/StringUtils.java @@ -156,6 +156,10 @@ public class StringUtils { return false; } + public static boolean contains(final CharSequence seq, final CharSequence searchSeq) { + return org.apache.commons.lang3.StringUtils.contains(seq, searchSeq); + } + public static String randomNumeric(int length) { return RandomStringUtils.randomNumeric(length); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java index 409570887c..196069ff5f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/ProtoTransportPayloadConfiguration.java @@ -15,29 +15,15 @@ */ package org.thingsboard.server.common.data.device.profile; -import com.github.os72.protobuf.dynamic.DynamicSchema; -import com.github.os72.protobuf.dynamic.EnumDefinition; -import com.github.os72.protobuf.dynamic.MessageDefinition; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; import com.squareup.wire.schema.Location; -import com.squareup.wire.schema.internal.parser.EnumConstantElement; -import com.squareup.wire.schema.internal.parser.EnumElement; -import com.squareup.wire.schema.internal.parser.FieldElement; -import com.squareup.wire.schema.internal.parser.MessageElement; -import com.squareup.wire.schema.internal.parser.OneOfElement; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import com.squareup.wire.schema.internal.parser.ProtoParser; -import com.squareup.wire.schema.internal.parser.TypeElement; import lombok.Data; import lombok.extern.slf4j.Slf4j; +import org.thingsboard.server.common.data.DynamicProtoUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.TransportPayloadType; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.stream.Collectors; - @Slf4j @Data public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeConfiguration { @@ -63,23 +49,23 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC } public Descriptors.Descriptor getTelemetryDynamicMessageDescriptor(String deviceTelemetryProtoSchema) { - return getDescriptor(deviceTelemetryProtoSchema, TELEMETRY_PROTO_SCHEMA); + return DynamicProtoUtils.getDescriptor(deviceTelemetryProtoSchema, TELEMETRY_PROTO_SCHEMA); } public Descriptors.Descriptor getAttributesDynamicMessageDescriptor(String deviceAttributesProtoSchema) { - return getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); + return DynamicProtoUtils.getDescriptor(deviceAttributesProtoSchema, ATTRIBUTES_PROTO_SCHEMA); } public Descriptors.Descriptor getRpcResponseDynamicMessageDescriptor(String deviceRpcResponseProtoSchema) { - return getDescriptor(deviceRpcResponseProtoSchema, RPC_RESPONSE_PROTO_SCHEMA); + return DynamicProtoUtils.getDescriptor(deviceRpcResponseProtoSchema, RPC_RESPONSE_PROTO_SCHEMA); } public DynamicMessage.Builder getRpcRequestDynamicMessageBuilder(String deviceRpcRequestProtoSchema) { - return getDynamicMessageBuilder(deviceRpcRequestProtoSchema, RPC_REQUEST_PROTO_SCHEMA); + return DynamicProtoUtils.getDynamicMessageBuilder(deviceRpcRequestProtoSchema, RPC_REQUEST_PROTO_SCHEMA); } public String getDeviceRpcResponseProtoSchema() { - if (!isEmptyStr(deviceRpcResponseProtoSchema)) { + if (StringUtils.isNotEmpty(deviceRpcResponseProtoSchema)) { return deviceRpcResponseProtoSchema; } else { return "syntax =\"proto3\";\n" + @@ -92,7 +78,7 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC } public String getDeviceRpcRequestProtoSchema() { - if (!isEmptyStr(deviceRpcRequestProtoSchema)) { + if (StringUtils.isNotEmpty(deviceRpcRequestProtoSchema)) { return deviceRpcRequestProtoSchema; } else { return "syntax =\"proto3\";\n" + @@ -106,143 +92,4 @@ public class ProtoTransportPayloadConfiguration implements TransportPayloadTypeC } } - private Descriptors.Descriptor getDescriptor(String protoSchema, String schemaName) { - try { - DynamicMessage.Builder builder = getDynamicMessageBuilder(protoSchema, schemaName); - return builder.getDescriptorForType(); - } catch (Exception e) { - log.warn("Failed to get Message Descriptor due to {}", e.getMessage()); - return null; - } - } - - public DynamicMessage.Builder getDynamicMessageBuilder(String protoSchema, String schemaName) { - ProtoFileElement protoFileElement = getTransportProtoSchema(protoSchema); - DynamicSchema dynamicSchema = getDynamicSchema(protoFileElement, schemaName); - String lastMsgName = getMessageTypes(protoFileElement.getTypes()).stream() - .map(MessageElement::getName).reduce((previous, last) -> last).get(); - return dynamicSchema.newMessageBuilder(lastMsgName); - } - - public DynamicSchema getDynamicSchema(ProtoFileElement protoFileElement, String schemaName) { - DynamicSchema.Builder schemaBuilder = DynamicSchema.newBuilder(); - schemaBuilder.setName(schemaName); - schemaBuilder.setSyntax(PROTO_3_SYNTAX); - schemaBuilder.setPackage(!isEmptyStr(protoFileElement.getPackageName()) ? - protoFileElement.getPackageName() : schemaName.toLowerCase()); - List types = protoFileElement.getTypes(); - List messageTypes = getMessageTypes(types); - - if (!messageTypes.isEmpty()) { - List enumTypes = getEnumElements(types); - if (!enumTypes.isEmpty()) { - enumTypes.forEach(enumElement -> { - EnumDefinition enumDefinition = getEnumDefinition(enumElement); - schemaBuilder.addEnumDefinition(enumDefinition); - }); - } - List messageDefinitions = getMessageDefinitions(messageTypes); - messageDefinitions.forEach(schemaBuilder::addMessageDefinition); - try { - return schemaBuilder.build(); - } catch (Descriptors.DescriptorValidationException e) { - throw new RuntimeException("Failed to create dynamic schema due to: " + e.getMessage()); - } - } else { - throw new RuntimeException("Failed to get Dynamic Schema! Message types is empty for schema:" + schemaName); - } - } - - public ProtoFileElement getTransportProtoSchema(String protoSchema) { - return new ProtoParser(LOCATION, protoSchema.toCharArray()).readProtoFile(); - } - - private List getMessageTypes(List types) { - return types.stream() - .filter(typeElement -> typeElement instanceof MessageElement) - .map(typeElement -> (MessageElement) typeElement) - .collect(Collectors.toList()); - } - - private List getEnumElements(List types) { - return types.stream() - .filter(typeElement -> typeElement instanceof EnumElement) - .map(typeElement -> (EnumElement) typeElement) - .collect(Collectors.toList()); - } - - private List getMessageDefinitions(List messageElementsList) { - if (!messageElementsList.isEmpty()) { - List messageDefinitions = new ArrayList<>(); - messageElementsList.forEach(messageElement -> { - MessageDefinition.Builder messageDefinitionBuilder = MessageDefinition.newBuilder(messageElement.getName()); - - List nestedTypes = messageElement.getNestedTypes(); - if (!nestedTypes.isEmpty()) { - List nestedEnumTypes = getEnumElements(nestedTypes); - if (!nestedEnumTypes.isEmpty()) { - nestedEnumTypes.forEach(enumElement -> { - EnumDefinition nestedEnumDefinition = getEnumDefinition(enumElement); - messageDefinitionBuilder.addEnumDefinition(nestedEnumDefinition); - }); - } - List nestedMessageTypes = getMessageTypes(nestedTypes); - List nestedMessageDefinitions = getMessageDefinitions(nestedMessageTypes); - nestedMessageDefinitions.forEach(messageDefinitionBuilder::addMessageDefinition); - } - List messageElementFields = messageElement.getFields(); - List oneOfs = messageElement.getOneOfs(); - if (!oneOfs.isEmpty()) { - for (OneOfElement oneOfelement : oneOfs) { - MessageDefinition.OneofBuilder oneofBuilder = messageDefinitionBuilder.addOneof(oneOfelement.getName()); - addMessageFieldsToTheOneOfDefinition(oneOfelement.getFields(), oneofBuilder); - } - } - if (!messageElementFields.isEmpty()) { - addMessageFieldsToTheMessageDefinition(messageElementFields, messageDefinitionBuilder); - } - messageDefinitions.add(messageDefinitionBuilder.build()); - }); - return messageDefinitions; - } else { - return Collections.emptyList(); - } - } - - private EnumDefinition getEnumDefinition(EnumElement enumElement) { - List enumElementTypeConstants = enumElement.getConstants(); - EnumDefinition.Builder enumDefinitionBuilder = EnumDefinition.newBuilder(enumElement.getName()); - if (!enumElementTypeConstants.isEmpty()) { - enumElementTypeConstants.forEach(constantElement -> enumDefinitionBuilder.addValue(constantElement.getName(), constantElement.getTag())); - } - return enumDefinitionBuilder.build(); - } - - - private void addMessageFieldsToTheMessageDefinition(List messageElementFields, MessageDefinition.Builder messageDefinitionBuilder) { - messageElementFields.forEach(fieldElement -> { - String labelStr = null; - if (fieldElement.getLabel() != null) { - labelStr = fieldElement.getLabel().name().toLowerCase(); - } - messageDefinitionBuilder.addField( - labelStr, - fieldElement.getType(), - fieldElement.getName(), - fieldElement.getTag()); - }); - } - - private void addMessageFieldsToTheOneOfDefinition(List oneOfsElementFields, MessageDefinition.OneofBuilder oneofBuilder) { - oneOfsElementFields.forEach(fieldElement -> oneofBuilder.addField( - fieldElement.getType(), - fieldElement.getName(), - fieldElement.getTag())); - oneofBuilder.msgDefBuilder(); - } - - private boolean isEmptyStr(String str) { - return str == null || "".equals(str); - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java index adfaf2bacb..94f8976e8b 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/edge/EdgeEvent.java @@ -17,6 +17,8 @@ package org.thingsboard.server.common.data.edge; import com.fasterxml.jackson.databind.JsonNode; import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.ToString; import org.thingsboard.server.common.data.BaseData; import org.thingsboard.server.common.data.id.EdgeEventId; import org.thingsboard.server.common.data.id.EdgeId; @@ -25,6 +27,8 @@ import org.thingsboard.server.common.data.id.TenantId; import java.util.UUID; @Data +@EqualsAndHashCode(callSuper = true) +@ToString(callSuper = true) public class EdgeEvent extends BaseData { private TenantId tenantId; @@ -42,9 +46,4 @@ public class EdgeEvent extends BaseData { public EdgeEvent(EdgeEventId id) { super(id); } - - public EdgeEvent(EdgeEvent event) { - super(event); - } - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java index da9a5c49eb..c6a19e0040 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/id/EntityIdFactory.java @@ -87,6 +87,8 @@ public class EntityIdFactory { public static EntityId getByEdgeEventTypeAndUuid(EdgeEventType edgeEventType, UUID uuid) { switch (edgeEventType) { + case TENANT: + return new TenantId(uuid); case CUSTOMER: return new CustomerId(uuid); case USER: diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/script/ScriptLanguage.java b/common/data/src/main/java/org/thingsboard/server/common/data/script/ScriptLanguage.java new file mode 100644 index 0000000000..3f8a2fae45 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/script/ScriptLanguage.java @@ -0,0 +1,20 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.script; + +public enum ScriptLanguage { + JS, MVEL +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java index 2dcd95f435..0436627033 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserAuthDataChangedEvent.java @@ -15,17 +15,9 @@ */ package org.thingsboard.server.common.data.security.event; -import lombok.Data; -import org.thingsboard.server.common.data.id.UserId; - -@Data -public class UserAuthDataChangedEvent { - private final UserId userId; - private final long ts; - - public UserAuthDataChangedEvent(UserId userId) { - this.userId = userId; - this.ts = System.currentTimeMillis(); - } +import java.io.Serializable; +public abstract class UserAuthDataChangedEvent implements Serializable { + public abstract String getId(); + public abstract long getTs(); } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserCredentialsInvalidationEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserCredentialsInvalidationEvent.java new file mode 100644 index 0000000000..63ee49e53b --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserCredentialsInvalidationEvent.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security.event; + +import lombok.EqualsAndHashCode; +import org.thingsboard.server.common.data.id.UserId; + +@EqualsAndHashCode(callSuper = true) +public class UserCredentialsInvalidationEvent extends UserAuthDataChangedEvent { + private final UserId userId; + private final long ts; + + public UserCredentialsInvalidationEvent(UserId userId) { + this.userId = userId; + this.ts = System.currentTimeMillis(); + } + + @Override + public String getId() { + return userId.toString(); + } + + @Override + public long getTs() { + return ts; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserSessionInvalidationEvent.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserSessionInvalidationEvent.java new file mode 100644 index 0000000000..2e6aea6bb9 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/event/UserSessionInvalidationEvent.java @@ -0,0 +1,39 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.security.event; + +import lombok.EqualsAndHashCode; + +@EqualsAndHashCode(callSuper = true) +public class UserSessionInvalidationEvent extends UserAuthDataChangedEvent { + private final String sessionId; + private final long ts; + + public UserSessionInvalidationEvent(String sessionId) { + this.sessionId = sessionId; + this.ts = System.currentTimeMillis(); + } + + @Override + public String getId() { + return sessionId; + } + + @Override + public long getTs() { + return ts; + } +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java index 50e23a1dba..4d39377218 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettings.java @@ -15,7 +15,6 @@ */ package org.thingsboard.server.common.data.sync.vc; -import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; import java.io.Serializable; @@ -32,6 +31,7 @@ public class RepositorySettings implements Serializable { private String privateKey; private String privateKeyPassword; private String defaultBranch; + private boolean readOnly; public RepositorySettings() { } @@ -45,5 +45,6 @@ public class RepositorySettings implements Serializable { this.privateKey = settings.getPrivateKey(); this.privateKeyPassword = settings.getPrivateKeyPassword(); this.defaultBranch = settings.getDefaultBranch(); + this.readOnly = settings.isReadOnly(); } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettingsInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettingsInfo.java new file mode 100644 index 0000000000..17513dd99d --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/vc/RepositorySettingsInfo.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.sync.vc; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +@Data +@AllArgsConstructor +@NoArgsConstructor +@Builder +public class RepositorySettingsInfo { + private boolean configured; + private Boolean readOnly; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/util/TbPair.java b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbPair.java new file mode 100644 index 0000000000..c7dba0c848 --- /dev/null +++ b/common/data/src/main/java/org/thingsboard/server/common/data/util/TbPair.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data.util; + +import lombok.AllArgsConstructor; +import lombok.Data; + +@Data +@AllArgsConstructor +public class TbPair { + private S first; + private T second; +} diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java index 389771e46f..eca19f0db5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/widget/WidgetsBundle.java @@ -16,12 +16,14 @@ package org.thingsboard.server.common.data.widget; import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; import org.thingsboard.server.common.data.ExportableEntity; +import org.thingsboard.server.common.data.HasName; import org.thingsboard.server.common.data.HasTenantId; import org.thingsboard.server.common.data.SearchTextBased; import org.thingsboard.server.common.data.id.TenantId; @@ -31,7 +33,7 @@ import org.thingsboard.server.common.data.validation.NoXss; @ApiModel @EqualsAndHashCode(callSuper = true) -public class WidgetsBundle extends SearchTextBased implements HasTenantId, ExportableEntity { +public class WidgetsBundle extends SearchTextBased implements HasName, HasTenantId, ExportableEntity { private static final long serialVersionUID = -7627368878362410489L; @@ -109,8 +111,9 @@ public class WidgetsBundle extends SearchTextBased implements H return getTitle(); } - @JsonIgnore + @ApiModelProperty(position = 3, value = "Same as title of the Widget Bundle. Read-only field. Update the 'title' to change the 'name' of the Widget Bundle.", accessMode = ApiModelProperty.AccessMode.READ_ONLY) @Override + @JsonProperty(access = JsonProperty.Access.READ_ONLY) public String getName() { return title; } diff --git a/common/data/src/test/java/org/thingsboard/server/common/data/DynamicProtoUtilsTest.java b/common/data/src/test/java/org/thingsboard/server/common/data/DynamicProtoUtilsTest.java new file mode 100644 index 0000000000..9ae4651cc9 --- /dev/null +++ b/common/data/src/test/java/org/thingsboard/server/common/data/DynamicProtoUtilsTest.java @@ -0,0 +1,169 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.data; + +import com.github.os72.protobuf.dynamic.DynamicSchema; +import com.google.protobuf.Descriptors; +import com.google.protobuf.DynamicMessage; +import com.squareup.wire.schema.internal.parser.ProtoFileElement; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.junit.MockitoJUnitRunner; + +import java.util.List; +import java.util.Set; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +@RunWith(MockitoJUnitRunner.class) +public class DynamicProtoUtilsTest { + + @Test + public void testProtoSchemaWithMessageNestedTypes() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testnested;\n" + + "\n" + + "message Outer {\n" + + " message MiddleAA {\n" + + " message Inner {\n" + + " optional int64 ival = 1;\n" + + " optional bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " message MiddleBB {\n" + + " message Inner {\n" + + " optional int32 ival = 1;\n" + + " optional bool booly = 2;\n" + + " }\n" + + " Inner inner = 1;\n" + + " }\n" + + " MiddleAA middleAA = 1;\n" + + " MiddleBB middleBB = 2;\n" + + "}"; + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(schema); + DynamicSchema dynamicSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, "test schema with nested types"); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(5, messageTypes.size()); + assertTrue(messageTypes.contains("testnested.Outer")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleAA.Inner")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB")); + assertTrue(messageTypes.contains("testnested.Outer.MiddleBB.Inner")); + + DynamicMessage.Builder middleAAInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleAAInnerMsgDescriptor = middleAAInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAInnerMsg = middleAAInnerMsgBuilder + .setField(middleAAInnerMsgDescriptor.findFieldByName("ival"), 1L) + .setField(middleAAInnerMsgDescriptor.findFieldByName("booly"), true) + .build(); + + DynamicMessage.Builder middleAAMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA"); + Descriptors.Descriptor middleAAMsgDescriptor = middleAAMsgBuilder.getDescriptorForType(); + DynamicMessage middleAAMsg = middleAAMsgBuilder + .setField(middleAAMsgDescriptor.findFieldByName("inner"), middleAAInnerMsg) + .build(); + + DynamicMessage.Builder middleBBInnerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleAA.Inner"); + Descriptors.Descriptor middleBBInnerMsgDescriptor = middleBBInnerMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBInnerMsg = middleBBInnerMsgBuilder + .setField(middleBBInnerMsgDescriptor.findFieldByName("ival"), 0L) + .setField(middleBBInnerMsgDescriptor.findFieldByName("booly"), false) + .build(); + + DynamicMessage.Builder middleBBMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer.MiddleBB"); + Descriptors.Descriptor middleBBMsgDescriptor = middleBBMsgBuilder.getDescriptorForType(); + DynamicMessage middleBBMsg = middleBBMsgBuilder + .setField(middleBBMsgDescriptor.findFieldByName("inner"), middleBBInnerMsg) + .build(); + + + DynamicMessage.Builder outerMsgBuilder = dynamicSchema.newMessageBuilder("testnested.Outer"); + Descriptors.Descriptor outerMsgBuilderDescriptor = outerMsgBuilder.getDescriptorForType(); + DynamicMessage outerMsg = outerMsgBuilder + .setField(outerMsgBuilderDescriptor.findFieldByName("middleAA"), middleAAMsg) + .setField(outerMsgBuilderDescriptor.findFieldByName("middleBB"), middleBBMsg) + .build(); + + assertEquals("{\n" + + " \"middleAA\": {\n" + + " \"inner\": {\n" + + " \"ival\": \"1\",\n" + + " \"booly\": true\n" + + " }\n" + + " },\n" + + " \"middleBB\": {\n" + + " \"inner\": {\n" + + " \"ival\": 0,\n" + + " \"booly\": false\n" + + " }\n" + + " }\n" + + "}", DynamicProtoUtils.dynamicMsgToJson(outerMsgBuilderDescriptor, outerMsg.toByteArray())); + } + + @Test + public void testProtoSchemaWithMessageOneOfs() throws Exception { + String schema = "syntax = \"proto3\";\n" + + "\n" + + "package testoneofs;\n" + + "\n" + + "message SubMessage {\n" + + " repeated string name = 1;\n" + + "}\n" + + "\n" + + "message SampleMessage {\n" + + " optional int32 id = 1;\n" + + " oneof testOneOf {\n" + + " string name = 4;\n" + + " SubMessage subMessage = 9;\n" + + " }\n" + + "}"; + ProtoFileElement protoFileElement = DynamicProtoUtils.getProtoFileElement(schema); + DynamicSchema dynamicSchema = DynamicProtoUtils.getDynamicSchema(protoFileElement, "test schema with message oneOfs"); + assertNotNull(dynamicSchema); + Set messageTypes = dynamicSchema.getMessageTypes(); + assertEquals(2, messageTypes.size()); + assertTrue(messageTypes.contains("testoneofs.SubMessage")); + assertTrue(messageTypes.contains("testoneofs.SampleMessage")); + + DynamicMessage.Builder sampleMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SampleMessage"); + Descriptors.Descriptor sampleMsgDescriptor = sampleMsgBuilder.getDescriptorForType(); + assertNotNull(sampleMsgDescriptor); + + List fields = sampleMsgDescriptor.getFields(); + assertEquals(3, fields.size()); + DynamicMessage sampleMsg = sampleMsgBuilder + .setField(sampleMsgDescriptor.findFieldByName("name"), "Bob") + .build(); + assertEquals("{\n" + " \"name\": \"Bob\"\n" + "}", DynamicProtoUtils.dynamicMsgToJson(sampleMsgDescriptor, sampleMsg.toByteArray())); + + DynamicMessage.Builder subMsgBuilder = dynamicSchema.newMessageBuilder("testoneofs.SubMessage"); + Descriptors.Descriptor subMsgDescriptor = subMsgBuilder.getDescriptorForType(); + DynamicMessage subMsg = subMsgBuilder + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "Alice") + .addRepeatedField(subMsgDescriptor.findFieldByName("name"), "John") + .build(); + + DynamicMessage sampleMsgWithOneOfSubMessage = sampleMsgBuilder.setField(sampleMsgDescriptor.findFieldByName("subMessage"), subMsg).build(); + assertEquals("{\n" + " \"subMessage\": {\n" + " \"name\": [\"Alice\", \"John\"]\n" + " }\n" + "}", + DynamicProtoUtils.dynamicMsgToJson(sampleMsgDescriptor, sampleMsgWithOneOfSubMessage.toByteArray())); + } + +} diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java index 119b011adb..522feed5fc 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeGrpcClient.java @@ -142,7 +142,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { @Override public void onError(Throwable t) { - log.debug("[{}] The rpc session received an error!", edgeKey, t); + log.warn("[{}] Stream was terminated due to error:", edgeKey, t); try { EdgeGrpcClient.this.disconnect(true); } catch (InterruptedException e) { @@ -153,7 +153,7 @@ public class EdgeGrpcClient implements EdgeRpcClient { @Override public void onCompleted() { - log.debug("[{}] The rpc session was closed!", edgeKey); + log.info("[{}] Stream was closed and completed successfully!", edgeKey); } }; } @@ -207,9 +207,17 @@ public class EdgeGrpcClient implements EdgeRpcClient { @Override public void sendSyncRequestMsg(boolean syncRequired) { + sendSyncRequestMsg(syncRequired, true); + } + + @Override + public void sendSyncRequestMsg(boolean syncRequired, boolean fullSync) { uplinkMsgLock.lock(); try { - SyncRequestMsg syncRequestMsg = SyncRequestMsg.newBuilder().setSyncRequired(syncRequired).build(); + SyncRequestMsg syncRequestMsg = SyncRequestMsg.newBuilder() + .setSyncRequired(syncRequired) + .setFullSync(fullSync) + .build(); this.inputStream.onNext(RequestMsg.newBuilder() .setMsgType(RequestMsgType.SYNC_REQUEST_RPC_MESSAGE) .setSyncRequestMsg(syncRequestMsg) diff --git a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java index 835f865e05..08214c61bd 100644 --- a/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java +++ b/common/edge-api/src/main/java/org/thingsboard/edge/rpc/EdgeRpcClient.java @@ -36,6 +36,8 @@ public interface EdgeRpcClient { void sendSyncRequestMsg(boolean syncRequired); + void sendSyncRequestMsg(boolean syncRequired, boolean fullSync); + void sendUplinkMsg(UplinkMsg uplinkMsg); void sendDownlinkResponseMsg(DownlinkResponseMsg downlinkResponseMsg); diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index 04d447cebf..249f251238 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -84,6 +84,7 @@ message ConnectResponseMsg { message SyncRequestMsg { bool syncRequired = 1; + optional bool fullSync = 2; } message SyncCompletedMsg { @@ -183,6 +184,7 @@ message DashboardUpdateMsg { optional int64 customerIdLSB = 5; string title = 6; string configuration = 7; + optional string assignedCustomers = 8; } message DeviceUpdateMsg { @@ -232,8 +234,10 @@ message AssetProfileUpdateMsg { bool default = 6; int64 defaultRuleChainIdMSB = 7; int64 defaultRuleChainIdLSB = 8; - string defaultQueueName = 9; - optional bytes image = 10; + int64 defaultDashboardIdMSB = 9; + int64 defaultDashboardIdLSB = 10; + optional string defaultQueueName = 11; + optional bytes image = 12; } message DeviceCredentialsUpdateMsg { @@ -250,12 +254,12 @@ message AssetUpdateMsg { int64 idLSB = 3; optional int64 customerIdMSB = 4; optional int64 customerIdLSB = 5; - optional int64 assetProfileIdMSB = 6; - optional int64 assetProfileIdLSB = 7; - string name = 8; - string type = 9; - optional string label = 10; - optional string additionalInfo = 11; + string name = 6; + string type = 7; + optional string label = 8; + optional string additionalInfo = 9; + optional int64 assetProfileIdMSB = 10; + optional int64 assetProfileIdLSB = 11; } message EntityViewUpdateMsg { @@ -399,16 +403,12 @@ message DeviceCredentialsRequestMsg { int64 deviceIdLSB = 2; } +// deprecated message DeviceProfileDevicesRequestMsg { int64 deviceProfileIdMSB = 1; int64 deviceProfileIdLSB = 2; } -message AssetProfileAssetsRequestMsg { - int64 assetProfileIdMSB = 1; - int64 assetProfileIdLSB = 2; -} - message WidgetBundleTypesRequestMsg { int64 widgetBundleIdMSB = 1; int64 widgetBundleIdLSB = 2; @@ -430,6 +430,11 @@ message DeviceRpcCallMsg { bool oneway = 7; RpcRequestMsg requestMsg = 8; RpcResponseMsg responseMsg = 9; + optional bool persisted = 10; + optional int32 retries = 11; + optional string additionalInfo = 12; + optional string serviceId = 13; + optional string sessionId = 14; } message RpcRequestMsg { @@ -513,10 +518,9 @@ message UplinkMsg { repeated UserCredentialsRequestMsg userCredentialsRequestMsg = 10; repeated DeviceCredentialsRequestMsg deviceCredentialsRequestMsg = 11; repeated DeviceRpcCallMsg deviceRpcCallMsg = 12; - repeated DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg = 13; + repeated DeviceProfileDevicesRequestMsg deviceProfileDevicesRequestMsg = 13; // deprecated repeated WidgetBundleTypesRequestMsg widgetBundleTypesRequestMsg = 14; repeated EntityViewsRequestMsg entityViewsRequestMsg = 15; - repeated AssetProfileAssetsRequestMsg assetProfileAssetsRequestMsg = 16; } message UplinkResponseMsg { @@ -556,5 +560,6 @@ message DownlinkMsg { repeated OtaPackageUpdateMsg otaPackageUpdateMsg = 22; repeated QueueUpdateMsg queueUpdateMsg = 23; repeated AssetProfileUpdateMsg assetProfileUpdateMsg = 24; + EdgeConfiguration edgeConfiguration = 25; } diff --git a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java index 7c7f300778..f17428f0bf 100644 --- a/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java +++ b/common/message/src/main/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItem.java @@ -20,10 +20,11 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.msg.gen.MsgProtos; +import java.io.Serializable; import java.util.UUID; @Data -public class TbMsgProcessingStackItem { +public class TbMsgProcessingStackItem implements Serializable { private final RuleChainId ruleChainId; private final RuleNodeId ruleNodeId; diff --git a/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItemTest.java b/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItemTest.java new file mode 100644 index 0000000000..8f8e59cd69 --- /dev/null +++ b/common/message/src/test/java/org/thingsboard/server/common/msg/TbMsgProcessingStackItemTest.java @@ -0,0 +1,37 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.msg; + +import org.junit.jupiter.api.Test; +import org.thingsboard.server.common.data.FSTUtils; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; + +import java.util.UUID; + +import static org.assertj.core.api.Assertions.assertThat; + +class TbMsgProcessingStackItemTest { + + @Test + void testSerialization() { + TbMsgProcessingStackItem item = new TbMsgProcessingStackItem(new RuleChainId(UUID.randomUUID()), new RuleNodeId(UUID.randomUUID())); + byte[] bytes = FSTUtils.encode(item); + TbMsgProcessingStackItem itemDecoded = FSTUtils.decode(bytes); + assertThat(item).isEqualTo(itemDecoded); + } + +} diff --git a/common/pom.xml b/common/pom.xml index 770cb402d2..dcf4685dea 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -47,6 +47,7 @@ coap-server edge-api version-control + script diff --git a/common/queue/pom.xml b/common/queue/pom.xml index c246ab358f..1ab33a3593 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -116,10 +116,6 @@ com.google.protobuf protobuf-java - - com.google.protobuf - protobuf-java-util - org.apache.curator curator-recipes diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java similarity index 95% rename from common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java rename to common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java index 11f01cba2b..81257389e5 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageClient.java +++ b/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/DefaultTbApiUsageReportClient.java @@ -17,9 +17,7 @@ package org.thingsboard.server.queue.usagestats; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; -import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.EntityType; @@ -28,6 +26,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TopicPartitionInfo; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceMsg; import org.thingsboard.server.gen.transport.TransportProtos.UsageStatsKVProto; import org.thingsboard.server.queue.TbQueueProducer; @@ -48,7 +47,7 @@ import java.util.concurrent.atomic.AtomicLong; @Component @Slf4j -public class DefaultTbApiUsageClient implements TbApiUsageClient { +public class DefaultTbApiUsageReportClient implements TbApiUsageReportClient { @Value("${usage.stats.report.enabled:true}") private boolean enabled; @@ -64,7 +63,7 @@ public class DefaultTbApiUsageClient implements TbApiUsageClient { private final TbQueueProducerProvider producerProvider; private TbQueueProducer> msgProducer; - public DefaultTbApiUsageClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { + public DefaultTbApiUsageReportClient(PartitionService partitionService, SchedulerComponent scheduler, TbQueueProducerProvider producerProvider) { this.partitionService = partitionService; this.scheduler = scheduler; this.producerProvider = producerProvider; diff --git a/common/script/pom.xml b/common/script/pom.xml new file mode 100644 index 0000000000..6f445fff76 --- /dev/null +++ b/common/script/pom.xml @@ -0,0 +1,42 @@ + + + 4.0.0 + + org.thingsboard + 3.4.2-SNAPSHOT + common + + org.thingsboard.common + script + pom + + Thingsboard Script Invoke Commons + https://thingsboard.io + + + UTF-8 + ${basedir}/../.. + + + script-api + remote-js-client + + + diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml new file mode 100644 index 0000000000..3068901013 --- /dev/null +++ b/common/script/remote-js-client/pom.xml @@ -0,0 +1,89 @@ + + + 4.0.0 + + org.thingsboard.common + 3.4.2-SNAPSHOT + script + + org.thingsboard.common.script + remote-js-client + jar + + Thingsboard Server JS Client for remote JS execution + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common + queue + + + org.thingsboard.common.script + script-api + + + org.thingsboard.rule-engine + rule-engine-api + + + org.thingsboard.common + data + + + org.thingsboard.common + message + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.awaitility + awaitility + test + + + + + + thingsboard-repo-deploy + ThingsBoard Repo Deployment + https://repo.thingsboard.io/artifactory/libs-release-public + + + + diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java rename to common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java index 78000a6e78..1096b91dfc 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java +++ b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/JsExecutorService.java @@ -22,7 +22,7 @@ import org.thingsboard.common.util.AbstractListeningExecutor; @Component public class JsExecutorService extends AbstractListeningExecutor { - @Value("${actors.rule.js_thread_pool_size}") + @Value("${js.remote.js_thread_pool_size:50}") private int jsExecutorThreadPoolSize; @Override diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java similarity index 56% rename from application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java rename to common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java index 328fc5ebb4..bda8d6429a 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java +++ b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsInvokeService.java @@ -15,39 +15,40 @@ */ package org.thingsboard.server.service.script; -import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; -import org.springframework.data.util.Pair; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; import org.springframework.util.StopWatch; import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.script.api.TbScriptException; +import org.thingsboard.script.api.js.AbstractJsInvokeService; +import org.thingsboard.script.api.js.JsScriptInfo; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; import org.thingsboard.server.gen.js.JsInvokeProtos; -import org.thingsboard.server.gen.js.JsInvokeProtos.JsInvokeErrorCode; import org.thingsboard.server.queue.TbQueueRequestTemplate; import org.thingsboard.server.queue.common.TbProtoJsQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; -import org.thingsboard.server.service.apiusage.TbApiUsageStateService; -import javax.annotation.Nullable; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.util.Map; +import java.util.Optional; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; @@ -56,11 +57,13 @@ import java.util.concurrent.locks.ReentrantLock; @Service public class RemoteJsInvokeService extends AbstractJsInvokeService { + @Getter @Value("${queue.js.max_eval_requests_timeout}") private long maxEvalRequestsTimeout; + @Getter @Value("${queue.js.max_requests_timeout}") - private long maxRequestsTimeout; + private long maxInvokeRequestsTimeout; @Value("${queue.js.max_exec_requests_timeout:2000}") private long maxExecRequestsTimeout; @@ -69,37 +72,34 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @Value("${js.remote.max_errors}") private int maxErrors; + @Getter @Value("${js.remote.max_black_list_duration_sec:60}") private int maxBlackListDurationSec; + @Getter @Value("${js.remote.stats.enabled:false}") private boolean statsEnabled; - private final AtomicInteger queuePushedMsgs = new AtomicInteger(0); - private final AtomicInteger queueInvokeMsgs = new AtomicInteger(0); - private final AtomicInteger queueEvalMsgs = new AtomicInteger(0); - private final AtomicInteger queueFailedMsgs = new AtomicInteger(0); - private final AtomicInteger queueTimeoutMsgs = new AtomicInteger(0); private final ExecutorService callbackExecutor = Executors.newFixedThreadPool( Runtime.getRuntime().availableProcessors(), ThingsBoardThreadFactory.forName("js-executor-remote-callback")); - public RemoteJsInvokeService(TbApiUsageStateService apiUsageStateService, TbApiUsageClient apiUsageClient) { - super(apiUsageStateService, apiUsageClient); + public RemoteJsInvokeService(Optional apiUsageStateClient, Optional apiUsageClient) { + super(apiUsageStateClient, apiUsageClient); + } + + @Override + protected Executor getCallbackExecutor() { + return callbackExecutor; + } + + @Override + protected String getStatsName() { + return "Queue JS Invoke Stats"; } @Scheduled(fixedDelayString = "${js.remote.stats.print_interval_ms}") public void printStats() { - if (statsEnabled) { - int pushedMsgs = queuePushedMsgs.getAndSet(0); - int invokeMsgs = queueInvokeMsgs.getAndSet(0); - int evalMsgs = queueEvalMsgs.getAndSet(0); - int failed = queueFailedMsgs.getAndSet(0); - int timedOut = queueTimeoutMsgs.getAndSet(0); - if (pushedMsgs > 0 || invokeMsgs > 0 || evalMsgs > 0 || failed > 0 || timedOut > 0) { - log.info("Queue JS Invoke Stats: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", - pushedMsgs, invokeMsgs + evalMsgs, invokeMsgs, evalMsgs, failed, timedOut); - } - } + super.printStats(); } @Autowired @@ -110,7 +110,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { @PostConstruct public void init() { - super.init(maxRequestsTimeout); + super.init(); requestTemplate.init(); } @@ -123,25 +123,25 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } @Override - protected ListenableFuture doEval(UUID scriptId, String scriptHash, String functionName, String scriptBody) { + protected ListenableFuture doEval(UUID scriptId, JsScriptInfo jsInfo, String scriptBody) { JsInvokeProtos.JsCompileRequest jsRequest = JsInvokeProtos.JsCompileRequest.newBuilder() - .setScriptHash(scriptHash) - .setFunctionName(functionName) + .setScriptHash(jsInfo.getHash()) + .setFunctionName(jsInfo.getFunctionName()) .setScriptBody(scriptBody).build(); JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() .setCompileRequest(jsRequest) .build(); - log.trace("Post compile request for scriptId [{}] (hash: {})", scriptId, scriptHash); - ListenableFuture> future = sendJsRequest(UUID.randomUUID(), jsRequestWrapper, maxEvalRequestsTimeout, queueEvalMsgs); + log.trace("Post compile request for scriptId [{}] (hash: {})", scriptId, jsInfo.getHash()); + ListenableFuture> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); return Futures.transform(future, response -> { JsInvokeProtos.JsCompileResponse compilationResult = response.getValue().getCompileResponse(); if (compilationResult.getSuccess()) { scriptsLock.lock(); try { - scriptIdToNameAndHashMap.put(scriptId, Pair.of(functionName, scriptHash)); - scriptHashToBodysMap.put(scriptHash, scriptBody); + scriptInfoMap.put(scriptId, jsInfo); + scriptHashToBodysMap.put(jsInfo.getHash(), scriptBody); } finally { scriptsLock.unlock(); } @@ -149,129 +149,111 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } else { log.debug("[{}] (hash: {}) Failed to compile script due to [{}]: {}", scriptId, compilationResult.getScriptHash(), compilationResult.getErrorCode().name(), compilationResult.getErrorDetails()); - throw new RuntimeException(compilationResult.getErrorDetails()); + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, new RuntimeException(compilationResult.getErrorDetails())); } }, callbackExecutor); } @Override - protected String constructFunctionName(UUID scriptId, String scriptHash) { - return "invokeInternal_" + scriptHash; - } - - @Override - protected ListenableFuture doInvokeFunction(UUID scriptId, String scriptHash, String functionName, Object[] args) { - log.trace("doInvokeFunction js-request for uuid {} with timeout {}ms", scriptHash, maxRequestsTimeout); + protected ListenableFuture doInvokeFunction(UUID scriptId, JsScriptInfo jsInfo, Object[] args) { + var scriptHash = jsInfo.getHash(); String scriptBody = scriptHashToBodysMap.get(scriptHash); if (scriptBody == null) { return Futures.immediateFailedFuture(new RuntimeException("No script body found for script hash [" + scriptHash + "] (script id: [" + scriptId + "])")); } - JsInvokeProtos.RemoteJsRequest jsRequestWrapper = buildJsInvokeRequest(scriptHash, functionName, args, false, null); - StopWatch stopWatch = new StopWatch(); - stopWatch.start(); + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = buildJsInvokeRequest(jsInfo, args, false, null); + + StopWatch stopWatch; + if (log.isTraceEnabled()) { + stopWatch = new StopWatch(); + stopWatch.start(); + } else { + stopWatch = null; + } UUID requestKey = UUID.randomUUID(); - ListenableFuture> future = sendJsRequest(requestKey, jsRequestWrapper, maxRequestsTimeout, queueInvokeMsgs); + ListenableFuture> future = requestTemplate.send(new TbProtoJsQueueMsg<>(requestKey, jsRequestWrapper)); return Futures.transformAsync(future, response -> { - stopWatch.stop(); - log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey()); + if (log.isTraceEnabled()) { + stopWatch.stop(); + log.trace("doInvokeFunction js-response took {}ms for uuid {}", stopWatch.getTotalTimeMillis(), response.getKey()); + } JsInvokeProtos.JsInvokeResponse invokeResult = response.getValue().getInvokeResponse(); if (invokeResult.getSuccess()) { return Futures.immediateFuture(invokeResult.getResult()); } else { - return handleInvokeError(requestKey, scriptId, scriptHash, invokeResult.getErrorCode(), - invokeResult.getErrorDetails(), functionName, args, scriptBody); + return handleInvokeError(requestKey, scriptId, jsInfo, invokeResult.getErrorCode(), invokeResult.getErrorDetails(), scriptBody, args); } }, callbackExecutor); } - private ListenableFuture handleInvokeError(UUID requestKey, UUID scriptId, String scriptHash, - JsInvokeErrorCode errorCode, String errorDetails, - String functionName, Object[] args, String scriptBody) { + @NotNull + private JsInvokeProtos.RemoteJsRequest buildJsInvokeRequest(JsScriptInfo jsInfo, Object[] args, boolean includeScriptBody, String scriptBody) { + JsInvokeProtos.JsInvokeRequest.Builder jsRequestBuilder = JsInvokeProtos.JsInvokeRequest.newBuilder() + .setScriptHash(jsInfo.getHash()) + .setFunctionName(jsInfo.getFunctionName()) + .setTimeout((int) maxExecRequestsTimeout); + if (includeScriptBody) { + jsRequestBuilder.setScriptBody(scriptBody); + } + + for (Object arg : args) { + jsRequestBuilder.addArgs(arg.toString()); + } + + JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() + .setInvokeRequest(jsRequestBuilder.build()) + .build(); + return jsRequestWrapper; + } + + private ListenableFuture handleInvokeError(UUID requestKey, UUID scriptId, JsScriptInfo jsInfo, + JsInvokeProtos.JsInvokeErrorCode errorCode, String errorDetails, + String scriptBody, Object[] args) { + final RuntimeException e = new RuntimeException(errorDetails); log.debug("[{}] Failed to invoke function due to [{}]: {}", scriptId, errorCode.name(), errorDetails); - RuntimeException e = new RuntimeException(errorDetails); - if (JsInvokeErrorCode.TIMEOUT_ERROR.equals(errorCode)) { - onScriptExecutionError(scriptId, e, scriptBody); - queueTimeoutMsgs.incrementAndGet(); - } else if (JsInvokeErrorCode.COMPILATION_ERROR.equals(errorCode)) { - onScriptExecutionError(scriptId, e, scriptBody); - } else if (JsInvokeErrorCode.NOT_FOUND_ERROR.equals(errorCode)) { + if (JsInvokeProtos.JsInvokeErrorCode.TIMEOUT_ERROR.equals(errorCode)) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.TIMEOUT, scriptBody, new TimeoutException()); + } else if (JsInvokeProtos.JsInvokeErrorCode.COMPILATION_ERROR.equals(errorCode)) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e); + } else if (JsInvokeProtos.JsInvokeErrorCode.NOT_FOUND_ERROR.equals(errorCode)) { log.debug("[{}] Remote JS executor couldn't find the script", scriptId); if (scriptBody != null) { - JsInvokeProtos.RemoteJsRequest invokeRequestWithScriptBody = buildJsInvokeRequest(scriptHash, functionName, args, true, scriptBody); + JsInvokeProtos.RemoteJsRequest invokeRequestWithScriptBody = buildJsInvokeRequest(jsInfo, args, true, scriptBody); log.debug("[{}] Sending invoke request again with script body", scriptId); - return Futures.transformAsync(sendJsRequest(requestKey, invokeRequestWithScriptBody, maxRequestsTimeout, queueInvokeMsgs), r -> { - JsInvokeProtos.JsInvokeResponse result = r.getValue().getInvokeResponse(); + ListenableFuture> future = requestTemplate.send(new TbProtoJsQueueMsg<>(requestKey, invokeRequestWithScriptBody)); + return Futures.transformAsync(future, response -> { + JsInvokeProtos.JsInvokeResponse result = response.getValue().getInvokeResponse(); if (result.getSuccess()) { return Futures.immediateFuture(result.getResult()); } else { - return handleInvokeError(requestKey, scriptId, scriptHash, result.getErrorCode(), - result.getErrorDetails(), functionName, args, null); + return handleInvokeError(requestKey, scriptId, jsInfo, result.getErrorCode(), result.getErrorDetails(), null, args); } }, MoreExecutors.directExecutor()); } } - queueFailedMsgs.incrementAndGet(); - return Futures.immediateFailedFuture(e); - } - - private ListenableFuture> sendJsRequest(UUID requestKey, JsInvokeProtos.RemoteJsRequest jsRequestWrapper, - long maxRequestsTimeout, AtomicInteger msgsCounter) { - ListenableFuture> future = requestTemplate.send(new TbProtoJsQueueMsg<>(requestKey, jsRequestWrapper)); - if (maxRequestsTimeout > 0) { - future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); - } - queuePushedMsgs.incrementAndGet(); - Futures.addCallback(future, new FutureCallback<>() { - @Override - public void onSuccess(@Nullable TbProtoQueueMsg result) { - msgsCounter.incrementAndGet(); - } - - @Override - public void onFailure(Throwable t) { - if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { - queueTimeoutMsgs.incrementAndGet(); - } - queueFailedMsgs.incrementAndGet(); - } - }, callbackExecutor); - return future; - } - - private JsInvokeProtos.RemoteJsRequest buildJsInvokeRequest(String scriptHash, String functionName, Object[] args, boolean includeScriptBody, String scriptBody) { - JsInvokeProtos.JsInvokeRequest.Builder jsRequestBuilder = JsInvokeProtos.JsInvokeRequest.newBuilder() - .setScriptHash(scriptHash) - .setFunctionName(functionName) - .setTimeout((int) maxExecRequestsTimeout); - if (includeScriptBody) jsRequestBuilder.setScriptBody(scriptBody); - for (Object arg : args) { - jsRequestBuilder.addArgs(arg.toString()); - } - - JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() - .setInvokeRequest(jsRequestBuilder.build()) - .build(); - return jsRequestWrapper; + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.RUNTIME, scriptBody, e); } @Override - protected void doRelease(UUID scriptId, String scriptHash, String functionName) throws Exception { - if (scriptIdToNameAndHashMap.values().stream().map(Pair::getSecond).anyMatch(hash -> hash.equals(scriptHash))) { + protected void doRelease(UUID scriptId, JsScriptInfo jsInfo) throws Exception { + String scriptHash = jsInfo.getHash(); + if (scriptInfoMap.values().stream().map(JsScriptInfo::getHash).anyMatch(hash -> hash.equals(scriptHash))) { return; } + JsInvokeProtos.JsReleaseRequest jsRequest = JsInvokeProtos.JsReleaseRequest.newBuilder() .setScriptHash(scriptHash) - .setFunctionName(functionName).build(); + .setFunctionName(jsInfo.getFunctionName()).build(); JsInvokeProtos.RemoteJsRequest jsRequestWrapper = JsInvokeProtos.RemoteJsRequest.newBuilder() .setReleaseRequest(jsRequest) .build(); ListenableFuture> future = requestTemplate.send(new TbProtoJsQueueMsg<>(UUID.randomUUID(), jsRequestWrapper)); - if (maxRequestsTimeout > 0) { - future = Futures.withTimeout(future, maxRequestsTimeout, TimeUnit.MILLISECONDS, timeoutExecutorService); + if (getMaxInvokeRequestsTimeout() > 0) { + future = Futures.withTimeout(future, getMaxInvokeRequestsTimeout(), TimeUnit.MILLISECONDS, timeoutExecutorService); } JsInvokeProtos.RemoteJsResponse response = future.get().getValue(); @@ -279,7 +261,7 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { if (releaseResponse.getSuccess()) { scriptsLock.lock(); try { - if (scriptIdToNameAndHashMap.values().stream().map(Pair::getSecond).noneMatch(h -> h.equals(scriptHash))) { + if (scriptInfoMap.values().stream().map(JsScriptInfo::getHash).noneMatch(hash -> hash.equals(scriptHash))) { scriptHashToBodysMap.remove(scriptHash); } } finally { @@ -290,9 +272,13 @@ public class RemoteJsInvokeService extends AbstractJsInvokeService { } } - @Override - protected long getMaxBlacklistDuration() { - return TimeUnit.SECONDS.toMillis(maxBlackListDurationSec); + protected String constructFunctionName(UUID scriptId, String scriptHash) { + return "invokeInternal_" + scriptHash; + } + + protected String getScriptHash(UUID scriptId) { + JsScriptInfo jsScriptInfo = scriptInfoMap.get(scriptId); + return jsScriptInfo != null ? jsScriptInfo.getHash() : null; } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java similarity index 100% rename from application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java rename to common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java index 8e604fdac7..b3c1d2268a 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java +++ b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsRequestEncoder.java @@ -17,8 +17,8 @@ package org.thingsboard.server.service.script; import com.google.protobuf.InvalidProtocolBufferException; import com.google.protobuf.util.JsonFormat; -import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.gen.js.JsInvokeProtos; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.kafka.TbKafkaEncoder; import java.nio.charset.StandardCharsets; diff --git a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java similarity index 100% rename from application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java rename to common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java index 3b647f6669..75ce86fea2 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java +++ b/common/script/remote-js-client/src/main/java/org/thingsboard/server/service/script/RemoteJsResponseDecoder.java @@ -16,9 +16,9 @@ package org.thingsboard.server.service.script; import com.google.protobuf.util.JsonFormat; +import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.TbQueueMsg; import org.thingsboard.server.queue.common.TbProtoQueueMsg; -import org.thingsboard.server.gen.js.JsInvokeProtos; import org.thingsboard.server.queue.kafka.TbKafkaDecoder; import java.io.IOException; diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml new file mode 100644 index 0000000000..62dc00ca97 --- /dev/null +++ b/common/script/script-api/pom.xml @@ -0,0 +1,121 @@ + + + 4.0.0 + + org.thingsboard.common + 3.4.2-SNAPSHOT + script + + org.thingsboard.common.script + script-api + jar + + Thingsboard Server Script invoke API + https://thingsboard.io + + + UTF-8 + ${basedir}/../../.. + + + + + org.thingsboard.common + data + + + org.thingsboard.common + stats + + + org.thingsboard.common + util + + + org.javadelight + delight-nashorn-sandbox + + + com.google.code.gson + gson + + + org.slf4j + slf4j-api + + + org.slf4j + log4j-over-slf4j + + + ch.qos.logback + logback-core + + + ch.qos.logback + logback-classic + + + org.springframework + spring-context + + + com.google.guava + guava + + + org.apache.commons + commons-lang3 + + + org.thingsboard + mvel2 + + + org.springframework.boot + spring-boot-starter-web + provided + + + org.springframework.boot + spring-boot-starter-test + test + + + org.junit.vintage + junit-vintage-engine + test + + + org.awaitility + awaitility + test + + + + + + thingsboard-repo-deploy + ThingsBoard Repo Deployment + https://repo.thingsboard.io/artifactory/libs-release-public + + + + diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java new file mode 100644 index 0000000000..9fb594a7dd --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/AbstractScriptInvokeService.java @@ -0,0 +1,288 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api; + +import com.google.common.util.concurrent.FutureCallback; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.util.Pair; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ThingsBoardThreadFactory; +import org.thingsboard.server.common.data.ApiUsageRecordKey; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; + +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicInteger; + +import static java.lang.String.format; + +@Slf4j +public abstract class AbstractScriptInvokeService implements ScriptInvokeService { + + protected final Map disabledScripts = new ConcurrentHashMap<>(); + + private final Optional apiUsageStateClient; + private final Optional apiUsageReportClient; + private final AtomicInteger pushedMsgs = new AtomicInteger(0); + private final AtomicInteger invokeMsgs = new AtomicInteger(0); + private final AtomicInteger evalMsgs = new AtomicInteger(0); + protected final AtomicInteger failedMsgs = new AtomicInteger(0); + protected final AtomicInteger timeoutMsgs = new AtomicInteger(0); + + private final FutureCallback evalCallback = new ScriptStatCallback<>(evalMsgs, timeoutMsgs, failedMsgs); + private final FutureCallback invokeCallback = new ScriptStatCallback<>(invokeMsgs, timeoutMsgs, failedMsgs); + + protected ScheduledExecutorService timeoutExecutorService; + + protected AbstractScriptInvokeService(Optional apiUsageStateClient, Optional apiUsageReportClient) { + this.apiUsageStateClient = apiUsageStateClient; + this.apiUsageReportClient = apiUsageReportClient; + } + + protected long getMaxEvalRequestsTimeout() { + return getMaxInvokeRequestsTimeout(); + } + + protected abstract long getMaxInvokeRequestsTimeout(); + + protected abstract long getMaxScriptBodySize(); + + protected abstract long getMaxTotalArgsSize(); + + protected abstract long getMaxResultSize(); + + protected abstract int getMaxBlackListDurationSec(); + + protected abstract int getMaxErrors(); + + protected abstract boolean isStatsEnabled(); + + protected abstract String getStatsName(); + + protected abstract Executor getCallbackExecutor(); + + protected abstract boolean isScriptPresent(UUID scriptId); + + protected abstract ListenableFuture doEvalScript(TenantId tenantId, ScriptType scriptType, String scriptBody, UUID scriptId, String[] argNames); + + protected abstract TbScriptExecutionTask doInvokeFunction(UUID scriptId, Object[] args); + + protected abstract void doRelease(UUID scriptId) throws Exception; + + public void init() { + if (getMaxEvalRequestsTimeout() > 0 || getMaxInvokeRequestsTimeout() > 0) { + timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("script-timeout")); + } + } + + public void stop() { + if (timeoutExecutorService != null) { + timeoutExecutorService.shutdownNow(); + } + } + + public void printStats() { + if (isStatsEnabled()) { + int pushed = pushedMsgs.getAndSet(0); + int invoked = invokeMsgs.getAndSet(0); + int evaluated = evalMsgs.getAndSet(0); + int failed = failedMsgs.getAndSet(0); + int timedOut = timeoutMsgs.getAndSet(0); + if (pushed > 0 || invoked > 0 || evaluated > 0 || failed > 0 || timedOut > 0) { + log.info("{}: pushed [{}] received [{}] invoke [{}] eval [{}] failed [{}] timedOut [{}]", + getStatsName(), pushed, invoked + evaluated, invoked, evaluated, failed, timedOut); + } + } + } + + @Override + public ListenableFuture eval(TenantId tenantId, ScriptType scriptType, String scriptBody, String... argNames) { + if (!apiUsageStateClient.isPresent() || apiUsageStateClient.get().getApiUsageState(tenantId).isJsExecEnabled()) { + if (scriptBodySizeExceeded(scriptBody)) { + return error(format("Script body exceeds maximum allowed size of %s symbols", getMaxScriptBodySize())); + } + UUID scriptId = UUID.randomUUID(); + pushedMsgs.incrementAndGet(); + return withTimeoutAndStatsCallback(scriptId, null, + doEvalScript(tenantId, scriptType, scriptBody, scriptId, argNames), evalCallback, getMaxEvalRequestsTimeout()); + } else { + return error("Script Execution is disabled due to API limits!"); + } + } + + @Override + public ListenableFuture invokeScript(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args) { + if (!apiUsageStateClient.isPresent() || apiUsageStateClient.get().getApiUsageState(tenantId).isJsExecEnabled()) { + if (!isScriptPresent(scriptId)) { + return error("No compiled script found for scriptId: [" + scriptId + "]!"); + } + if (!isDisabled(scriptId)) { + if (argsSizeExceeded(args)) { + TbScriptException t = new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, null, new IllegalArgumentException( + format("Script input arguments exceed maximum allowed total args size of %s symbols", getMaxTotalArgsSize()) + )); + return Futures.immediateFailedFuture(handleScriptException(scriptId, null, t)); + } + apiUsageReportClient.ifPresent(client -> client.report(tenantId, customerId, ApiUsageRecordKey.JS_EXEC_COUNT, 1)); + pushedMsgs.incrementAndGet(); + log.trace("InvokeScript uuid {} with timeout {}ms", scriptId, getMaxInvokeRequestsTimeout()); + var task = doInvokeFunction(scriptId, args); + + var resultFuture = Futures.transformAsync(task.getResultFuture(), output -> { + String result = JacksonUtil.toString(output); + if (resultSizeExceeded(result)) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, null, new RuntimeException( + format("Script invocation result exceeds maximum allowed size of %s symbols", getMaxResultSize()) + )); + } + return Futures.immediateFuture(output); + }, MoreExecutors.directExecutor()); + + return withTimeoutAndStatsCallback(scriptId, task, resultFuture, invokeCallback, getMaxInvokeRequestsTimeout()); + } else { + String message = "Script invocation is blocked due to maximum error count " + + getMaxErrors() + ", scriptId " + scriptId + "!"; + log.warn(message); + return error(message); + } + } else { + return error("Script execution is disabled due to API limits!"); + } + } + + private ListenableFuture withTimeoutAndStatsCallback(UUID scriptId, TbScriptExecutionTask task, ListenableFuture future, FutureCallback statsCallback, long timeout) { + if (timeout > 0) { + future = Futures.withTimeout(future, timeout, TimeUnit.MILLISECONDS, timeoutExecutorService); + } + Futures.addCallback(future, statsCallback, getCallbackExecutor()); + return Futures.catchingAsync(future, Exception.class, + input -> Futures.immediateFailedFuture(handleScriptException(scriptId, task, input)), + MoreExecutors.directExecutor()); + } + + private Throwable handleScriptException(UUID scriptId, TbScriptExecutionTask task, Throwable t) { + boolean timeout = t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException); + if (timeout && task != null) { + task.stop(); + } + boolean blockList = timeout; + String scriptBody = null; + if (t instanceof TbScriptException) { + var scriptException = (TbScriptException) t; + scriptBody = scriptException.getBody(); + var cause = scriptException.getCause(); + switch (scriptException.getErrorCode()) { + case COMPILATION: + log.debug("[{}] Failed to compile script: {}", scriptId, scriptException.getBody(), cause); + break; + case TIMEOUT: + log.debug("[{}] Timeout to execute script: {}", scriptId, scriptException.getBody(), cause); + break; + case OTHER: + case RUNTIME: + log.debug("[{}] Failed to execute script: {}", scriptId, scriptException.getBody(), cause); + break; + } + blockList = timeout || scriptException.getErrorCode() != TbScriptException.ErrorCode.RUNTIME; + } + if (blockList) { + BlockedScriptInfo disableListInfo = disabledScripts.computeIfAbsent(scriptId, key -> new BlockedScriptInfo(getMaxBlackListDurationSec())); + int counter = disableListInfo.incrementAndGet(); + if (log.isDebugEnabled()) { + log.debug("Script has exception counter {} on disabledFunctions for id {}, exception {}, cause {}, scriptBody {}", + counter, scriptId, t, t.getCause(), scriptBody); + } else { + log.warn("Script has exception counter {} on disabledFunctions for id {}, exception {}", + counter, scriptId, t.getMessage()); + } + } + if (timeout) { + return new TimeoutException("Script timeout!"); + } else { + return t; + } + } + + @Override + public ListenableFuture release(UUID scriptId) { + if (isScriptPresent(scriptId)) { + try { + disabledScripts.remove(scriptId); + doRelease(scriptId); + } catch (Exception e) { + return Futures.immediateFailedFuture(e); + } + } + return Futures.immediateFuture(null); + } + + private boolean isDisabled(UUID scriptId) { + BlockedScriptInfo errorCount = disabledScripts.get(scriptId); + if (errorCount != null) { + if (errorCount.getExpirationTime() <= System.currentTimeMillis()) { + disabledScripts.remove(scriptId); + return false; + } else { + return errorCount.get() >= getMaxErrors(); + } + } else { + return false; + } + } + + private boolean scriptBodySizeExceeded(String scriptBody) { + if (getMaxScriptBodySize() <= 0) return false; + return scriptBody.length() > getMaxScriptBodySize(); + } + + private boolean argsSizeExceeded(Object[] args) { + if (getMaxTotalArgsSize() <= 0) return false; + long totalArgsSize = 0; + for (Object arg : args) { + if (arg instanceof CharSequence) { + totalArgsSize += ((CharSequence) arg).length(); + } else { + var str = JacksonUtil.toString(arg); + if (str != null) { + totalArgsSize += str.length(); + } + } + } + return totalArgsSize > getMaxTotalArgsSize(); + } + + private boolean resultSizeExceeded(String result) { + if (getMaxResultSize() <= 0) return false; + return result != null && result.length() > getMaxResultSize(); + } + + private ListenableFuture error(String message) { + return Futures.immediateFailedFuture(new RuntimeException(message)); + } +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/BlockedScriptInfo.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/BlockedScriptInfo.java new file mode 100644 index 0000000000..22d98e2035 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/BlockedScriptInfo.java @@ -0,0 +1,44 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class BlockedScriptInfo { + private final long maxScriptBlockDurationMs; + private final AtomicInteger counter; + private long expirationTime; + + BlockedScriptInfo(int maxScriptBlockDuration) { + this.maxScriptBlockDurationMs = TimeUnit.SECONDS.toMillis(maxScriptBlockDuration); + this.counter = new AtomicInteger(0); + } + + public int get() { + return counter.get(); + } + + public int incrementAndGet() { + int result = counter.incrementAndGet(); + expirationTime = System.currentTimeMillis() + maxScriptBlockDurationMs; + return result; + } + + public long getExpirationTime() { + return expirationTime; + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptFactory.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/RuleNodeScriptFactory.java similarity index 97% rename from application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptFactory.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/RuleNodeScriptFactory.java index 9aab3dde63..c92ef7c16a 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeScriptFactory.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/RuleNodeScriptFactory.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.script; +package org.thingsboard.script.api; public class RuleNodeScriptFactory { diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptInvokeService.java similarity index 68% rename from application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptInvokeService.java index f9af2f83e4..c765554572 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeService.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptInvokeService.java @@ -13,20 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.script; +package org.thingsboard.script.api; import com.google.common.util.concurrent.ListenableFuture; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.script.ScriptLanguage; import java.util.UUID; -public interface JsInvokeService { +public interface ScriptInvokeService { - ListenableFuture eval(TenantId tenantId, JsScriptType scriptType, String scriptBody, String... argNames); + ListenableFuture eval(TenantId tenantId, ScriptType scriptType, String scriptBody, String... argNames); - ListenableFuture invokeFunction(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args); + ListenableFuture invokeScript(TenantId tenantId, CustomerId customerId, UUID scriptId, Object... args); ListenableFuture release(UUID scriptId); + ScriptLanguage getLanguage(); + } diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptStatCallback.java similarity index 73% rename from application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptStatCallback.java index 75caa0511e..aa32875ebe 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsStatCallback.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptStatCallback.java @@ -13,34 +13,35 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.script; +package org.thingsboard.script.api; import com.google.common.util.concurrent.FutureCallback; import lombok.AllArgsConstructor; +import lombok.extern.slf4j.Slf4j; import javax.annotation.Nullable; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; +@Slf4j @AllArgsConstructor -public class JsStatCallback implements FutureCallback { - - private final AtomicInteger jsSuccessMsgs; - private final AtomicInteger jsTimeoutMsgs; - private final AtomicInteger jsFailedMsgs; +public class ScriptStatCallback implements FutureCallback { + private final AtomicInteger successMsgs; + private final AtomicInteger timeoutMsgs; + private final AtomicInteger failedMsgs; @Override public void onSuccess(@Nullable T result) { - jsSuccessMsgs.incrementAndGet(); + successMsgs.incrementAndGet(); } @Override public void onFailure(Throwable t) { if (t instanceof TimeoutException || (t.getCause() != null && t.getCause() instanceof TimeoutException)) { - jsTimeoutMsgs.incrementAndGet(); + timeoutMsgs.incrementAndGet(); } else { - jsFailedMsgs.incrementAndGet(); + failedMsgs.incrementAndGet(); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsScriptType.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptType.java similarity index 89% rename from application/src/main/java/org/thingsboard/server/service/script/JsScriptType.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptType.java index ee4b0c4c41..bd15e517b7 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsScriptType.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/ScriptType.java @@ -13,8 +13,8 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.script; +package org.thingsboard.script.api; -public enum JsScriptType { +public enum ScriptType { RULE_NODE_SCRIPT } diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java new file mode 100644 index 0000000000..8c6bcb61bb --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptException.java @@ -0,0 +1,40 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api; + +import lombok.Getter; + +import java.util.UUID; + +public class TbScriptException extends RuntimeException { + private static final long serialVersionUID = -1958193538782818284L; + + public static enum ErrorCode {COMPILATION, TIMEOUT, RUNTIME, OTHER} + + @Getter + private final UUID scriptId; + @Getter + private final ErrorCode errorCode; + @Getter + private final String body; + + public TbScriptException(UUID scriptId, ErrorCode errorCode, String body, Exception cause) { + super(cause); + this.scriptId = scriptId; + this.errorCode = errorCode; + this.body = body; + } +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptExecutionTask.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptExecutionTask.java new file mode 100644 index 0000000000..df67d9896f --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/TbScriptExecutionTask.java @@ -0,0 +1,30 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.Getter; +import lombok.RequiredArgsConstructor; + + +@RequiredArgsConstructor +public abstract class TbScriptExecutionTask { + + @Getter + private final ListenableFuture resultFuture; + + public abstract void stop(); +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/AbstractJsInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/AbstractJsInvokeService.java new file mode 100644 index 0000000000..1bfccad510 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/AbstractJsInvokeService.java @@ -0,0 +1,106 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.js; + +import com.google.common.hash.Hashing; +import com.google.common.util.concurrent.ListenableFuture; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.data.util.Pair; +import org.thingsboard.script.api.AbstractScriptInvokeService; +import org.thingsboard.script.api.RuleNodeScriptFactory; +import org.thingsboard.script.api.ScriptType; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; + +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +/** + * Created by ashvayka on 26.09.18. + */ +@Slf4j +public abstract class AbstractJsInvokeService extends AbstractScriptInvokeService implements JsInvokeService { + + protected final Map scriptInfoMap = new ConcurrentHashMap<>(); + + @Getter + @Value("${js.max_total_args_size:100000}") + private long maxTotalArgsSize; + @Getter + @Value("${js.max_result_size:300000}") + private long maxResultSize; + @Getter + @Value("${js.max_script_body_size:50000}") + private long maxScriptBodySize; + + protected AbstractJsInvokeService(Optional apiUsageStateClient, Optional apiUsageReportClient) { + super(apiUsageStateClient, apiUsageReportClient); + } + + @Override + protected boolean isScriptPresent(UUID scriptId) { + return scriptInfoMap.containsKey(scriptId); + } + + @Override + protected JsScriptExecutionTask doInvokeFunction(UUID scriptId, Object[] args) { + return new JsScriptExecutionTask(doInvokeFunction(scriptId, scriptInfoMap.get(scriptId), args)); + } + + @Override + protected ListenableFuture doEvalScript(TenantId tenantId, ScriptType scriptType, String scriptBody, UUID scriptId, String[] argNames) { + String scriptHash = hash(tenantId, scriptBody); + String functionName = constructFunctionName(scriptId, scriptHash); + String jsScript = generateJsScript(scriptType, functionName, scriptBody, argNames); + return doEval(scriptId, new JsScriptInfo(scriptHash, functionName), jsScript); + } + + @Override + protected void doRelease(UUID scriptId) throws Exception { + doRelease(scriptId, scriptInfoMap.remove(scriptId)); + } + + protected abstract ListenableFuture doEval(UUID scriptId, JsScriptInfo jsInfo, String scriptBody); + + protected abstract ListenableFuture doInvokeFunction(UUID scriptId, JsScriptInfo jsInfo, Object[] args); + + protected abstract void doRelease(UUID scriptId, JsScriptInfo scriptInfo) throws Exception; + + private String generateJsScript(ScriptType scriptType, String functionName, String scriptBody, String... argNames) { + if (scriptType == ScriptType.RULE_NODE_SCRIPT) { + return RuleNodeScriptFactory.generateRuleNodeScript(functionName, scriptBody, argNames); + } + throw new RuntimeException("No script factory implemented for scriptType: " + scriptType); + } + + protected String constructFunctionName(UUID scriptId, String scriptHash) { + return "invokeInternal_" + scriptId.toString().replace('-', '_'); + } + + protected String hash(TenantId tenantId, String scriptBody) { + return Hashing.murmur3_128().newHasher() + .putLong(tenantId.getId().getMostSignificantBits()) + .putLong(tenantId.getId().getLeastSignificantBits()) + .putUnencodedChars(scriptBody) + .hash().toString(); + } + +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsInvokeService.java new file mode 100644 index 0000000000..e6a64d8c64 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsInvokeService.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.js; + +import org.thingsboard.script.api.ScriptInvokeService; +import org.thingsboard.server.common.data.script.ScriptLanguage; + +public interface JsInvokeService extends ScriptInvokeService { + + @Override + default ScriptLanguage getLanguage() { + return ScriptLanguage.JS; + } + +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsScriptExecutionTask.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsScriptExecutionTask.java new file mode 100644 index 0000000000..5e493bc663 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsScriptExecutionTask.java @@ -0,0 +1,31 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.js; + +import com.google.common.util.concurrent.ListenableFuture; +import org.thingsboard.script.api.TbScriptExecutionTask; + +public class JsScriptExecutionTask extends TbScriptExecutionTask { + + public JsScriptExecutionTask(ListenableFuture resultFuture) { + super(resultFuture); + } + + @Override + public void stop() { + // do nothing + } +} diff --git a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeRequest.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsScriptInfo.java similarity index 72% rename from application/src/main/java/org/thingsboard/server/service/script/JsInvokeRequest.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsScriptInfo.java index 92166e2ac7..795394cb95 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/JsInvokeRequest.java +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/JsScriptInfo.java @@ -13,17 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.script; +package org.thingsboard.script.api.js; -import java.util.List; +import lombok.Data; -/** - * Created by ashvayka on 25.09.18. - */ -public class JsInvokeRequest { +@Data +public class JsScriptInfo { - private String scriptId; - private String scriptBody; - private List args; + private final String hash; + private final String functionName; } diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java new file mode 100644 index 0000000000..61e03d4077 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/js/NashornJsInvokeService.java @@ -0,0 +1,179 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.js; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import delight.nashornsandbox.NashornSandbox; +import delight.nashornsandbox.NashornSandboxes; +import lombok.Getter; +import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.script.api.TbScriptException; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import javax.script.Invocable; +import javax.script.ScriptEngine; +import javax.script.ScriptEngineManager; +import javax.script.ScriptException; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.Executor; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.locks.ReentrantLock; + +@Slf4j +@ConditionalOnProperty(prefix = "js", value = "evaluator", havingValue = "local", matchIfMissing = true) +@Service +public class NashornJsInvokeService extends AbstractJsInvokeService { + + private NashornSandbox sandbox; + private ScriptEngine engine; + private ExecutorService monitorExecutorService; + private ListeningExecutorService jsExecutor; + + private final ReentrantLock evalLock = new ReentrantLock(); + + @Value("${js.local.use_js_sandbox}") + private boolean useJsSandbox; + + @Value("${js.local.monitor_thread_pool_size}") + private int monitorThreadPoolSize; + + @Value("${js.local.max_cpu_time}") + private long maxCpuTime; + + @Getter + @Value("${js.local.max_errors}") + private int maxErrors; + + @Getter + @Value("${js.local.max_black_list_duration_sec:60}") + private int maxBlackListDurationSec; + + @Getter + @Value("${js.local.max_requests_timeout:0}") + private long maxInvokeRequestsTimeout; + + @Getter + @Value("${js.local.stats.enabled:false}") + private boolean statsEnabled; + + @Value("${js.local.js_thread_pool_size:50}") + private int jsExecutorThreadPoolSize; + + public NashornJsInvokeService(Optional apiUsageStateClient, Optional apiUsageReportClient) { + super(apiUsageStateClient, apiUsageReportClient); + } + + @Override + protected String getStatsName() { + return "Nashorn JS Invoke Stats"; + } + + @Override + protected Executor getCallbackExecutor() { + return MoreExecutors.directExecutor(); + } + + @Scheduled(fixedDelayString = "${js.local.stats.print_interval_ms:10000}") + public void printStats() { + super.printStats(); + } + + @PostConstruct + public void init() { + super.init(); + jsExecutor = MoreExecutors.listeningDecorator(Executors.newWorkStealingPool(jsExecutorThreadPoolSize)); + if (useJsSandbox) { + sandbox = NashornSandboxes.create(); + monitorExecutorService = ThingsBoardExecutors.newWorkStealingPool(monitorThreadPoolSize, "nashorn-js-monitor"); + sandbox.setExecutor(monitorExecutorService); + sandbox.setMaxCPUTime(maxCpuTime); + sandbox.allowNoBraces(false); + sandbox.allowLoadFunctions(true); + sandbox.setMaxPreparedStatements(30); + } else { + ScriptEngineManager factory = new ScriptEngineManager(); + engine = factory.getEngineByName("nashorn"); + } + } + + @PreDestroy + public void stop() { + super.stop(); + if (monitorExecutorService != null) { + monitorExecutorService.shutdownNow(); + } + } + + @Override + protected ListenableFuture doEval(UUID scriptId, JsScriptInfo scriptInfo, String jsScript) { + return jsExecutor.submit(() -> { + try { + evalLock.lock(); + try { + if (useJsSandbox) { + sandbox.eval(jsScript); + } else { + engine.eval(jsScript); + } + } finally { + evalLock.unlock(); + } + scriptInfoMap.put(scriptId, scriptInfo); + return scriptId; + } catch (Exception e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, jsScript, e); + } + }); + } + + @Override + protected ListenableFuture doInvokeFunction(UUID scriptId, JsScriptInfo scriptInfo, Object[] args) { + return jsExecutor.submit(() -> { + try { + if (useJsSandbox) { + return sandbox.getSandboxedInvocable().invokeFunction(scriptInfo.getFunctionName(), args); + } else { + return ((Invocable) engine).invokeFunction(scriptInfo.getFunctionName(), args); + } + } catch (ScriptException e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.RUNTIME, null, e); + } catch (Exception e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, null, e); + } + }); + } + + protected void doRelease(UUID scriptId, JsScriptInfo scriptInfo) throws ScriptException { + if (useJsSandbox) { + sandbox.eval(scriptInfo.getFunctionName() + " = undefined;"); + } else { + engine.eval(scriptInfo.getFunctionName() + " = undefined;"); + } + } + +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/DefaultMvelInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/DefaultMvelInvokeService.java new file mode 100644 index 0000000000..901a49180e --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/DefaultMvelInvokeService.java @@ -0,0 +1,183 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.mvel; + +import com.google.common.util.concurrent.ListenableFuture; +import com.google.common.util.concurrent.ListeningExecutorService; +import com.google.common.util.concurrent.MoreExecutors; +import lombok.Getter; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import org.mvel2.ExecutionContext; +import org.mvel2.MVEL; +import org.mvel2.SandboxedParserConfiguration; +import org.mvel2.SandboxedParserContext; +import org.mvel2.ScriptMemoryOverflowException; +import org.mvel2.optimizers.OptimizerFactory; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Service; +import org.thingsboard.common.util.ThingsBoardExecutors; +import org.thingsboard.script.api.AbstractScriptInvokeService; +import org.thingsboard.script.api.ScriptType; +import org.thingsboard.script.api.TbScriptException; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; +import org.thingsboard.server.common.stats.TbApiUsageStateClient; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; +import java.io.Serializable; +import java.util.Collections; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.Executor; +import java.util.regex.Pattern; + +@Slf4j +@ConditionalOnProperty(prefix = "mvel", value = "enabled", havingValue = "true", matchIfMissing = true) +@Service +public class DefaultMvelInvokeService extends AbstractScriptInvokeService implements MvelInvokeService { + + protected Map scriptMap = new ConcurrentHashMap<>(); + private SandboxedParserConfiguration parserConfig; + + private static final Pattern NEW_KEYWORD_PATTERN = Pattern.compile("new\\s"); + + @Getter + @Value("${mvel.max_total_args_size:100000}") + private long maxTotalArgsSize; + @Getter + @Value("${mvel.max_result_size:300000}") + private long maxResultSize; + @Getter + @Value("${mvel.max_script_body_size:50000}") + private long maxScriptBodySize; + + @Getter + @Value("${mvel.max_errors:3}") + private int maxErrors; + + @Getter + @Value("${mvel.max_black_list_duration_sec:60}") + private int maxBlackListDurationSec; + + @Getter + @Value("${mvel.max_requests_timeout:0}") + private long maxInvokeRequestsTimeout; + + @Getter + @Value("${mvel.stats.enabled:false}") + private boolean statsEnabled; + + @Value("${mvel.thread_pool_size:50}") + private int threadPoolSize; + + @Value("${mvel.max_memory_limit_mb:8}") + private long maxMemoryLimitMb; + + private ListeningExecutorService executor; + + protected DefaultMvelInvokeService(Optional apiUsageStateClient, Optional apiUsageReportClient) { + super(apiUsageStateClient, apiUsageReportClient); + } + + @Scheduled(fixedDelayString = "${mvel.stats.print_interval_ms:10000}") + public void printStats() { + super.printStats(); + } + + @SneakyThrows + @PostConstruct + public void init() { + super.init(); + OptimizerFactory.setDefaultOptimizer(OptimizerFactory.SAFE_REFLECTIVE); + parserConfig = new SandboxedParserConfiguration(); + parserConfig.addImport("JSON", TbJson.class); + parserConfig.registerDataType("Date", TbDate.class, date -> 8L); + TbUtils.register(parserConfig); + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(threadPoolSize, "mvel-executor")); + try { + // Special command to warm up MVEL engine + Serializable script = MVEL.compileExpression("var warmUp = {}; warmUp", new SandboxedParserContext(parserConfig)); + MVEL.executeTbExpression(script, new ExecutionContext(parserConfig), Collections.emptyMap()); + } catch (Exception e) { + // do nothing + } + } + + @PreDestroy + public void destroy() { + if (executor != null) { + executor.shutdownNow(); + } + } + + @Override + protected String getStatsName() { + return "MVEL Scripts Stats"; + } + + @Override + protected Executor getCallbackExecutor() { + return MoreExecutors.directExecutor(); + } + + @Override + protected boolean isScriptPresent(UUID scriptId) { + return scriptMap.containsKey(scriptId); + } + + @Override + protected ListenableFuture doEvalScript(TenantId tenantId, ScriptType scriptType, String scriptBody, UUID scriptId, String[] argNames) { + return executor.submit(() -> { + try { + Serializable compiledScript = MVEL.compileExpression(scriptBody, new SandboxedParserContext(parserConfig)); + MvelScript script = new MvelScript(compiledScript, scriptBody, argNames); + scriptMap.put(scriptId, script); + return scriptId; + } catch (Exception e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e); + } + }); + } + + @Override + protected MvelScriptExecutionTask doInvokeFunction(UUID scriptId, Object[] args) { + ExecutionContext executionContext = new ExecutionContext(this.parserConfig, maxMemoryLimitMb * 1024 * 1024); + return new MvelScriptExecutionTask(executionContext, executor.submit(() -> { + MvelScript script = scriptMap.get(scriptId); + if (script == null) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, null, new RuntimeException("Script not found!")); + } + try { + return MVEL.executeTbExpression(script.getCompiledScript(), executionContext, script.createVars(args)); + } catch (ScriptMemoryOverflowException e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, script.getScriptBody(), new RuntimeException("Script memory overflow!")); + } catch (Exception e) { + throw new TbScriptException(scriptId, TbScriptException.ErrorCode.RUNTIME, script.getScriptBody(), e); + } + })); + } + + @Override + protected void doRelease(UUID scriptId) throws Exception { + scriptMap.remove(scriptId); + } +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelInvokeService.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelInvokeService.java new file mode 100644 index 0000000000..cd5678018a --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelInvokeService.java @@ -0,0 +1,28 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.mvel; + +import org.thingsboard.script.api.ScriptInvokeService; +import org.thingsboard.server.common.data.script.ScriptLanguage; + +public interface MvelInvokeService extends ScriptInvokeService { + + @Override + default ScriptLanguage getLanguage() { + return ScriptLanguage.MVEL; + } + +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScript.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScript.java new file mode 100644 index 0000000000..7a84c7b0af --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScript.java @@ -0,0 +1,41 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.mvel; + +import lombok.Data; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +@Data +public class MvelScript { + + private final Serializable compiledScript; + private final String scriptBody; + private final String[] argNames; + + public Map createVars(Object[] args) { + if (args == null || args.length != argNames.length) { + throw new IllegalArgumentException("Invalid number of argument values"); + } + var result = new HashMap<>(); + for (int i = 0; i < argNames.length; i++) { + result.put(argNames[i], args[i]); + } + return result; + } +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScriptExecutionTask.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScriptExecutionTask.java new file mode 100644 index 0000000000..e95b5b7b0c --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScriptExecutionTask.java @@ -0,0 +1,38 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.mvel; + +import com.google.common.util.concurrent.ListenableFuture; +import lombok.Data; +import lombok.Getter; +import org.mvel2.ExecutionContext; +import org.thingsboard.script.api.TbScriptExecutionTask; + + +public class MvelScriptExecutionTask extends TbScriptExecutionTask { + + private final ExecutionContext context; + + public MvelScriptExecutionTask(ExecutionContext context, ListenableFuture resultFuture) { + super(resultFuture); + this.context = context; + } + + @Override + public void stop(){ + context.stop(); + } +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbDate.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbDate.java new file mode 100644 index 0000000000..9b58bd9357 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbDate.java @@ -0,0 +1,95 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.mvel; + +import java.text.DateFormat; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.GregorianCalendar; +import java.util.Locale; +import java.util.TimeZone; + +public class TbDate extends Date { + + public TbDate() { + super(); + } + + public TbDate(String s) { + super(parse(s)); + } + + public TbDate(long date) { + super(date); + } + + public TbDate(int year, int month, int date) { + this(year, month, date, 0, 0, 0); + } + + public TbDate(int year, int month, int date, int hrs, int min) { + this(year, month, date, hrs, min, 0); + } + + public TbDate(int year, int month, int date, + int hrs, int min, int second) { + super(new GregorianCalendar(year, month, date, hrs, min, second).getTimeInMillis()); + } + + public String toDateString() { + DateFormat formatter = DateFormat.getDateInstance(); + return formatter.format(this); + } + + public String toTimeString() { + DateFormat formatter = DateFormat.getTimeInstance(DateFormat.LONG); + return formatter.format(this); + } + + public String toISOString() { + DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sssZ"); + return formatter.format(this); + } + + public String toLocaleString(String locale) { + DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); + return formatter.format(this); + } + + public String toLocaleString(String locale, String tz) { + DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.MEDIUM, DateFormat.MEDIUM, Locale.forLanguageTag(locale)); + formatter.setTimeZone(TimeZone.getTimeZone(tz)); + return formatter.format(this); + } + + public static long now() { + return System.currentTimeMillis(); + } + + public static long parse(String value) { + try { + return Date.parse(value); + } catch (IllegalArgumentException e) { + return -1; + } + } + + public static long UTC(int year, int month, int date, + int hrs, int min, int sec) { + return Date.UTC(year - 1900, month, date, hrs, min, sec); + } + +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbJson.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbJson.java new file mode 100644 index 0000000000..18f3abb4df --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbJson.java @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.mvel; + +import com.fasterxml.jackson.databind.JsonNode; +import org.mvel2.ExecutionContext; +import org.mvel2.util.ArgsRepackUtil; +import org.thingsboard.common.util.JacksonUtil; + +import java.io.IOException; +import java.util.List; +import java.util.Map; + +public class TbJson { + + public static String stringify(Object value) { + return value != null ? JacksonUtil.toString(value) : "null"; + } + + public static Object parse(ExecutionContext ctx, String value) throws IOException { + if (value != null) { + JsonNode node = JacksonUtil.toJsonNode(value); + if (node.isObject()) { + return ArgsRepackUtil.repack(ctx, JacksonUtil.convertValue(node, Map.class)); + } else if (node.isArray()) { + return ArgsRepackUtil.repack(ctx, JacksonUtil.convertValue(node, List.class)); + } else if (node.isDouble()) { + return node.doubleValue(); + } else if (node.isLong()) { + return node.longValue(); + } else if (node.isInt()) { + return node.intValue(); + } else if (node.isBoolean()) { + return node.booleanValue(); + } else if (node.isTextual()) { + return node.asText(); + } else if (node.isBinary()) { + return node.binaryValue(); + } else if (node.isNull()) { + return null; + } else { + return node.asText(); + } + } else { + return null; + } + } +} diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbUtils.java new file mode 100644 index 0000000000..ae38c13125 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbUtils.java @@ -0,0 +1,167 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.script.api.mvel; + +import org.mvel2.ExecutionContext; +import org.mvel2.ParserConfiguration; +import org.mvel2.execution.ExecutionArrayList; +import org.mvel2.util.MethodStub; + +import java.io.UnsupportedEncodingException; +import java.util.Base64; +import java.util.List; + +public class TbUtils { + + public static void register(ParserConfiguration parserConfig) throws Exception { + parserConfig.addImport("btoa", new MethodStub(TbUtils.class.getMethod("btoa", + String.class))); + parserConfig.addImport("atob", new MethodStub(TbUtils.class.getMethod("atob", + String.class))); + parserConfig.addImport("bytesToString", new MethodStub(TbUtils.class.getMethod("bytesToString", + List.class))); + parserConfig.addImport("bytesToString", new MethodStub(TbUtils.class.getMethod("bytesToString", + List.class, String.class))); + parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes", + ExecutionContext.class, String.class))); + parserConfig.addImport("stringToBytes", new MethodStub(TbUtils.class.getMethod("stringToBytes", + ExecutionContext.class, String.class, String.class))); + parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt", + String.class))); + parserConfig.addImport("parseInt", new MethodStub(TbUtils.class.getMethod("parseInt", + String.class, int.class))); + parserConfig.addImport("parseFloat", new MethodStub(TbUtils.class.getMethod("parseFloat", + String.class))); + parserConfig.addImport("parseDouble", new MethodStub(TbUtils.class.getMethod("parseDouble", + String.class))); + } + + public static void main(String[] args) { + System.out.println(Integer.class == int.class); + } + + public static String btoa(String input) { + return new String(Base64.getEncoder().encode(input.getBytes())); + } + + public static String atob(String encoded) { + return new String(Base64.getDecoder().decode(encoded)); + } + + public static String bytesToString(List bytesList) { + byte[] bytes = bytesFromList(bytesList); + return new String(bytes); + } + + public static String bytesToString(List bytesList, String charsetName) throws UnsupportedEncodingException { + byte[] bytes = bytesFromList(bytesList); + return new String(bytes, charsetName); + } + + public static List stringToBytes(ExecutionContext ctx, String str) { + byte[] bytes = str.getBytes(); + return bytesToList(ctx, bytes); + } + + public static List stringToBytes(ExecutionContext ctx, String str, String charsetName) throws UnsupportedEncodingException { + byte[] bytes = str.getBytes(charsetName); + return bytesToList(ctx, bytes); + } + + private static byte[] bytesFromList(List bytesList) { + byte[] bytes = new byte[bytesList.size()]; + for (int i = 0; i < bytesList.size(); i++) { + bytes[i] = bytesList.get(i); + } + return bytes; + } + + private static List bytesToList(ExecutionContext ctx, byte[] bytes) { + List list = new ExecutionArrayList<>(ctx); + for (int i = 0; i < bytes.length; i++) { + list.add(bytes[i]); + } + return list; + } + + public static Integer parseInt(String value) { + if (value != null) { + try { + int radix = 10; + if (isHexadecimal(value)) { + radix = 16; + } + return Integer.parseInt(prepareNumberString(value), radix); + } catch (NumberFormatException e) { + Float f = parseFloat(value); + if (f != null) { + return f.intValue(); + } + } + } + return null; + } + + public static Integer parseInt(String value, int radix) { + if (value != null) { + try { + return Integer.parseInt(prepareNumberString(value), radix); + } catch (NumberFormatException e) { + Float f = parseFloat(value); + if (f != null) { + return f.intValue(); + } + } + } + return null; + } + + public static Float parseFloat(String value) { + if (value != null) { + try { + return Float.parseFloat(prepareNumberString(value)); + } catch (NumberFormatException e) { + } + } + return null; + } + + public static Double parseDouble(String value) { + if (value != null) { + try { + return Double.parseDouble(prepareNumberString(value)); + } catch (NumberFormatException e) { + } + } + return null; + } + + private static boolean isHexadecimal(String value) { + return value != null && (value.contains("0x") || value.contains("0X")); + } + + private static String prepareNumberString(String value) { + if (value != null) { + value = value.trim(); + if (isHexadecimal(value)) { + value = value.replace("0x", ""); + value = value.replace("0X", ""); + } + value = value.replace(",", "."); + } + return value; + } +} diff --git a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java b/common/stats/src/main/java/org/thingsboard/server/common/stats/TbApiUsageReportClient.java similarity index 91% rename from common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java rename to common/stats/src/main/java/org/thingsboard/server/common/stats/TbApiUsageReportClient.java index 84ca085a7a..60c03c0f68 100644 --- a/common/queue/src/main/java/org/thingsboard/server/queue/usagestats/TbApiUsageClient.java +++ b/common/stats/src/main/java/org/thingsboard/server/common/stats/TbApiUsageReportClient.java @@ -13,13 +13,13 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.queue.usagestats; +package org.thingsboard.server.common.stats; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.TenantId; -public interface TbApiUsageClient { +public interface TbApiUsageReportClient { void report(TenantId tenantId, CustomerId customerId, ApiUsageRecordKey key, long value); diff --git a/common/stats/src/main/java/org/thingsboard/server/common/stats/TbApiUsageStateClient.java b/common/stats/src/main/java/org/thingsboard/server/common/stats/TbApiUsageStateClient.java new file mode 100644 index 0000000000..04e2cd277b --- /dev/null +++ b/common/stats/src/main/java/org/thingsboard/server/common/stats/TbApiUsageStateClient.java @@ -0,0 +1,25 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.common.stats; + +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.TenantId; + +public interface TbApiUsageStateClient { + + ApiUsageState getApiUsageState(TenantId tenantId); + +} diff --git a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java index ad07c9f9a4..2a320cc2ac 100644 --- a/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java +++ b/common/transport/mqtt/src/main/java/org/thingsboard/server/transport/mqtt/MqttTransportHandler.java @@ -883,7 +883,22 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { - log.error("[{}] Unexpected Exception", sessionId, cause); + if (cause instanceof IOException) { + if (log.isDebugEnabled()) { + log.debug("[{}][{}][{}] IOException: {}", sessionId, + Optional.ofNullable(this.deviceSessionCtx.getDeviceInfo()).map(TransportDeviceInfo::getDeviceId).orElse(null), + Optional.ofNullable(this.deviceSessionCtx.getDeviceInfo()).map(TransportDeviceInfo::getDeviceName).orElse(""), + cause); + } else if (log.isInfoEnabled()) { + log.info("[{}][{}][{}] IOException: {}", sessionId, + Optional.ofNullable(this.deviceSessionCtx.getDeviceInfo()).map(TransportDeviceInfo::getDeviceId).orElse(null), + Optional.ofNullable(this.deviceSessionCtx.getDeviceInfo()).map(TransportDeviceInfo::getDeviceName).orElse(""), + cause.getMessage()); + } + } else { + log.error("[{}] Unexpected Exception", sessionId, cause); + } + ctx.close(); if (cause instanceof OutOfMemoryError) { log.error("Received critical error. Going to shutdown the service."); diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java index 6fe0f29cfc..d7d685bd5a 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/adaptor/ProtoConverter.java @@ -27,6 +27,7 @@ import lombok.extern.slf4j.Slf4j; import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.gen.transport.TransportApiProtos; import org.thingsboard.server.gen.transport.TransportProtos; @@ -184,8 +185,7 @@ public class ProtoConverter { try { JsonElement paramsElement = JSON_PARSER.parse(params); rpcRequestJson.add("params", paramsElement); - JsonFormat.parser().ignoringUnknownFields().merge(GSON.toJson(rpcRequestJson), rpcRequestDynamicMessageBuilder); - DynamicMessage dynamicRpcRequest = rpcRequestDynamicMessageBuilder.build(); + DynamicMessage dynamicRpcRequest = DynamicProtoUtils.jsonToDynamicMessage(rpcRequestDynamicMessageBuilder, GSON.toJson(rpcRequestJson)); return dynamicRpcRequest.toByteArray(); } catch (Exception e) { throw new AdaptorException("Failed to convert ToDeviceRpcRequestMsg to Dynamic Rpc request message due to: ", e); @@ -200,8 +200,7 @@ public class ProtoConverter { } public static String dynamicMsgToJson(byte[] bytes, Descriptors.Descriptor descriptor) throws InvalidProtocolBufferException { - DynamicMessage dynamicMessage = DynamicMessage.parseFrom(descriptor, bytes); - return JsonFormat.printer().includingDefaultValueFields().print(dynamicMessage); + return DynamicProtoUtils.dynamicMsgToJson(descriptor, bytes); } } diff --git a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java index 8b532ae92e..fac988ed8b 100644 --- a/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java +++ b/common/transport/transport-api/src/main/java/org/thingsboard/server/common/transport/service/DefaultTransportService.java @@ -57,6 +57,7 @@ import org.thingsboard.server.common.msg.tools.TbRateLimitsException; import org.thingsboard.server.common.stats.MessagesStats; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.common.stats.StatsType; +import org.thingsboard.server.common.stats.TbApiUsageReportClient; import org.thingsboard.server.common.transport.DeviceDeletedEvent; import org.thingsboard.server.common.transport.DeviceProfileUpdatedEvent; import org.thingsboard.server.common.transport.DeviceUpdatedEvent; @@ -94,7 +95,6 @@ import org.thingsboard.server.queue.discovery.TbServiceInfoProvider; import org.thingsboard.server.queue.provider.TbQueueProducerProvider; import org.thingsboard.server.queue.provider.TbTransportQueueFactory; import org.thingsboard.server.queue.scheduler.SchedulerComponent; -import org.thingsboard.server.queue.usagestats.TbApiUsageClient; import org.thingsboard.server.queue.util.AfterStartUp; import org.thingsboard.server.queue.util.TbTransportComponent; @@ -157,7 +157,7 @@ public class DefaultTransportService implements TransportService { @Autowired @Lazy - private TbApiUsageClient apiUsageClient; + private TbApiUsageReportClient apiUsageClient; private final Map statsMap = new LinkedHashMap<>(); private final Gson gson = new Gson(); diff --git a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java index 98141e2a08..ec464caf7b 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java +++ b/common/util/src/main/java/org/thingsboard/common/util/JacksonUtil.java @@ -15,7 +15,9 @@ */ package org.thingsboard.common.util; +import com.fasterxml.jackson.core.JsonParser; import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonWriteFeature; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.MapperFeature; @@ -46,6 +48,10 @@ public class JacksonUtil { .configure(SerializationFeature.ORDER_MAP_ENTRIES_BY_KEYS, true) .configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true) .build(); + public static ObjectMapper ALLOW_UNQUOTED_FIELD_NAMES_MAPPER = JsonMapper.builder() + .configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false) + .configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true) + .build(); public static T convertValue(Object fromValue, Class toValueType) { try { @@ -118,27 +124,35 @@ public class JacksonUtil { } } + public static T treeToValue(JsonNode node, Class clazz) { + try { + return OBJECT_MAPPER.treeToValue(node, clazz); + } catch (IOException e) { + throw new IllegalArgumentException("Can't convert value: " + node.toString(), e); + } + } + public static JsonNode toJsonNode(String value) { + return toJsonNode(value, OBJECT_MAPPER); + } + + public static JsonNode toJsonNode(String value, ObjectMapper mapper) { if (value == null || value.isEmpty()) { return null; } try { - return OBJECT_MAPPER.readTree(value); + return mapper.readTree(value); } catch (IOException e) { throw new IllegalArgumentException(e); } } - public static T treeToValue(JsonNode node, Class clazz) { - try { - return OBJECT_MAPPER.treeToValue(node, clazz); - } catch (IOException e) { - throw new IllegalArgumentException("Can't convert value: " + node.toString(), e); - } + public static ObjectNode newObjectNode() { + return newObjectNode(OBJECT_MAPPER); } - public static ObjectNode newObjectNode() { - return OBJECT_MAPPER.createObjectNode(); + public static ObjectNode newObjectNode(ObjectMapper mapper) { + return mapper.createObjectNode(); } public static T clone(T value) { @@ -216,18 +230,27 @@ public class JacksonUtil { } public static void addKvEntry(ObjectNode entityNode, KvEntry kvEntry) { + addKvEntry(entityNode, kvEntry, kvEntry.getKey()); + } + + public static void addKvEntry(ObjectNode entityNode, KvEntry kvEntry, String key) { + addKvEntry(entityNode, kvEntry, key, OBJECT_MAPPER); + } + + public static void addKvEntry(ObjectNode entityNode, KvEntry kvEntry, String key, ObjectMapper mapper) { if (kvEntry.getDataType() == DataType.BOOLEAN) { - kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + kvEntry.getBooleanValue().ifPresent(value -> entityNode.put(key, value)); } else if (kvEntry.getDataType() == DataType.DOUBLE) { - kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + kvEntry.getDoubleValue().ifPresent(value -> entityNode.put(key, value)); } else if (kvEntry.getDataType() == DataType.LONG) { - kvEntry.getLongValue().ifPresent(value -> entityNode.put(kvEntry.getKey(), value)); + kvEntry.getLongValue().ifPresent(value -> entityNode.put(key, value)); } else if (kvEntry.getDataType() == DataType.JSON) { if (kvEntry.getJsonValue().isPresent()) { - entityNode.set(kvEntry.getKey(), JacksonUtil.toJsonNode(kvEntry.getJsonValue().get())); + entityNode.set(key, toJsonNode(kvEntry.getJsonValue().get(), mapper)); } } else { - entityNode.put(kvEntry.getKey(), kvEntry.getValueAsString()); + entityNode.put(key, kvEntry.getValueAsString()); } } + } diff --git a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java index 55bbb38700..51cf24398a 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java +++ b/common/util/src/main/java/org/thingsboard/common/util/ThingsBoardExecutors.java @@ -15,6 +15,10 @@ */ package org.thingsboard.common.util; +import com.google.common.util.concurrent.MoreExecutors; + +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.concurrent.ExecutorService; import java.util.concurrent.ForkJoinPool; @@ -47,4 +51,5 @@ public class ThingsBoardExecutors { public static ExecutorService newWorkStealingPool(int parallelism, Class clazz) { return newWorkStealingPool(parallelism, clazz.getSimpleName()); } + } diff --git a/common/util/src/test/java/org/thingsboard/common/util/JacksonUtilTest.java b/common/util/src/test/java/org/thingsboard/common/util/JacksonUtilTest.java new file mode 100644 index 0000000000..22cd1b3ff6 --- /dev/null +++ b/common/util/src/test/java/org/thingsboard/common/util/JacksonUtilTest.java @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.common.util; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; +import org.junit.Test; + +public class JacksonUtilTest { + + @Test + public void allow_unquoted_field_mapper_test() { + String data = "{data: 123}"; + JsonNode actualResult = JacksonUtil.toJsonNode(data, JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); // should be: {"data": 123} + ObjectNode expectedResult = JacksonUtil.newObjectNode(); + expectedResult.put("data", 123); // {"data": 123} + Assert.assertEquals(expectedResult, actualResult); + Assert.assertThrows(IllegalArgumentException.class, () -> JacksonUtil.toJsonNode(data)); // syntax exception due to missing quotes in the field name! + } + +} \ No newline at end of file diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java index 64f4e90cb8..13e1262ec5 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/DefaultGitRepositoryService.java @@ -47,6 +47,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; import java.util.Set; +import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -219,12 +220,14 @@ public class DefaultGitRepositoryService implements GitRepositoryService { @Override public void testRepository(TenantId tenantId, RepositorySettings settings) throws Exception { - Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); - GitRepository.test(settings, repositoryDirectory.toFile()); + Path testDirectory = Path.of(repositoriesFolder, "repo-test-" + UUID.randomUUID()); + GitRepository.test(settings, testDirectory.toFile()); } @Override public void initRepository(TenantId tenantId, RepositorySettings settings) throws Exception { + testRepository(tenantId, settings); + clearRepository(tenantId); log.debug("[{}] Init tenant repository started.", tenantId); Path repositoryDirectory = Path.of(repositoriesFolder, tenantId.getId().toString()); diff --git a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java index d820659e77..4c0dae4a12 100644 --- a/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java +++ b/common/version-control/src/main/java/org/thingsboard/server/service/sync/vc/GitRepository.java @@ -20,7 +20,9 @@ import com.google.common.collect.Ordering; import com.google.common.collect.Streams; import lombok.Data; import lombok.Getter; -import org.thingsboard.server.common.data.StringUtils; +import lombok.RequiredArgsConstructor; +import org.apache.commons.io.FileUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.sshd.common.util.security.SecurityUtils; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.Git; @@ -49,6 +51,7 @@ import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.FetchResult; import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.SshTransport; +import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider; import org.eclipse.jgit.transport.sshd.JGitKeyCache; import org.eclipse.jgit.transport.sshd.ServerKeyDatabase; @@ -61,8 +64,8 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.common.data.sync.vc.BranchInfo; -import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import org.thingsboard.server.common.data.sync.vc.RepositoryAuthMethod; +import org.thingsboard.server.common.data.sync.vc.RepositorySettings; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; @@ -70,6 +73,7 @@ import java.io.File; import java.io.IOException; import java.net.InetSocketAddress; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.KeyPair; import java.security.PublicKey; import java.util.ArrayList; @@ -78,70 +82,67 @@ import java.util.Comparator; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.UUID; import java.util.function.Function; import java.util.stream.Collectors; public class GitRepository { private final Git git; + private final AuthHandler authHandler; @Getter private final RepositorySettings settings; - private final CredentialsProvider credentialsProvider; - private final SshdSessionFactory sshSessionFactory; @Getter private final String directory; private ObjectId headId; - private GitRepository(Git git, RepositorySettings settings, CredentialsProvider credentialsProvider, SshdSessionFactory sshSessionFactory, String directory) { + private GitRepository(Git git, RepositorySettings settings, AuthHandler authHandler, String directory) { this.git = git; this.settings = settings; - this.credentialsProvider = credentialsProvider; - this.sshSessionFactory = sshSessionFactory; + this.authHandler = authHandler; this.directory = directory; } public static GitRepository clone(RepositorySettings settings, File directory) throws GitAPIException { - CredentialsProvider credentialsProvider = null; - SshdSessionFactory sshSessionFactory = null; - if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { - credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); - } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { - sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); - } CloneCommand cloneCommand = Git.cloneRepository() .setURI(settings.getRepositoryUri()) .setDirectory(directory) .setNoCheckout(true); - configureTransportCommand(cloneCommand, credentialsProvider, sshSessionFactory); + AuthHandler authHandler = AuthHandler.createFor(settings, directory); + authHandler.configureCommand(cloneCommand); Git git = cloneCommand.call(); - return new GitRepository(git, settings, credentialsProvider, sshSessionFactory, directory.getAbsolutePath()); + return new GitRepository(git, settings, authHandler, directory.getAbsolutePath()); } public static GitRepository open(File directory, RepositorySettings settings) throws IOException { Git git = Git.open(directory); - CredentialsProvider credentialsProvider = null; - SshdSessionFactory sshSessionFactory = null; - if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { - credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); - } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { - sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); - } - return new GitRepository(git, settings, credentialsProvider, sshSessionFactory, directory.getAbsolutePath()); + AuthHandler authHandler = AuthHandler.createFor(settings, directory); + return new GitRepository(git, settings, authHandler, directory.getAbsolutePath()); } - public static void test(RepositorySettings settings, File directory) throws GitAPIException { - CredentialsProvider credentialsProvider = null; - SshdSessionFactory sshSessionFactory = null; - if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { - credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); - } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { - sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); + public static void test(RepositorySettings settings, File directory) throws Exception { + AuthHandler authHandler = AuthHandler.createFor(settings, directory); + if (settings.isReadOnly()) { + LsRemoteCommand lsRemoteCommand = Git.lsRemoteRepository().setRemote(settings.getRepositoryUri()); + authHandler.configureCommand(lsRemoteCommand); + lsRemoteCommand.call(); + } else { + Files.createDirectories(directory.toPath()); + try { + Git git = Git.init().setDirectory(directory).call(); + GitRepository repository = new GitRepository(git, settings, authHandler, directory.getAbsolutePath()); + repository.execute(repository.git.remoteAdd() + .setName("origin") + .setUri(new URIish(settings.getRepositoryUri()))); + repository.push("", UUID.randomUUID().toString()); // trying to delete non-existing branch on remote repo + } finally { + try { + FileUtils.forceDelete(directory); + } catch (Exception ignored) {} + } } - LsRemoteCommand lsRemoteCommand = Git.lsRemoteRepository().setRemote(settings.getRepositoryUri()); - configureTransportCommand(lsRemoteCommand, credentialsProvider, sshSessionFactory); - lsRemoteCommand.call(); } public void fetch() throws GitAPIException { @@ -363,12 +364,12 @@ public class GitRepository { private , T> T execute(C command) throws GitAPIException { if (command instanceof TransportCommand) { - configureTransportCommand((TransportCommand) command, credentialsProvider, sshSessionFactory); + authHandler.configureCommand((TransportCommand) command); } return command.call(); } - private static Function> revCommitComparatorFunction = pageLink -> { + private static final Function> revCommitComparatorFunction = pageLink -> { SortOrder sortOrder = pageLink.getSortOrder(); if (sortOrder != null && sortOrder.getProperty().equals("timestamp") @@ -405,59 +406,76 @@ public class GitRepository { return new PageData<>(data, totalPages, totalElements, hasNext); } - private static void configureTransportCommand(TransportCommand transportCommand, CredentialsProvider credentialsProvider, SshdSessionFactory sshSessionFactory) { - if (credentialsProvider != null) { - transportCommand.setCredentialsProvider(credentialsProvider); - } - if (sshSessionFactory != null) { - transportCommand.setTransportConfigCallback(transport -> { - if (transport instanceof SshTransport) { - SshTransport sshTransport = (SshTransport) transport; - sshTransport.setSshSessionFactory(sshSessionFactory); - } - }); + @RequiredArgsConstructor + private static class AuthHandler { + private final CredentialsProvider credentialsProvider; + private final SshdSessionFactory sshSessionFactory; + + protected static AuthHandler createFor(RepositorySettings settings, File directory) { + CredentialsProvider credentialsProvider = null; + SshdSessionFactory sshSessionFactory = null; + if (RepositoryAuthMethod.USERNAME_PASSWORD.equals(settings.getAuthMethod())) { + credentialsProvider = newCredentialsProvider(settings.getUsername(), settings.getPassword()); + } else if (RepositoryAuthMethod.PRIVATE_KEY.equals(settings.getAuthMethod())) { + sshSessionFactory = newSshdSessionFactory(settings.getPrivateKey(), settings.getPrivateKeyPassword(), directory); + } + return new AuthHandler(credentialsProvider, sshSessionFactory); } - } - private static CredentialsProvider newCredentialsProvider(String username, String password) { - return new UsernamePasswordCredentialsProvider(username, password == null ? "" : password); - } + protected void configureCommand(TransportCommand command) { + if (credentialsProvider != null) { + command.setCredentialsProvider(credentialsProvider); + } + if (sshSessionFactory != null) { + command.setTransportConfigCallback(transport -> { + if (transport instanceof SshTransport) { + SshTransport sshTransport = (SshTransport) transport; + sshTransport.setSshSessionFactory(sshSessionFactory); + } + }); + } + } - private static SshdSessionFactory newSshdSessionFactory(String privateKey, String password, File directory) { - SshdSessionFactory sshSessionFactory = null; - if (StringUtils.isNotBlank(privateKey)) { - Iterable keyPairs = loadKeyPairs(privateKey, password); - sshSessionFactory = new SshdSessionFactoryBuilder() - .setPreferredAuthentications("publickey") - .setDefaultKeysProvider(file -> keyPairs) - .setHomeDirectory(directory) - .setSshDirectory(directory) - .setServerKeyDatabase((file, file2) -> new ServerKeyDatabase() { - @Override - public List lookup(String connectAddress, InetSocketAddress remoteAddress, Configuration config) { - return Collections.emptyList(); - } + private static CredentialsProvider newCredentialsProvider(String username, String password) { + return new UsernamePasswordCredentialsProvider(username, password == null ? "" : password); + } - @Override - public boolean accept(String connectAddress, InetSocketAddress remoteAddress, PublicKey serverKey, Configuration config, CredentialsProvider provider) { - return true; - } - }) - .build(new JGitKeyCache()); + private static SshdSessionFactory newSshdSessionFactory(String privateKey, String password, File directory) { + SshdSessionFactory sshSessionFactory = null; + if (StringUtils.isNotBlank(privateKey)) { + Iterable keyPairs = loadKeyPairs(privateKey, password); + sshSessionFactory = new SshdSessionFactoryBuilder() + .setPreferredAuthentications("publickey") + .setDefaultKeysProvider(file -> keyPairs) + .setHomeDirectory(directory) + .setSshDirectory(directory) + .setServerKeyDatabase((file, file2) -> new ServerKeyDatabase() { + @Override + public List lookup(String connectAddress, InetSocketAddress remoteAddress, Configuration config) { + return Collections.emptyList(); + } + + @Override + public boolean accept(String connectAddress, InetSocketAddress remoteAddress, PublicKey serverKey, Configuration config, CredentialsProvider provider) { + return true; + } + }) + .build(new JGitKeyCache()); + } + return sshSessionFactory; } - return sshSessionFactory; - } - private static Iterable loadKeyPairs(String privateKeyContent, String password) { - Iterable keyPairs = null; - try { - keyPairs = SecurityUtils.loadKeyPairIdentities(null, - null, new ByteArrayInputStream(privateKeyContent.getBytes()), (session, resourceKey, retryIndex) -> password); - } catch (Exception e) {} - if (keyPairs == null) { - throw new IllegalArgumentException("Failed to load ssh private key"); + private static Iterable loadKeyPairs(String privateKeyContent, String password) { + Iterable keyPairs = null; + try { + keyPairs = SecurityUtils.loadKeyPairIdentities(null, + null, new ByteArrayInputStream(privateKeyContent.getBytes()), (session, resourceKey, retryIndex) -> password); + } catch (Exception e) {} + if (keyPairs == null) { + throw new IllegalArgumentException("Failed to load ssh private key"); + } + return keyPairs; } - return keyPairs; } private static class NoMergesAndCommitMessageFilter extends RevFilter { diff --git a/dao/pom.xml b/dao/pom.xml index f911a7e043..35e253312e 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -177,6 +177,11 @@ test + + org.apache.cassandra + cassandra-thrift + test + com.google.protobuf protobuf-java diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index 35766e707a..87788d1b2b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -42,7 +42,11 @@ import java.util.UUID; */ public interface AlarmDao extends Dao { - ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); + Alarm findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); + + ListenableFuture findLatestByOriginatorAndTypeAsync(TenantId tenantId, EntityId originator, String type); + + Alarm findAlarmById(TenantId tenantId, UUID key); ListenableFuture findAlarmByIdAsync(TenantId tenantId, UUID key); diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 7addad7051..e6012602f1 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java @@ -114,7 +114,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } alarm.setCustomerId(entityService.fetchEntityCustomerId(alarm.getTenantId(), alarm.getOriginator())); if (alarm.getId() == null) { - Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()).get(); + Alarm existing = alarmDao.findLatestByOriginatorAndType(alarm.getTenantId(), alarm.getOriginator(), alarm.getType()); if (existing == null || existing.getStatus().isCleared()) { if (!alarmCreationEnabled) { throw new ApiUsageLimitsExceededException("Alarms creation is disabled"); @@ -124,7 +124,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ return updateAlarm(existing, alarm); } } else { - return updateAlarm(alarm).get(); + return updateAlarm(alarm); } } catch (ExecutionException | InterruptedException e) { throw new RuntimeException(e); @@ -132,7 +132,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { - return alarmDao.findLatestByOriginatorAndType(tenantId, originator, type); + return alarmDao.findLatestByOriginatorAndTypeAsync(tenantId, originator, type); } @Override @@ -145,19 +145,15 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override @Transactional public AlarmOperationResult deleteAlarm(TenantId tenantId, AlarmId alarmId) { - try { - log.debug("Deleting Alarm Id: {}", alarmId); - Alarm alarm = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()).get(); - if (alarm == null) { - return new AlarmOperationResult(alarm, false); - } - AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); - deleteEntityRelations(tenantId, alarm.getId()); - alarmDao.removeById(tenantId, alarm.getUuidId()); - return result; - } catch (ExecutionException | InterruptedException e) { - throw new RuntimeException(e); + log.debug("Deleting Alarm Id: {}", alarmId); + Alarm alarm = alarmDao.findAlarmById(tenantId, alarmId.getId()); + if (alarm == null) { + return new AlarmOperationResult(alarm, false); } + AlarmOperationResult result = new AlarmOperationResult(alarm, true, new ArrayList<>(getPropagationEntityIds(alarm))); + deleteEntityRelations(tenantId, alarm.getId()); + alarmDao.removeById(tenantId, alarm.getUuidId()); + return result; } private AlarmOperationResult createAlarm(Alarm alarm) throws InterruptedException, ExecutionException { @@ -197,19 +193,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ return relations.map(EntityRelation::getFrom).collect(Collectors.toCollection(LinkedHashSet::new)); } - private ListenableFuture updateAlarm(Alarm update) { + private AlarmOperationResult updateAlarm(Alarm update) { alarmDataValidator.validate(update, Alarm::getTenantId); - return getAndUpdate(update.getTenantId(), update.getId(), new Function() { - @Nullable - @Override - public AlarmOperationResult apply(@Nullable Alarm alarm) { - if (alarm == null) { - return null; - } else { - return updateAlarm(alarm, update); - } - } - }); + return getAndUpdate(update.getTenantId(), update.getId(), + (alarm) -> alarm == null ? null : updateAlarm(alarm, update)); } private AlarmOperationResult updateAlarm(Alarm oldAlarm, Alarm newAlarm) { @@ -233,7 +220,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public ListenableFuture ackAlarm(TenantId tenantId, AlarmId alarmId, long ackTime) { - return getAndUpdate(tenantId, alarmId, new Function() { + return getAndUpdateAsync(tenantId, alarmId, new Function() { @Nullable @Override public AlarmOperationResult apply(@Nullable Alarm alarm) { @@ -253,7 +240,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ @Override public ListenableFuture clearAlarm(TenantId tenantId, AlarmId alarmId, JsonNode details, long clearTime) { - return getAndUpdate(tenantId, alarmId, new Function() { + return getAndUpdateAsync(tenantId, alarmId, new Function() { @Nullable @Override public AlarmOperationResult apply(@Nullable Alarm alarm) { @@ -275,9 +262,16 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } @Override - public ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId) { + public Alarm findAlarmById(TenantId tenantId, AlarmId alarmId) { log.trace("Executing findAlarmById [{}]", alarmId); validateId(alarmId, "Incorrect alarmId " + alarmId); + return alarmDao.findAlarmById(tenantId, alarmId.getId()); + } + + @Override + public ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId) { + log.trace("Executing findAlarmByIdAsync [{}]", alarmId); + validateId(alarmId, "Incorrect alarmId " + alarmId); return alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()); } @@ -404,9 +398,15 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ } } - private ListenableFuture getAndUpdate(TenantId tenantId, AlarmId alarmId, Function function) { + private ListenableFuture getAndUpdateAsync(TenantId tenantId, AlarmId alarmId, Function function) { validateId(alarmId, "Alarm id should be specified!"); ListenableFuture entity = alarmDao.findAlarmByIdAsync(tenantId, alarmId.getId()); return Futures.transform(entity, function, readResultsProcessingExecutor); } + + private T getAndUpdate(TenantId tenantId, AlarmId alarmId, Function function) { + validateId(alarmId, "Alarm id should be specified!"); + Alarm entity = alarmDao.findAlarmById(tenantId, alarmId.getId()); + return function.apply(entity); + } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java b/dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java index 9c4ca8c536..c1d0cc60a7 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java +++ b/dao/src/main/java/org/thingsboard/server/dao/aspect/SqlDaoCallsAspect.java @@ -72,7 +72,7 @@ public class SqlDaoCallsAspect { try { if (log.isTraceEnabled()) { logTopNTenants(snapshots, Comparator.comparing(DbCallStatsSnapshot::getTotalTiming).reversed(), 0, snapshot -> { - logSnapshot(snapshot, 0, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), log::trace); + logSnapshot(snapshot, 0, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), "timing", log::trace); }); Map> byMethodStats = new HashMap<>(); @@ -97,29 +97,30 @@ public class SqlDaoCallsAspect { } else if (log.isDebugEnabled()) { log.debug("Total calls statistics below:"); logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalCalls).reversed(), 10, - s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getExecutions).reversed(), log::debug)); + s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getExecutions).reversed(), "executions", log::debug)); log.debug("Total timing statistics below:"); - logTopNTenants(snapshots, Comparator.comparingLong(DbCallStatsSnapshot::getTotalTiming).reversed(), - 10, s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), log::debug)); + logTopNTenants(snapshots, Comparator.comparingLong(DbCallStatsSnapshot::getTotalTiming).reversed(), 10, + s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), "timing", log::debug)); log.debug("Total errors statistics below:"); - logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalFailure).reversed(), - 10, s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getFailures).reversed(), log::debug)); + logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalFailure).reversed(), 10, + s -> logSnapshot(s, 10, Comparator.comparing(MethodCallStatsSnapshot::getFailures).reversed(), "failures", log::debug)); } else if (log.isInfoEnabled()) { - log.info("Total calls statistics below:"); - logTopNTenants(snapshots, Comparator.comparingInt(DbCallStatsSnapshot::getTotalFailure).reversed(), - 3, s -> logSnapshot(s, 3, Comparator.comparing(MethodCallStatsSnapshot::getFailures).reversed(), log::info)); + log.info("Total timing statistics below:"); + logTopNTenants(snapshots, Comparator.comparingLong(DbCallStatsSnapshot::getTotalTiming).reversed(), 3, + s -> logSnapshot(s, 3, Comparator.comparing(MethodCallStatsSnapshot::getTiming).reversed(), "timing", log::info)); } } finally { statsMap.clear(); } } - private void logSnapshot(DbCallStatsSnapshot snapshot, int limit, Comparator methodStatsComparator, Consumer logger) { + private void logSnapshot(DbCallStatsSnapshot snapshot, int limit, Comparator methodStatsComparator, String sortingKey, Consumer logger) { logger.accept(String.format("[%s]: calls: %s, failures: %s, exec time: %s ", snapshot.getTenantId(), snapshot.getTotalCalls(), snapshot.getTotalFailure(), snapshot.getTotalTiming())); var stream = snapshot.getMethodStats().entrySet().stream() .sorted(Map.Entry.comparingByValue(methodStatsComparator)); if (limit > 0) { + logger.accept(String.format("[%s] Top %s methods by %s:", snapshot.getTenantId(), limit, sortingKey)); stream = stream.limit(limit); } stream.forEach(e -> { diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java index 4d67a794b6..46a1a1bac5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java @@ -26,6 +26,7 @@ import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.dao.Dao; import org.thingsboard.server.dao.ExportableEntityDao; import org.thingsboard.server.dao.TenantEntityDao; +import org.thingsboard.server.common.data.util.TbPair; import java.util.List; import java.util.Optional; @@ -222,4 +223,6 @@ public interface AssetDao extends Dao, TenantEntityDao, ExportableEntityD * @return the list of asset objects */ PageData findAssetsByTenantIdAndEdgeIdAndType(UUID tenantId, UUID edgeId, String type, PageLink pageLink); + + PageData> getAllAssetTypes(PageLink pageLink); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java index da94141768..8bb862bea5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/audit/AuditLogServiceImpl.java @@ -23,13 +23,13 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; -import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.stereotype.Service; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.HasName; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.audit.ActionStatus; import org.thingsboard.server.common.data.audit.ActionType; import org.thingsboard.server.common.data.audit.AuditLog; @@ -257,10 +257,14 @@ public class AuditLogServiceImpl implements AuditLogService { String browser = extractParameter(String.class, 1, additionalInfo); String os = extractParameter(String.class, 2, additionalInfo); String device = extractParameter(String.class, 3, additionalInfo); + String provider = extractParameter(String.class, 4, additionalInfo); actionData.put("clientAddress", clientAddress); actionData.put("browser", browser); actionData.put("os", os); actionData.put("device", device); + if (StringUtils.hasText(provider)) { + actionData.put("provider", provider); + } break; case PROVISION_SUCCESS: case PROVISION_FAILURE: diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java index f9e94af613..be78866e7a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/BaseEdgeEventService.java @@ -16,8 +16,8 @@ package org.thingsboard.server.dao.edge; import com.google.common.util.concurrent.ListenableFuture; +import lombok.AllArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.id.EdgeId; @@ -28,13 +28,12 @@ import org.thingsboard.server.dao.service.DataValidator; @Service @Slf4j +@AllArgsConstructor public class BaseEdgeEventService implements EdgeEventService { - @Autowired - private EdgeEventDao edgeEventDao; + private final EdgeEventDao edgeEventDao; - @Autowired - private DataValidator edgeEventValidator; + private final DataValidator edgeEventValidator; @Override public ListenableFuture saveAsync(EdgeEvent edgeEvent) { diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java index 7a43d6c065..cb31869213 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeEventDao.java @@ -54,4 +54,6 @@ public interface EdgeEventDao extends Dao { */ void cleanupEvents(long ttl); + void migrateEdgeEvents(); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java index 5985674bd0..3ab3f6d905 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/edge/EdgeServiceImpl.java @@ -21,9 +21,11 @@ import com.google.common.base.Function; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.hibernate.exception.ConstraintViolationException; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; @@ -43,6 +45,7 @@ import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageDataIterableByTenantIdEntityId; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.EntitySearchDirection; @@ -97,6 +100,10 @@ public class EdgeServiceImpl extends AbstractCachedEntityService edgeValidator; + @Value("${edges.enabled}") + @Getter + private boolean edgesEnabled; + @TransactionalEventListener(classes = EdgeCacheEvictEvent.class) @Override public void handleEvictEvent(EdgeCacheEvictEvent event) { @@ -396,6 +403,23 @@ public class EdgeServiceImpl extends AbstractCachedEntityService findAllRelatedEdgeIds(TenantId tenantId, EntityId entityId) { + if (!edgesEnabled) { + return null; + } + if (EntityType.EDGE.equals(entityId.getEntityType())) { + return Collections.singletonList(new EdgeId(entityId.getId())); + } + PageDataIterableByTenantIdEntityId relatedEdgeIdsIterator = + new PageDataIterableByTenantIdEntityId<>(edgeService::findRelatedEdgeIdsByEntityId, tenantId, entityId, DEFAULT_PAGE_SIZE); + List result = new ArrayList<>(); + for (EdgeId edgeId : relatedEdgeIdsIterator) { + result.add(edgeId); + } + return result; + } + @Override public PageData findRelatedEdgeIdsByEntityId(TenantId tenantId, EntityId entityId, PageLink pageLink) { log.trace("[{}] Executing findRelatedEdgeIdsByEntityId [{}] [{}]", tenantId, entityId, pageLink); diff --git a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java index 6f6a942162..ceda7eb52c 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/relation/BaseRelationService.java @@ -20,7 +20,10 @@ import com.google.common.collect.Lists; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; +import com.google.common.util.concurrent.SettableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.beans.factory.annotation.Value; import org.springframework.context.ApplicationEventPublisher; import org.springframework.context.annotation.Lazy; import org.springframework.dao.ConcurrencyFailureException; @@ -28,6 +31,7 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.event.TransactionalEventListener; import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.cache.TbTransactionalCache; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.EntityId; @@ -44,13 +48,22 @@ import org.thingsboard.server.dao.entity.EntityService; import org.thingsboard.server.dao.exception.DataValidationException; import org.thingsboard.server.dao.service.ConstraintValidator; import org.thingsboard.server.dao.sql.JpaExecutorService; +import org.thingsboard.server.dao.sql.relation.JpaRelationQueryExecutorService; +import javax.annotation.PostConstruct; +import javax.annotation.PreDestroy; import java.util.ArrayList; import java.util.Collections; -import java.util.HashSet; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Queue; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.Executors; +import java.util.concurrent.ScheduledExecutorService; +import java.util.concurrent.TimeUnit; import java.util.function.BiConsumer; import static org.thingsboard.server.dao.service.Validator.validateId; @@ -67,15 +80,34 @@ public class BaseRelationService implements RelationService { private final TbTransactionalCache cache; private final ApplicationEventPublisher eventPublisher; private final JpaExecutorService executor; + private final JpaRelationQueryExecutorService relationsExecutor; + protected ScheduledExecutorService timeoutExecutorService; + + @Value("${sql.relations.query_timeout:20}") + private Integer relationQueryTimeout; public BaseRelationService(RelationDao relationDao, @Lazy EntityService entityService, TbTransactionalCache cache, - ApplicationEventPublisher eventPublisher, JpaExecutorService executor) { + ApplicationEventPublisher eventPublisher, JpaExecutorService executor, + JpaRelationQueryExecutorService relationsExecutor) { this.relationDao = relationDao; this.entityService = entityService; this.cache = cache; this.eventPublisher = eventPublisher; this.executor = executor; + this.relationsExecutor = relationsExecutor; + } + + @PostConstruct + public void init() { + timeoutExecutorService = Executors.newSingleThreadScheduledExecutor(ThingsBoardThreadFactory.forName("relations-query-timeout")); + } + + @PreDestroy + public void destroy() { + if (timeoutExecutorService != null) { + timeoutExecutorService.shutdownNow(); + } } @TransactionalEventListener(classes = EntityRelationEvent.class) @@ -375,7 +407,6 @@ public class BaseRelationService implements RelationService { @Override public ListenableFuture> findByQuery(TenantId tenantId, EntityRelationsQuery query) { - //boolean fetchLastLevelOnly = true; log.trace("Executing findByQuery [{}]", query); RelationsSearchParameters params = query.getParameters(); final List filters = query.getFilters(); @@ -386,7 +417,8 @@ public class BaseRelationService implements RelationService { int maxLvl = params.getMaxLevel() > 0 ? params.getMaxLevel() : Integer.MAX_VALUE; try { - ListenableFuture> relationSet = findRelationsRecursively(tenantId, params.getEntityId(), params.getDirection(), params.getRelationTypeGroup(), maxLvl, params.isFetchLastLevelOnly(), new ConcurrentHashMap<>()); + ListenableFuture> relationSet = findRelationsRecursively(tenantId, params.getEntityId(), params.getDirection(), + params.getRelationTypeGroup(), maxLvl, params.isFetchLastLevelOnly(), new ConcurrentHashMap<>()); return Futures.transform(relationSet, input -> { List relations = new ArrayList<>(); if (filters == null || filters.isEmpty()) { @@ -510,53 +542,89 @@ public class BaseRelationService implements RelationService { } } - private ListenableFuture> findRelationsRecursively(final TenantId tenantId, final EntityId rootId, final EntitySearchDirection direction, - RelationTypeGroup relationTypeGroup, int lvl, boolean fetchLastLevelOnly, - final ConcurrentHashMap uniqueMap) throws Exception { - if (lvl == 0) { - return Futures.immediateFuture(Collections.emptySet()); - } - lvl--; - //TODO: try to remove this blocking operation - Set children = new HashSet<>(findRelations(tenantId, rootId, direction, relationTypeGroup).get()); - Set childrenIds = new HashSet<>(); - for (EntityRelation childRelation : children) { - log.trace("Found Relation: {}", childRelation); - EntityId childId; - if (direction == EntitySearchDirection.FROM) { - childId = childRelation.getTo(); - } else { - childId = childRelation.getFrom(); + @RequiredArgsConstructor + private static class RelationQueueCtx { + final SettableFuture> future = SettableFuture.create(); + final Set result = ConcurrentHashMap.newKeySet(); + final Queue tasks = new ConcurrentLinkedQueue<>(); + + final TenantId tenantId; + final EntitySearchDirection direction; + final RelationTypeGroup relationTypeGroup; + final boolean fetchLastLevelOnly; + final int maxLvl; + final ConcurrentHashMap uniqueMap; + + } + + @RequiredArgsConstructor + private static class RelationTask { + private final int currentLvl; + private final EntityId root; + private final List prevRelations; + } + + private void processQueue(RelationQueueCtx ctx) { + RelationTask task = ctx.tasks.poll(); + while (task != null) { + List relations = findRelations(ctx.tenantId, task.root, ctx.direction, ctx.relationTypeGroup); + Map> newChildrenRelations = new HashMap<>(); + for (EntityRelation childRelation : relations) { + log.trace("Found Relation: {}", childRelation); + EntityId childId = ctx.direction == EntitySearchDirection.FROM ? childRelation.getTo() : childRelation.getFrom(); + if (ctx.uniqueMap.putIfAbsent(childId, Boolean.TRUE) == null) { + log.trace("Adding Relation: {}", childId); + newChildrenRelations.put(childId, new ArrayList<>()); + } + if (ctx.fetchLastLevelOnly) { + var list = newChildrenRelations.get(childId); + if (list != null) { + list.add(childRelation); + } + } } - if (uniqueMap.putIfAbsent(childId, Boolean.TRUE) == null) { - log.trace("Adding Relation: {}", childId); - if (childrenIds.add(childId)) { - log.trace("Added Relation: {}", childId); + if (ctx.fetchLastLevelOnly) { + if (relations.isEmpty()) { + ctx.result.addAll(task.prevRelations); + } else if (task.currentLvl == ctx.maxLvl) { + ctx.result.addAll(relations); } + } else { + ctx.result.addAll(relations); } + var finalTask = task; + newChildrenRelations.forEach((child, childRelations) -> { + var newLvl = finalTask.currentLvl + 1; + if (newLvl <= ctx.maxLvl) + ctx.tasks.add(new RelationTask(newLvl, child, childRelations)); + }); + task = ctx.tasks.poll(); } - List>> futures = new ArrayList<>(); - for (EntityId entityId : childrenIds) { - futures.add(findRelationsRecursively(tenantId, entityId, direction, relationTypeGroup, lvl, fetchLastLevelOnly, uniqueMap)); - } - //TODO: try to remove this blocking operation - List> relations = Futures.successfulAsList(futures).get(); - if (fetchLastLevelOnly && lvl > 0) { - children.clear(); + ctx.future.set(ctx.result); + } + + private ListenableFuture> findRelationsRecursively(final TenantId tenantId, final EntityId rootId, final EntitySearchDirection direction, + RelationTypeGroup relationTypeGroup, int lvl, boolean fetchLastLevelOnly, + final ConcurrentHashMap uniqueMap) { + if (lvl == 0) { + return Futures.immediateFuture(Collections.emptySet()); } - relations.forEach(r -> r.forEach(children::add)); - return Futures.immediateFuture(children); + var relationQueueCtx = new RelationQueueCtx(tenantId, direction, relationTypeGroup, fetchLastLevelOnly, lvl, uniqueMap); + relationQueueCtx.tasks.add(new RelationTask(1, rootId, Collections.emptyList())); + relationsExecutor.submit(() -> processQueue(relationQueueCtx)); + return Futures.withTimeout(relationQueueCtx.future, relationQueryTimeout, TimeUnit.SECONDS, timeoutExecutorService); } - private ListenableFuture> findRelations(final TenantId tenantId, final EntityId rootId, final EntitySearchDirection direction, RelationTypeGroup relationTypeGroup) { - ListenableFuture> relations; + + private List findRelations(final TenantId tenantId, final EntityId rootId, final EntitySearchDirection direction, RelationTypeGroup relationTypeGroup) { + List relations; if (relationTypeGroup == null) { relationTypeGroup = RelationTypeGroup.COMMON; } if (direction == EntitySearchDirection.FROM) { - relations = findByFromAsync(tenantId, rootId, relationTypeGroup); + relations = findByFrom(tenantId, rootId, relationTypeGroup); } else { - relations = findByToAsync(tenantId, rootId, relationTypeGroup); + relations = findByTo(tenantId, rootId, relationTypeGroup); } return relations; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java index 420739e37c..1d11d34b2d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/rule/BaseRuleChainService.java @@ -731,6 +731,8 @@ public class BaseRuleChainService extends AbstractEntityService implements RuleC ConstraintViolationException e = extractConstraintViolationException(t).orElse(null); if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_default_rule_chain_device_profile")) { throw new DataValidationException("The rule chain referenced by the device profiles cannot be deleted!"); + } else if (e != null && e.getConstraintName() != null && e.getConstraintName().equalsIgnoreCase("fk_default_rule_chain_asset_profile")) { + throw new DataValidationException("The rule chain referenced by the asset profiles cannot be deleted!"); } else { throw t; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java index 16c9bc4840..84eea112f3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/DeviceProfileDataValidator.java @@ -17,16 +17,6 @@ package org.thingsboard.server.dao.service.validator; import com.google.protobuf.Descriptors; import com.google.protobuf.DynamicMessage; -import com.squareup.wire.Syntax; -import com.squareup.wire.schema.Field; -import com.squareup.wire.schema.Location; -import com.squareup.wire.schema.internal.parser.EnumElement; -import com.squareup.wire.schema.internal.parser.FieldElement; -import com.squareup.wire.schema.internal.parser.MessageElement; -import com.squareup.wire.schema.internal.parser.OneOfElement; -import com.squareup.wire.schema.internal.parser.ProtoFileElement; -import com.squareup.wire.schema.internal.parser.ProtoParser; -import com.squareup.wire.schema.internal.parser.TypeElement; import org.eclipse.leshan.core.util.SecurityUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; @@ -35,6 +25,7 @@ import org.springframework.util.CollectionUtils; import org.thingsboard.server.common.data.DashboardInfo; import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.DynamicProtoUtils; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.device.credentials.lwm2m.LwM2MSecurityMode; import org.thingsboard.server.common.data.device.profile.CoapDeviceProfileTransportConfiguration; @@ -67,16 +58,16 @@ import org.thingsboard.server.dao.tenant.TenantService; import java.util.HashSet; import java.util.List; import java.util.Set; -import java.util.stream.Collectors; @Component public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator { - private static final Location LOCATION = new Location("", "", -1, -1); private static final String ATTRIBUTES_PROTO_SCHEMA = "attributes proto schema"; private static final String TELEMETRY_PROTO_SCHEMA = "telemetry proto schema"; private static final String RPC_REQUEST_PROTO_SCHEMA = "rpc request proto schema"; private static final String RPC_RESPONSE_PROTO_SCHEMA = "rpc response proto schema"; + private static final String EXCEPTION_PREFIX = "[Transport Configuration]"; + @Autowired private DeviceProfileDao deviceProfileDao; @Autowired @@ -94,10 +85,6 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator types = protoFileElement.getTypes(); - if (!types.isEmpty()) { - if (types.stream().noneMatch(typeElement -> typeElement instanceof MessageElement)) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " At least one Message definition should exists!"); - } else { - checkEnumElements(schemaName, getEnumElements(types)); - checkMessageElements(schemaName, getMessageTypes(types)); - } - } else { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Type elements is empty!"); - } - } - - private void checkFieldElements(String schemaName, List fieldElements) { - if (!fieldElements.isEmpty()) { - boolean hasRequiredLabel = fieldElements.stream().anyMatch(fieldElement -> { - Field.Label label = fieldElement.getLabel(); - return label != null && label.equals(Field.Label.REQUIRED); - }); - if (hasRequiredLabel) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Required labels are not supported!"); - } - boolean hasDefaultValue = fieldElements.stream().anyMatch(fieldElement -> fieldElement.getDefaultValue() != null); - if (hasDefaultValue) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Default values are not supported!"); - } - } - } - - private void checkEnumElements(String schemaName, List enumTypes) { - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getNestedTypes().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Nested types in Enum definitions are not supported!"); - } - if (enumTypes.stream().anyMatch(enumElement -> !enumElement.getOptions().isEmpty())) { - throw new IllegalArgumentException(invalidSchemaProvidedMessage(schemaName) + " Enum definitions options are not supported!"); - } - } - - private void checkMessageElements(String schemaName, List messageElementsList) { - if (!messageElementsList.isEmpty()) { - messageElementsList.forEach(messageElement -> { - checkProtoFileCommonSettings(schemaName, messageElement.getGroups().isEmpty(), - " Message definition groups don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getOptions().isEmpty(), - " Message definition options don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getExtensions().isEmpty(), - " Message definition extensions don't support!"); - checkProtoFileCommonSettings(schemaName, messageElement.getReserveds().isEmpty(), - " Message definition reserved elements don't support!"); - checkFieldElements(schemaName, messageElement.getFields()); - List oneOfs = messageElement.getOneOfs(); - if (!oneOfs.isEmpty()) { - oneOfs.forEach(oneOfElement -> { - checkProtoFileCommonSettings(schemaName, oneOfElement.getGroups().isEmpty(), - " OneOf definition groups don't support!"); - checkFieldElements(schemaName, oneOfElement.getFields()); - }); - } - List nestedTypes = messageElement.getNestedTypes(); - if (!nestedTypes.isEmpty()) { - List nestedEnumTypes = getEnumElements(nestedTypes); - if (!nestedEnumTypes.isEmpty()) { - checkEnumElements(schemaName, nestedEnumTypes); - } - List nestedMessageTypes = getMessageTypes(nestedTypes); - checkMessageElements(schemaName, nestedMessageTypes); - } - }); - } - } - - private List getMessageTypes(List types) { - return types.stream() - .filter(typeElement -> typeElement instanceof MessageElement) - .map(typeElement -> (MessageElement) typeElement) - .collect(Collectors.toList()); - } - - private List getEnumElements(List types) { - return types.stream() - .filter(typeElement -> typeElement instanceof EnumElement) - .map(typeElement -> (EnumElement) typeElement) - .collect(Collectors.toList()); - } private void validateTelemetryDynamicMessageFields(ProtoTransportPayloadConfiguration protoTransportPayloadTypeConfiguration) { String deviceTelemetryProtoSchema = protoTransportPayloadTypeConfiguration.getDeviceTelemetryProtoSchema(); Descriptors.Descriptor telemetryDynamicMessageDescriptor = protoTransportPayloadTypeConfiguration.getTelemetryDynamicMessageDescriptor(deviceTelemetryProtoSchema); if (telemetryDynamicMessageDescriptor == null) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Failed to get telemetryDynamicMessageDescriptor!"); + throw new DataValidationException(DynamicProtoUtils.invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA, EXCEPTION_PREFIX) + " Failed to get telemetryDynamicMessageDescriptor!"); } else { List fields = telemetryDynamicMessageDescriptor.getFields(); if (CollectionUtils.isEmpty(fields)) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " " + telemetryDynamicMessageDescriptor.getName() + " fields is empty!"); + throw new DataValidationException(DynamicProtoUtils.invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA, EXCEPTION_PREFIX) + " " + telemetryDynamicMessageDescriptor.getName() + " fields is empty!"); } else if (fields.size() == 2) { Descriptors.FieldDescriptor tsFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("ts"); Descriptors.FieldDescriptor valuesFieldDescriptor = telemetryDynamicMessageDescriptor.findFieldByName("values"); if (tsFieldDescriptor != null && valuesFieldDescriptor != null) { if (!Descriptors.FieldDescriptor.Type.MESSAGE.equals(valuesFieldDescriptor.getType())) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'values' has invalid data type. Only message type is supported!"); + throw new DataValidationException(DynamicProtoUtils.invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA, EXCEPTION_PREFIX) + " Field 'values' has invalid data type. Only message type is supported!"); } if (!Descriptors.FieldDescriptor.Type.INT64.equals(tsFieldDescriptor.getType())) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid data type. Only int64 type is supported!"); + throw new DataValidationException(DynamicProtoUtils.invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA, EXCEPTION_PREFIX) + " Field 'ts' has invalid data type. Only int64 type is supported!"); } if (!tsFieldDescriptor.hasOptionalKeyword()) { - throw new DataValidationException(invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA) + " Field 'ts' has invalid label. Field 'ts' should have optional keyword!"); + throw new DataValidationException(DynamicProtoUtils.invalidSchemaProvidedMessage(TELEMETRY_PROTO_SCHEMA, EXCEPTION_PREFIX) + " Field 'ts' has invalid label. Field 'ts' should have optional keyword!"); } } } @@ -384,39 +257,39 @@ public class DeviceProfileDataValidator extends AbstractHasOtaPackageValidator implements A } @Override - public ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { - return service.submit(() -> { - List latest = alarmRepository.findLatestByOriginatorAndType( - originator.getId(), - type, - PageRequest.of(0, 1)); - return latest.isEmpty() ? null : DaoUtil.getData(latest.get(0)); - }); + public Alarm findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type) { + List latest = alarmRepository.findLatestByOriginatorAndType( + originator.getId(), + type, + PageRequest.of(0, 1)); + return latest.isEmpty() ? null : DaoUtil.getData(latest.get(0)); + } + + @Override + public ListenableFuture findLatestByOriginatorAndTypeAsync(TenantId tenantId, EntityId originator, String type) { + return service.submit(() -> findLatestByOriginatorAndType(tenantId, originator, type)); + } + + @Override + public Alarm findAlarmById(TenantId tenantId, UUID key) { + return findById(tenantId, key); } @Override diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java index eadf0d2b84..aec62f0021 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/AssetRepository.java @@ -23,6 +23,7 @@ import org.springframework.data.repository.query.Param; import org.thingsboard.server.dao.ExportableEntityRepository; import org.thingsboard.server.dao.model.sql.AssetEntity; import org.thingsboard.server.dao.model.sql.AssetInfoEntity; +import org.thingsboard.server.common.data.util.TbPair; import java.util.List; import java.util.UUID; @@ -70,9 +71,9 @@ public interface AssetRepository extends JpaRepository, Expor "AND a.assetProfileId = :profileId " + "AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") Page findByTenantIdAndProfileId(@Param("tenantId") UUID tenantId, - @Param("profileId") UUID profileId, - @Param("searchText") String searchText, - Pageable pageable); + @Param("profileId") UUID profileId, + @Param("searchText") String searchText, + Pageable pageable); @Query("SELECT new org.thingsboard.server.dao.model.sql.AssetInfoEntity(a, c.title, c.additionalInfo, p.name) " + "FROM AssetEntity a " + @@ -186,14 +187,17 @@ public interface AssetRepository extends JpaRepository, Expor "AND a.type = :type " + "AND LOWER(a.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") Page findByTenantIdAndEdgeIdAndType(@Param("tenantId") UUID tenantId, - @Param("edgeId") UUID edgeId, - @Param("type") String type, - @Param("searchText") String searchText, - Pageable pageable); + @Param("edgeId") UUID edgeId, + @Param("type") String type, + @Param("searchText") String searchText, + Pageable pageable); Long countByTenantIdAndTypeIsNot(UUID tenantId, String type); @Query("SELECT externalId FROM AssetEntity WHERE id = :id") UUID getExternalIdById(@Param("id") UUID id); + @Query(value = "SELECT DISTINCT new org.thingsboard.server.common.data.util.TbPair(a.tenantId , a.type) FROM AssetEntity a") + Page> getAllAssetTypes(Pageable pageable); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java index 489e15502e..b0e1921e87 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/asset/JpaAssetDao.java @@ -28,14 +28,17 @@ import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.page.SortOrder; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.asset.AssetDao; import org.thingsboard.server.dao.model.sql.AssetEntity; import org.thingsboard.server.dao.model.sql.AssetInfoEntity; +import org.thingsboard.server.common.data.util.TbPair; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.util.SqlDao; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; @@ -243,6 +246,12 @@ public class JpaAssetDao extends JpaAbstractSearchTextDao im DaoUtil.toPageable(pageLink))); } + public PageData> getAllAssetTypes(PageLink pageLink) { + log.debug("Try to find all asset types and pageLink [{}]", pageLink); + return DaoUtil.pageToPageData(assetRepository.getAllAssetTypes( + DaoUtil.toPageable(pageLink, Arrays.asList(new SortOrder("tenantId"), new SortOrder("type"))))); + } + @Override public Long countByTenantId(TenantId tenantId) { return assetRepository.countByTenantIdAndTypeIsNot(tenantId.getId(), TB_SERVICE_QUEUE); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java index df182659bf..3ab21ae6ff 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/edge/JpaBaseEdgeEventDao.java @@ -17,10 +17,11 @@ package org.thingsboard.server.dao.sql.edge; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.ListenableFuture; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.edge.EdgeEvent; @@ -31,19 +32,17 @@ import org.thingsboard.server.common.data.page.TimePageLink; import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.edge.EdgeEventDao; +import org.thingsboard.server.dao.model.ModelConstants; import org.thingsboard.server.dao.model.sql.EdgeEventEntity; import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; import org.thingsboard.server.dao.sql.ScheduledLogExecutorComponent; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; +import org.thingsboard.server.dao.sqlts.insert.sql.SqlPartitioningRepository; import org.thingsboard.server.dao.util.SqlDao; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; -import java.sql.Connection; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; import java.util.Comparator; import java.util.Objects; import java.util.UUID; @@ -52,18 +51,25 @@ import java.util.function.Function; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; -@Slf4j @Component @SqlDao +@RequiredArgsConstructor +@Slf4j public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao implements EdgeEventDao { private final UUID systemTenantId = NULL_UUID; - @Autowired - ScheduledLogExecutorComponent logExecutor; + private final ScheduledLogExecutorComponent logExecutor; + + private final StatsFactory statsFactory; + + private final EdgeEventRepository edgeEventRepository; + + private final EdgeEventInsertRepository edgeEventInsertRepository; - @Autowired - private StatsFactory statsFactory; + private final SqlPartitioningRepository partitioningRepository; + + private final JdbcTemplate jdbcTemplate; @Value("${sql.edge_events.batch_size:1000}") private int batchSize; @@ -74,13 +80,15 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao queue; + @Value("${sql.edge_events.partitions_size:168}") + private int partitionSizeInHours; - @Autowired - private EdgeEventRepository edgeEventRepository; + @Value("${sql.ttl.edge_events.edge_events_ttl:2628000}") + private long edge_events_ttl; - @Autowired - private EdgeEventInsertRepository edgeEventInsertRepository; + private static final String TABLE_NAME = ModelConstants.EDGE_EVENT_COLUMN_FAMILY_NAME; + + private TbSqlBlockingQueueWrapper queue; @Override protected Class getEntityClass() { @@ -140,6 +148,7 @@ public class JpaBaseEdgeEventDao extends JpaAbstractSearchTextDao 0 ? System.currentTimeMillis() - TimeUnit.SECONDS.toMillis(edge_events_ttl) : 1629158400000L; + + long currentTime = System.currentTimeMillis(); + var partitionStepInMs = TimeUnit.HOURS.toMillis(partitionSizeInHours); + long numberOfPartitions = (currentTime - startTime) / partitionStepInMs; + + if (numberOfPartitions > 1000) { + String error = "Please adjust your edge event partitioning configuration. Configuration with partition size " + + "of " + partitionSizeInHours + " hours and corresponding TTL will use " + numberOfPartitions + " " + + "(> 1000) partitions which is not recommended!"; + log.error(error); + throw new RuntimeException(error); } + + while (startTime < currentTime) { + var endTime = startTime + partitionStepInMs; + log.info("Migrating edge event for time period: {} - {}", startTime, endTime); + callMigrationFunction(startTime, endTime, partitionStepInMs); + startTime = endTime; + } + log.info("Event edge migration finished"); + jdbcTemplate.execute("DROP TABLE IF EXISTS old_edge_event"); + } + + private void callMigrationFunction(long startTime, long endTime, long partitionSIzeInMs) { + jdbcTemplate.update("CALL migrate_edge_event(?, ?, ?)", startTime, endTime, partitionSIzeInMs); } } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationQueryExecutorService.java b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationQueryExecutorService.java new file mode 100644 index 0000000000..be6caad8d9 --- /dev/null +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/relation/JpaRelationQueryExecutorService.java @@ -0,0 +1,33 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.server.dao.sql.relation; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; +import org.thingsboard.common.util.AbstractListeningExecutor; + +@Component +public class JpaRelationQueryExecutorService extends AbstractListeningExecutor { + + @Value("${sql.relations.pool_size:4}") + private int poolSize; + + @Override + protected int getThreadPollSize() { + return poolSize; + } + +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java index 7042dcbb16..ac02d77bc8 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/tenant/TenantRepository.java @@ -53,5 +53,4 @@ public interface TenantRepository extends JpaRepository { @Query("SELECT t.id FROM TenantEntity t where t.tenantProfileId = :tenantProfileId") List findTenantIdsByTenantProfileId(@Param("tenantProfileId") UUID tenantProfileId); - } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java index ad8cac6a28..e5a7cfcae3 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/JpaUserDao.java @@ -60,6 +60,11 @@ public class JpaUserDao extends JpaAbstractSearchTextDao imple return DaoUtil.getData(userRepository.findByEmail(email)); } + @Override + public User findByTenantIdAndEmail(TenantId tenantId, String email) { + return DaoUtil.getData(userRepository.findByTenantIdAndEmail(tenantId.getId(), email)); + } + @Override public PageData findByTenantId(UUID tenantId, PageLink pageLink) { return DaoUtil.toPageData( diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java index 51f6138cc1..092873f960 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/user/UserRepository.java @@ -20,6 +20,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.query.Param; +import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.dao.model.sql.UserEntity; @@ -32,6 +33,8 @@ public interface UserRepository extends JpaRepository { UserEntity findByEmail(String email); + UserEntity findByTenantIdAndEmail(UUID tenantId, String email); + @Query("SELECT u FROM UserEntity u WHERE u.tenantId = :tenantId " + "AND u.customerId = :customerId AND u.authority = :authority " + "AND LOWER(u.searchText) LIKE LOWER(CONCAT('%', :searchText, '%'))") @@ -48,4 +51,5 @@ public interface UserRepository extends JpaRepository { Pageable pageable); Long countByTenantId(UUID tenantId); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 22c4661fcd..cf726c32c4 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java @@ -20,7 +20,7 @@ import com.google.common.util.concurrent.ListenableFuture; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Sort; +import org.springframework.data.domain.Sort.Direction; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.data.kv.Aggregation; @@ -143,8 +143,7 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq keyId, query.getStartTs(), query.getEndTs(), - PageRequest.of(0, query.getLimit(), - Sort.by(new Sort.Order(Sort.Direction.fromString(query.getOrder()), "ts").nullsNative()))); + PageRequest.ofSize(query.getLimit()).withSort(Direction.fromString(query.getOrder()), "ts")); tsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(query.getKey())); List tsKvEntries = DaoUtil.convertDataList(tsKvEntities); long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java index 6e9f9e61b8..c9daf1ec6d 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TimescaleTimeseriesDao.java @@ -175,9 +175,7 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements keyId, query.getStartTs(), query.getEndTs(), - PageRequest.of(0, query.getLimit(), - Sort.by(new Sort.Order(Sort.Direction.fromString(query.getOrder()), "ts").nullsNative()))); - ; + PageRequest.ofSize(query.getLimit()).withSort(Sort.Direction.fromString(query.getOrder()), "ts")); timescaleTsKvEntities.forEach(tsKvEntity -> tsKvEntity.setStrKey(strKey)); var tsKvEntries = DaoUtil.convertDataList(timescaleTsKvEntities); long lastTs = tsKvEntries.stream().map(TsKvEntry::getTs).max(Long::compare).orElse(query.getStartTs()); diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java index 075cffcbc7..821833cad0 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/timescale/TsKvTimescaleRepository.java @@ -31,14 +31,13 @@ import java.util.UUID; @TimescaleDBTsOrTsLatestDao public interface TsKvTimescaleRepository extends JpaRepository { - @Query("SELECT tskv FROM TimescaleTsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey " + - "AND tskv.ts >= :startTs AND tskv.ts < :endTs") - List findAllWithLimit( - @Param("entityId") UUID entityId, - @Param("entityKey") int key, - @Param("startTs") long startTs, - @Param("endTs") long endTs, Pageable pageable); + @Query(value = "SELECT * FROM ts_kv WHERE entity_id = :entityId " + + "AND key = :entityKey AND ts >= :startTs AND ts < :endTs", nativeQuery = true) + List findAllWithLimit(@Param("entityId") UUID entityId, + @Param("entityKey") int key, + @Param("startTs") long startTs, + @Param("endTs") long endTs, + Pageable pageable); @Transactional @Modifying diff --git a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java index 1d9817a5bd..66d619d0ea 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/ts/TsKvRepository.java @@ -29,8 +29,15 @@ import java.util.UUID; public interface TsKvRepository extends JpaRepository { - @Query("SELECT tskv FROM TsKvEntity tskv WHERE tskv.entityId = :entityId " + - "AND tskv.key = :entityKey AND tskv.ts >= :startTs AND tskv.ts < :endTs") + /* + * Using native query to avoid adding 'nulls first' or 'nulls last' (ignoring spring.jpa.properties.hibernate.order_by.default_null_ordering) + * to the order so that index scan is done instead of full scan. + * + * Note: even when setting custom NullHandling for the Sort.Order for non-native queries, + * it will be ignored and default_null_ordering will be used + * */ + @Query(value = "SELECT * FROM ts_kv WHERE entity_id = :entityId " + + "AND key = :entityKey AND ts >= :startTs AND ts < :endTs ", nativeQuery = true) List findAllWithLimit(@Param("entityId") UUID entityId, @Param("entityKey") int key, @Param("startTs") long startTs, diff --git a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java index 367ad942a7..3d704a0429 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDao.java @@ -643,17 +643,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD stmtCreationLock.lock(); try { if (saveStmts == null) { - saveStmts = new PreparedStatement[DataType.values().length]; + var stmts = new PreparedStatement[DataType.values().length]; for (DataType type : DataType.values()) { - saveStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + - "(" + ModelConstants.ENTITY_TYPE_COLUMN + - "," + ModelConstants.ENTITY_ID_COLUMN + - "," + ModelConstants.KEY_COLUMN + - "," + ModelConstants.PARTITION_COLUMN + - "," + ModelConstants.TS_COLUMN + - "," + getColumnName(type) + ")" + - " VALUES(?, ?, ?, ?, ?, ?)"); + stmts[type.ordinal()] = prepare(getPreparedStatementQuery(type)); } + saveStmts = stmts; } } finally { stmtCreationLock.unlock(); @@ -667,17 +661,11 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD stmtCreationLock.lock(); try { if (saveTtlStmts == null) { - saveTtlStmts = new PreparedStatement[DataType.values().length]; + var stmts = new PreparedStatement[DataType.values().length]; for (DataType type : DataType.values()) { - saveTtlStmts[type.ordinal()] = prepare(INSERT_INTO + ModelConstants.TS_KV_CF + - "(" + ModelConstants.ENTITY_TYPE_COLUMN + - "," + ModelConstants.ENTITY_ID_COLUMN + - "," + ModelConstants.KEY_COLUMN + - "," + ModelConstants.PARTITION_COLUMN + - "," + ModelConstants.TS_COLUMN + - "," + getColumnName(type) + ")" + - " VALUES(?, ?, ?, ?, ?, ?) USING TTL ?"); + stmts[type.ordinal()] = prepare(getPreparedStatementQueryWithTtl(type)); } + saveTtlStmts = stmts; } } finally { stmtCreationLock.unlock(); @@ -686,6 +674,21 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD return saveTtlStmts[dataType.ordinal()]; } + private String getPreparedStatementQuery(DataType type) { + return INSERT_INTO + ModelConstants.TS_KV_CF + + "(" + ModelConstants.ENTITY_TYPE_COLUMN + + "," + ModelConstants.ENTITY_ID_COLUMN + + "," + ModelConstants.KEY_COLUMN + + "," + ModelConstants.PARTITION_COLUMN + + "," + ModelConstants.TS_COLUMN + + "," + getColumnName(type) + ")" + + " VALUES(?, ?, ?, ?, ?, ?)"; + } + + private String getPreparedStatementQueryWithTtl(DataType type) { + return getPreparedStatementQuery(type) + " USING TTL ?"; + } + private PreparedStatement getPartitionInsertStmt() { if (partitionInsertStmt == null) { stmtCreationLock.lock(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java index b906b3278d..41a264c6db 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserDao.java @@ -42,6 +42,15 @@ public interface UserDao extends Dao, TenantEntityDao { */ User findByEmail(TenantId tenantId, String email); + /** + * Find user by tenant id and email. + * + * @param tenantId the tenant id + * @param email the email + * @return the user entity + */ + User findByTenantIdAndEmail(TenantId tenantId, String email); + /** * Find users by tenantId and page link. * @@ -69,4 +78,5 @@ public interface UserDao extends Dao, TenantEntityDao { * @return the list of user entities */ PageData findCustomerUsers(UUID tenantId, UUID customerId, PageLink pageLink); + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java index fc0de8a4fa..41d121b889 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/user/UserServiceImpl.java @@ -37,7 +37,7 @@ import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.security.UserCredentials; -import org.thingsboard.server.common.data.security.event.UserAuthDataChangedEvent; +import org.thingsboard.server.common.data.security.event.UserCredentialsInvalidationEvent; import org.thingsboard.server.dao.entity.AbstractEntityService; import org.thingsboard.server.dao.exception.IncorrectParameterException; import org.thingsboard.server.dao.service.DataValidator; @@ -87,6 +87,14 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic } } + @Override + public User findUserByTenantIdAndEmail(TenantId tenantId, String email) { + log.trace("Executing findUserByTenantIdAndEmail [{}][{}]", tenantId, email); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateString(email, "Incorrect email " + email); + return userDao.findByTenantIdAndEmail(tenantId, email); + } + @Override public User findUserById(TenantId tenantId, UserId userId) { log.trace("Executing findUserById [{}]", userId); @@ -211,7 +219,7 @@ public class UserServiceImpl extends AbstractEntityService implements UserServic userAuthSettingsDao.removeByUserId(userId); deleteEntityRelations(tenantId, userId); userDao.removeById(tenantId, userId.getId()); - eventPublisher.publishEvent(new UserAuthDataChangedEvent(userId)); + eventPublisher.publishEvent(new UserCredentialsInvalidationEvent(userId)); } @Override diff --git a/dao/src/main/resources/cassandra/schema-keyspace.cql b/dao/src/main/resources/cassandra/schema-keyspace.cql new file mode 100644 index 0000000000..8323f149ce --- /dev/null +++ b/dao/src/main/resources/cassandra/schema-keyspace.cql @@ -0,0 +1,21 @@ +-- +-- Copyright © 2016-2022 The Thingsboard Authors +-- +-- Licensed under the Apache License, Version 2.0 (the "License"); +-- you may not use this file except in compliance with the License. +-- You may obtain a copy of the License at +-- +-- http://www.apache.org/licenses/LICENSE-2.0 +-- +-- Unless required by applicable law or agreed to in writing, software +-- distributed under the License is distributed on an "AS IS" BASIS, +-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +-- See the License for the specific language governing permissions and +-- limitations under the License. +-- + +CREATE KEYSPACE IF NOT EXISTS thingsboard +WITH replication = { + 'class' : 'SimpleStrategy', + 'replication_factor' : 1 +}; diff --git a/dao/src/main/resources/cassandra/schema-ts-latest.cql b/dao/src/main/resources/cassandra/schema-ts-latest.cql index 0de4e88158..10b3edb84e 100644 --- a/dao/src/main/resources/cassandra/schema-ts-latest.cql +++ b/dao/src/main/resources/cassandra/schema-ts-latest.cql @@ -14,12 +14,6 @@ -- limitations under the License. -- -CREATE KEYSPACE IF NOT EXISTS thingsboard -WITH replication = { - 'class' : 'SimpleStrategy', - 'replication_factor' : 1 -}; - CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_latest_cf ( entity_type text, // (DEVICE, CUSTOMER, TENANT) entity_id timeuuid, diff --git a/dao/src/main/resources/cassandra/schema-ts.cql b/dao/src/main/resources/cassandra/schema-ts.cql index 9cb609add2..6ab099f841 100644 --- a/dao/src/main/resources/cassandra/schema-ts.cql +++ b/dao/src/main/resources/cassandra/schema-ts.cql @@ -14,12 +14,6 @@ -- limitations under the License. -- -CREATE KEYSPACE IF NOT EXISTS thingsboard -WITH replication = { - 'class' : 'SimpleStrategy', - 'replication_factor' : 1 -}; - CREATE TABLE IF NOT EXISTS thingsboard.ts_kv_cf ( entity_type text, // (DEVICE, CUSTOMER, TENANT) entity_id timeuuid, diff --git a/dao/src/main/resources/sql/schema-entities-idx.sql b/dao/src/main/resources/sql/schema-entities-idx.sql index 34862e5af3..e7586b17a8 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -50,6 +50,8 @@ CREATE INDEX IF NOT EXISTS idx_attribute_kv_by_key_and_last_update_ts ON attribu CREATE INDEX IF NOT EXISTS idx_audit_log_tenant_id_and_created_time ON audit_log(tenant_id, created_time DESC); +CREATE INDEX IF NOT EXISTS idx_edge_event_tenant_id_and_created_time ON edge_event(tenant_id, created_time DESC); + CREATE INDEX IF NOT EXISTS idx_rpc_tenant_id_device_id ON rpc(tenant_id, device_id); CREATE INDEX IF NOT EXISTS idx_device_external_id ON device(tenant_id, external_id); diff --git a/dao/src/main/resources/sql/schema-entities.sql b/dao/src/main/resources/sql/schema-entities.sql index 51df863ae5..73039274a6 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -719,7 +719,7 @@ CREATE TABLE IF NOT EXISTS edge ( ); CREATE TABLE IF NOT EXISTS edge_event ( - id uuid NOT NULL CONSTRAINT edge_event_pkey PRIMARY KEY, + id uuid NOT NULL, created_time bigint NOT NULL, edge_id uuid, edge_event_type varchar(255), @@ -729,7 +729,7 @@ CREATE TABLE IF NOT EXISTS edge_event ( body varchar(10000000), tenant_id uuid, ts bigint NOT NULL -); +) PARTITION BY RANGE(created_time); CREATE TABLE IF NOT EXISTS rpc ( id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY, diff --git a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java index 838be51349..e601218845 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java +++ b/dao/src/test/java/org/thingsboard/server/dao/NoSqlDaoServiceTestSuite.java @@ -33,6 +33,7 @@ public class NoSqlDaoServiceTestSuite { public static CustomCassandraCQLUnit cassandraUnit = new CustomCassandraCQLUnit( Arrays.asList( + new ClassPathCQLDataSet("cassandra/schema-keyspace.cql", false, false), new ClassPathCQLDataSet("cassandra/schema-ts.cql", false, false), new ClassPathCQLDataSet("cassandra/schema-ts-latest.cql", false, false) ), diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java index 75ac95d470..e294a3f35b 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseRelationServiceTest.java @@ -35,6 +35,7 @@ import org.thingsboard.server.dao.exception.DataValidationException; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; @@ -80,7 +81,7 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertTrue(relationService.deleteRelationAsync(SYSTEM_TENANT_ID, relationA).get()); - Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); + Assert.assertFalse(relationService.checkRelation(SYSTEM_TENANT_ID, parentId, childId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); Assert.assertTrue(relationService.checkRelation(SYSTEM_TENANT_ID, childId, subChildId, EntityRelation.CONTAINS_TYPE, RelationTypeGroup.COMMON)); @@ -171,8 +172,8 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertEquals(0, relations.size()); } - private Boolean saveRelation(EntityRelation relationA1) throws ExecutionException, InterruptedException { - return relationService.saveRelationAsync(SYSTEM_TENANT_ID, relationA1).get(); + private Boolean saveRelation(EntityRelation relationA1) { + return relationService.saveRelation(SYSTEM_TENANT_ID, relationA1); } @Test @@ -194,9 +195,6 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { saveRelation(relationB1); saveRelation(relationB2); - // Data propagation to views is async - Thread.sleep(3000); - List relations = relationService.findByTo(SYSTEM_TENANT_ID, childA, RelationTypeGroup.COMMON); Assert.assertEquals(2, relations.size()); for (EntityRelation relation : relations) { @@ -288,6 +286,53 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { Assert.assertTrue(relations.contains(relationBC)); } + @Test + public void testRecursiveRelationDepth() throws ExecutionException, InterruptedException { + int maxLevel = 1000; + AssetId root = new AssetId(Uuids.timeBased()); + AssetId left = new AssetId(Uuids.timeBased()); + AssetId right = new AssetId(Uuids.timeBased()); + + List expected = new ArrayList<>(); + + EntityRelation relationAB = new EntityRelation(root, left, EntityRelation.CONTAINS_TYPE); + EntityRelation relationBC = new EntityRelation(root, right, EntityRelation.CONTAINS_TYPE); + saveRelation(relationAB); + expected.add(relationAB); + + saveRelation(relationBC); + expected.add(relationBC); + + for (int i = 0; i < maxLevel; i++) { + var newLeft = new AssetId(Uuids.timeBased()); + var newRight = new AssetId(Uuids.timeBased()); + EntityRelation relationLeft = new EntityRelation(left, newLeft, EntityRelation.CONTAINS_TYPE); + EntityRelation relationRight = new EntityRelation(right, newRight, EntityRelation.CONTAINS_TYPE); + saveRelation(relationLeft); + expected.add(relationLeft); + saveRelation(relationRight); + expected.add(relationRight); + left = newLeft; + right = newRight; + } + + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(root, EntitySearchDirection.FROM, -1, false)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(expected.size(), relations.size()); + for(EntityRelation r : expected){ + Assert.assertTrue(relations.contains(r)); + } + + //Test from cache + relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(expected.size(), relations.size()); + for(EntityRelation r : expected){ + Assert.assertTrue(relations.contains(r)); + } + } @Test(expected = DataValidationException.class) public void testSaveRelationWithEmptyFrom() throws ExecutionException, InterruptedException { @@ -312,4 +357,290 @@ public abstract class BaseRelationServiceTest extends AbstractServiceTest { relation.setTo(new AssetId(Uuids.timeBased())); Assert.assertTrue(saveRelation(relation)); } + + @Test + public void testFindByQueryFetchLastOnlyTreeLike() throws Exception { + // A -> B + // A -> C + // C -> D + // C -> E + + AssetId assetA = new AssetId(Uuids.timeBased()); + AssetId assetB = new AssetId(Uuids.timeBased()); + AssetId assetC = new AssetId(Uuids.timeBased()); + AssetId assetD = new AssetId(Uuids.timeBased()); + AssetId assetE = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); + EntityRelation relationB = new EntityRelation(assetA, assetC, EntityRelation.CONTAINS_TYPE); + EntityRelation relationC = new EntityRelation(assetC, assetD, EntityRelation.CONTAINS_TYPE); + EntityRelation relationD = new EntityRelation(assetC, assetE, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + saveRelation(relationB); + saveRelation(relationC); + saveRelation(relationD); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, true)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(3, relations.size()); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertFalse(relations.contains(relationB)); + + //Test from cache + relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertFalse(relations.contains(relationB)); + } + + @Test + public void testFindByQueryFetchLastOnlySingleLinked() throws Exception { + // A -> B -> C -> D + + AssetId assetA = new AssetId(Uuids.timeBased()); + AssetId assetB = new AssetId(Uuids.timeBased()); + AssetId assetC = new AssetId(Uuids.timeBased()); + AssetId assetD = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); + EntityRelation relationB = new EntityRelation(assetB, assetC, EntityRelation.CONTAINS_TYPE); + EntityRelation relationC = new EntityRelation(assetC, assetD, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + saveRelation(relationB); + saveRelation(relationC); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, true)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(1, relations.size()); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertFalse(relations.contains(relationA)); + Assert.assertFalse(relations.contains(relationB)); + + //Test from cache + relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertFalse(relations.contains(relationA)); + Assert.assertFalse(relations.contains(relationB)); + } + + @Test + public void testFindByQueryFetchLastOnlyTreeLikeWithMaxLvl() throws Exception { + // A -> B A + // A -> C B + // C -> D C + // C -> E D + // D -> F E + // D -> G F + + AssetId assetA = new AssetId(Uuids.timeBased()); + AssetId assetB = new AssetId(Uuids.timeBased()); + AssetId assetC = new AssetId(Uuids.timeBased()); + AssetId assetD = new AssetId(Uuids.timeBased()); + AssetId assetE = new AssetId(Uuids.timeBased()); + AssetId assetF = new AssetId(Uuids.timeBased()); + AssetId assetG = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); + EntityRelation relationB = new EntityRelation(assetA, assetC, EntityRelation.CONTAINS_TYPE); + EntityRelation relationC = new EntityRelation(assetC, assetD, EntityRelation.CONTAINS_TYPE); + EntityRelation relationD = new EntityRelation(assetC, assetE, EntityRelation.CONTAINS_TYPE); + EntityRelation relationE = new EntityRelation(assetD, assetF, EntityRelation.CONTAINS_TYPE); + EntityRelation relationF = new EntityRelation(assetD, assetG, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + saveRelation(relationB); + saveRelation(relationC); + saveRelation(relationD); + saveRelation(relationE); + saveRelation(relationF); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, 2, true)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(3, relations.size()); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertFalse(relations.contains(relationB)); + Assert.assertFalse(relations.contains(relationE)); + Assert.assertFalse(relations.contains(relationF)); + + //Test from cache + relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertFalse(relations.contains(relationB)); + Assert.assertFalse(relations.contains(relationE)); + Assert.assertFalse(relations.contains(relationF)); + } + + @Test + public void testFindByQueryTreeLikeWithMaxLvl() throws Exception { + // A -> B A + // A -> C B + // C -> D C + // C -> E D + // D -> F E + // D -> G F + + AssetId assetA = new AssetId(Uuids.timeBased()); + AssetId assetB = new AssetId(Uuids.timeBased()); + AssetId assetC = new AssetId(Uuids.timeBased()); + AssetId assetD = new AssetId(Uuids.timeBased()); + AssetId assetE = new AssetId(Uuids.timeBased()); + AssetId assetF = new AssetId(Uuids.timeBased()); + AssetId assetG = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); + EntityRelation relationB = new EntityRelation(assetA, assetC, EntityRelation.CONTAINS_TYPE); + EntityRelation relationC = new EntityRelation(assetC, assetD, EntityRelation.CONTAINS_TYPE); + EntityRelation relationD = new EntityRelation(assetC, assetE, EntityRelation.CONTAINS_TYPE); + EntityRelation relationE = new EntityRelation(assetD, assetF, EntityRelation.CONTAINS_TYPE); + EntityRelation relationF = new EntityRelation(assetD, assetG, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + saveRelation(relationB); + saveRelation(relationC); + saveRelation(relationD); + saveRelation(relationE); + saveRelation(relationF); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, 2, false)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(4, relations.size()); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationB)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertFalse(relations.contains(relationE)); + Assert.assertFalse(relations.contains(relationF)); + + //Test from cache + relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationB)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertFalse(relations.contains(relationE)); + Assert.assertFalse(relations.contains(relationF)); + } + + @Test + public void testFindByQueryTreeLikeWithUnlimLvl() throws Exception { + // A -> B A + // A -> C B + // C -> D C + // C -> E D + // D -> F E + // D -> G F + + AssetId assetA = new AssetId(Uuids.timeBased()); + AssetId assetB = new AssetId(Uuids.timeBased()); + AssetId assetC = new AssetId(Uuids.timeBased()); + AssetId assetD = new AssetId(Uuids.timeBased()); + AssetId assetE = new AssetId(Uuids.timeBased()); + AssetId assetF = new AssetId(Uuids.timeBased()); + AssetId assetG = new AssetId(Uuids.timeBased()); + + EntityRelation relationA = new EntityRelation(assetA, assetB, EntityRelation.CONTAINS_TYPE); + EntityRelation relationB = new EntityRelation(assetA, assetC, EntityRelation.CONTAINS_TYPE); + EntityRelation relationC = new EntityRelation(assetC, assetD, EntityRelation.CONTAINS_TYPE); + EntityRelation relationD = new EntityRelation(assetC, assetE, EntityRelation.CONTAINS_TYPE); + EntityRelation relationE = new EntityRelation(assetD, assetF, EntityRelation.CONTAINS_TYPE); + EntityRelation relationF = new EntityRelation(assetD, assetG, EntityRelation.CONTAINS_TYPE); + + saveRelation(relationA); + saveRelation(relationB); + saveRelation(relationC); + saveRelation(relationD); + saveRelation(relationE); + saveRelation(relationF); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(assetA, EntitySearchDirection.FROM, -1, false)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(6, relations.size()); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationB)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertTrue(relations.contains(relationE)); + Assert.assertTrue(relations.contains(relationF)); + + //Test from cache + relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertTrue(relations.contains(relationA)); + Assert.assertTrue(relations.contains(relationB)); + Assert.assertTrue(relations.contains(relationC)); + Assert.assertTrue(relations.contains(relationD)); + Assert.assertTrue(relations.contains(relationE)); + Assert.assertTrue(relations.contains(relationF)); + } + + @Test + public void testFindByQueryLargeHierarchyFetchAllWithUnlimLvl() throws Exception { + AssetId rootAsset = new AssetId(Uuids.timeBased()); + final int hierarchyLvl = 10; + List expectedRelations = new LinkedList<>(); + + createAssetRelationsRecursively(rootAsset, hierarchyLvl, expectedRelations, false); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(rootAsset, EntitySearchDirection.FROM, -1, false)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(expectedRelations.size(), relations.size()); + Assert.assertTrue(relations.containsAll(expectedRelations)); + } + + @Test + public void testFindByQueryLargeHierarchyFetchLastOnlyWithUnlimLvl() throws Exception { + AssetId rootAsset = new AssetId(Uuids.timeBased()); + final int hierarchyLvl = 10; + List expectedRelations = new LinkedList<>(); + + createAssetRelationsRecursively(rootAsset, hierarchyLvl, expectedRelations, true); + + EntityRelationsQuery query = new EntityRelationsQuery(); + query.setParameters(new RelationsSearchParameters(rootAsset, EntitySearchDirection.FROM, -1, true)); + query.setFilters(Collections.singletonList(new RelationEntityTypeFilter(EntityRelation.CONTAINS_TYPE, Collections.singletonList(EntityType.ASSET)))); + List relations = relationService.findByQuery(SYSTEM_TENANT_ID, query).get(); + Assert.assertEquals(expectedRelations.size(), relations.size()); + Assert.assertTrue(relations.containsAll(expectedRelations)); + } + + private void createAssetRelationsRecursively(AssetId rootAsset, int lvl, List entityRelations, boolean lastLvlOnly) throws Exception { + if (lvl == 0) return; + + AssetId firstAsset = new AssetId(Uuids.timeBased()); + AssetId secondAsset = new AssetId(Uuids.timeBased()); + + EntityRelation firstRelation = new EntityRelation(rootAsset, firstAsset, EntityRelation.CONTAINS_TYPE); + EntityRelation secondRelation = new EntityRelation(rootAsset, secondAsset, EntityRelation.CONTAINS_TYPE); + + saveRelation(firstRelation); + saveRelation(secondRelation); + + if (!lastLvlOnly || lvl == 1) { + entityRelations.add(firstRelation); + entityRelations.add(secondRelation); + } + + createAssetRelationsRecursively(firstAsset, lvl - 1, entityRelations, lastLvlOnly); + createAssetRelationsRecursively(secondAsset, lvl - 1, entityRelations, lastLvlOnly); + } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java index 941c2d01ac..32ac8a0470 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDaoTest.java @@ -63,7 +63,7 @@ public class JpaAlarmDaoTest extends AbstractJpaDaoTest { int alarmCountAfterSave = alarmDao.find(TenantId.fromUUID(tenantId)).size(); assertEquals(3, alarmCountAfterSave - alarmCountBeforeSave); ListenableFuture future = alarmDao - .findLatestByOriginatorAndType(TenantId.fromUUID(tenantId), new DeviceId(originator1Id), "TEST_ALARM"); + .findLatestByOriginatorAndTypeAsync(TenantId.fromUUID(tenantId), new DeviceId(originator1Id), "TEST_ALARM"); Alarm alarm = future.get(30, TimeUnit.SECONDS); assertNotNull(alarm); assertEquals(alarm2Id, alarm.getId().getId()); diff --git a/msa/js-executor/queue/kafkaTemplate.ts b/msa/js-executor/queue/kafkaTemplate.ts index 7c34d99889..6cb8281837 100644 --- a/msa/js-executor/queue/kafkaTemplate.ts +++ b/msa/js-executor/queue/kafkaTemplate.ts @@ -30,6 +30,8 @@ import { TopicMessages } from 'kafkajs'; +import process, { kill, exit } from 'process'; + export class KafkaTemplate implements IQueue { private logger = _logger(`kafkaTemplate`); @@ -122,6 +124,7 @@ export class KafkaTemplate implements IQueue { this.logger.error(`Got consumer CRASH event, should restart: ${e.payload.restart}`); if (!e.payload.restart) { this.logger.error('Going to exit due to not retryable error!'); + kill(process.pid, 'SIGTERM'); //sending signal to myself process to trigger the handler await this.destroy(); } }); @@ -253,6 +256,7 @@ export class KafkaTemplate implements IQueue { } } this.logger.info('Kafka resources stopped.'); + exit(0); //same as in version before } private async disconnectProducer(): Promise { diff --git a/msa/js-executor/server.ts b/msa/js-executor/server.ts index 035bf77183..a64dcc2c98 100644 --- a/msa/js-executor/server.ts +++ b/msa/js-executor/server.ts @@ -81,15 +81,19 @@ process.on('exit', (code: number) => { async function exit(status: number) { logger.info('Exiting with status: %d ...', status); - if (httpServer) { - const _httpServer = httpServer; - httpServer = null; - await _httpServer.stop(); - } - if (queues) { - const _queues = queues; - queues = null; - await _queues.destroy(); + try { + if (httpServer) { + const _httpServer = httpServer; + httpServer = null; + await _httpServer.stop(); + } + if (queues) { + const _queues = queues; + queues = null; + await _queues.destroy(); + } + } catch (e) { + logger.error('Error on exit'); } process.exit(status); } diff --git a/pom.xml b/pom.xml index 38718586f6..a582dfb56c 100755 --- a/pom.xml +++ b/pom.xml @@ -51,10 +51,9 @@ 2.17.1 1.2.10 0.10 - 4.10.0 + 4.15.0 4.0.5 - 3.11.10 - 3.11.0 + 3.11.14 30.0-jre 2.6.1 3.4 @@ -65,7 +64,8 @@ 4.5.13 4.4.14 2.8.1 - 2.13.2 + 2.13.4 + 2.13.4.2 1.3.4 2.2.6 3.0.0 @@ -75,8 +75,9 @@ 1.6.2 4.2.0 3.5.5 - 3.17.2 + 3.21.9 1.42.1 + 2.4.23TB 1.18.18 1.2.4 4.1.75.Final @@ -91,7 +92,7 @@ 1.18.2 1.69 2.0.1 - 42.2.20 + 42.5.0 org/thingsboard/server/gen/**/*, org/thingsboard/server/extensions/core/plugin/telemetry/gen/**/* @@ -114,7 +115,7 @@ 1.9.4 3.2.2 1.9.0 - 1.0.3TB + 1.0.4TB 3.4.0 8.17.0 6.0.20.Final @@ -973,6 +974,16 @@ coap-server ${project.version} + + org.thingsboard.common.script + script-api + ${project.version} + + + org.thingsboard.common.script + remote-js-client + ${project.version} + org.thingsboard tools @@ -1322,11 +1333,6 @@ java-driver-query-builder ${cassandra.version} - - com.datastax.cassandra - cassandra-driver-core - ${cassandra-driver-core.version} - io.dropwizard.metrics metrics-jmx @@ -1375,7 +1381,7 @@ com.fasterxml.jackson.core jackson-databind - ${jackson.version} + ${jackson-databind.version} com.fasterxml.jackson.core @@ -1567,6 +1573,11 @@ grpc-api ${grpc.version} + + org.thingsboard + mvel2 + ${mvel.version} + org.springframework spring-test @@ -1610,6 +1621,11 @@ cassandra-all ${cassandra-all.version} + + org.apache.cassandra + cassandra-thrift + ${cassandra-all.version} + org.junit.vintage junit-vintage-engine diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java index 69c705c827..21bab255f2 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java @@ -51,6 +51,8 @@ public interface RuleEngineAlarmService { ListenableFuture findAlarmByIdAsync(TenantId tenantId, AlarmId alarmId); + Alarm findAlarmById(TenantId tenantId, AlarmId alarmId); + ListenableFuture findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); ListenableFuture findAlarmInfoByIdAsync(TenantId tenantId, AlarmId alarmId); diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineApiUsageStateService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineApiUsageStateService.java new file mode 100644 index 0000000000..03734ffcad --- /dev/null +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineApiUsageStateService.java @@ -0,0 +1,26 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.api; + +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.id.ApiUsageStateId; +import org.thingsboard.server.common.data.id.TenantId; + +public interface RuleEngineApiUsageStateService { + + ApiUsageState findApiUsageStateById(TenantId tenantId, ApiUsageStateId id); + +} diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineRpcService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineRpcService.java index abb5ace0f8..779e958793 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineRpcService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineRpcService.java @@ -15,7 +15,9 @@ */ package org.thingsboard.rule.engine.api; -import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.RpcId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.rpc.Rpc; import java.util.UUID; import java.util.function.Consumer; @@ -29,4 +31,5 @@ public interface RuleEngineRpcService { void sendRpcRequestToDevice(RuleEngineDeviceRpcRequest request, Consumer consumer); + Rpc findRpcById(TenantId tenantId, RpcId id); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java index 4ca5eade5c..9851eead80 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/TbContext.java @@ -39,6 +39,7 @@ import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.RuleNode; import org.thingsboard.server.common.data.rule.RuleNodeState; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.asset.AssetService; @@ -61,6 +62,8 @@ import org.thingsboard.server.dao.rule.RuleChainService; import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.timeseries.TimeseriesService; import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; import java.util.List; import java.util.Set; @@ -196,6 +199,8 @@ public interface TbContext { * */ + void checkTenantEntity(EntityId entityId); + boolean isLocalEntity(EntityId entityId); RuleNodeId getSelfId(); @@ -266,8 +271,17 @@ public interface TbContext { SmsSenderFactory getSmsSenderFactory(); + /** + * Creates JS Script Engine + * @deprecated + *

Use {@link #createScriptEngine} instead. + * + */ + @Deprecated ScriptEngine createJsScriptEngine(String script, String... argNames); + ScriptEngine createScriptEngine(ScriptLanguage scriptLang, String script, String... argNames); + void logJsEvalRequest(); void logJsEvalResponse(); @@ -303,4 +317,10 @@ public interface TbContext { void removeListeners(); TenantProfile getTenantProfile(); + + WidgetsBundleService getWidgetBundleService(); + + WidgetTypeService getWidgetTypeService(); + + RuleEngineApiUsageStateService getRuleEngineApiUsageStateService(); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java index 00c4676026..27080da2e8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbAbstractAlarmNode.java @@ -26,6 +26,7 @@ import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.common.util.JacksonUtil; @@ -41,12 +42,13 @@ public abstract class TbAbstractAlarmNode { @@ -25,7 +26,9 @@ public class TbClearAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigurat @Override public TbClearAlarmNodeConfiguration defaultConfiguration() { TbClearAlarmNodeConfiguration configuration = new TbClearAlarmNodeConfiguration(); + configuration.setScriptLang(ScriptLanguage.MVEL); configuration.setAlarmDetailsBuildJs(ALARM_DETAILS_BUILD_JS_TEMPLATE); + configuration.setAlarmDetailsBuildMvel(ALARM_DETAILS_BUILD_MVEL_TEMPLATE); configuration.setAlarmType("General Alarm"); return configuration; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java index 398e99d29c..e6352b65a5 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.action; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.server.common.data.alarm.AlarmSeverity; +import org.thingsboard.server.common.data.script.ScriptLanguage; import java.util.Collections; import java.util.List; @@ -38,7 +39,9 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura @Override public TbCreateAlarmNodeConfiguration defaultConfiguration() { TbCreateAlarmNodeConfiguration configuration = new TbCreateAlarmNodeConfiguration(); + configuration.setScriptLang(ScriptLanguage.MVEL); configuration.setAlarmDetailsBuildJs(ALARM_DETAILS_BUILD_JS_TEMPLATE); + configuration.setAlarmDetailsBuildMvel(ALARM_DETAILS_BUILD_MVEL_TEMPLATE); configuration.setAlarmType("General Alarm"); configuration.setSeverity(AlarmSeverity.CRITICAL.name()); configuration.setPropagate(false); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java index d5213da4f2..b3d5a4b705 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateRelationNode.java @@ -32,6 +32,7 @@ import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.EntityViewId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.relation.RelationTypeGroup; @@ -143,6 +144,8 @@ public class TbCreateRelationNode extends TbAbstractRelationActionNode processUser(TbContext ctx, EntityContainer entityContainer, SearchDirectionIds sdId, String relationType) { + return Futures.transformAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), new UserId(entityContainer.getEntityId().getId())), user -> { + if (user != null) { + return processSave(ctx, sdId, relationType); + } else { + return Futures.immediateFuture(true); + } + }, ctx.getDbCallbackExecutor()); + } + private ListenableFuture processSave(TbContext ctx, SearchDirectionIds sdId, String relationType) { return ctx.getRelationService().saveRelationAsync(ctx.getTenantId(), new EntityRelation(sdId.getFromId(), sdId.getToId(), relationType, RelationTypeGroup.COMMON)); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java index 431d63a674..8ae88a57bf 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNode.java @@ -29,6 +29,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; @Slf4j @@ -47,14 +48,15 @@ import org.thingsboard.server.common.msg.TbMsg; public class TbLogNode implements TbNode { private TbLogNodeConfiguration config; - private ScriptEngine jsEngine; + private ScriptEngine scriptEngine; private boolean standard; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbLogNodeConfiguration.class); this.standard = new TbLogNodeConfiguration().defaultConfiguration().getJsScript().equals(config.getJsScript()); - this.jsEngine = this.standard ? null : ctx.createJsScriptEngine(config.getJsScript()); + this.scriptEngine = this.standard ? null : ctx.createScriptEngine(config.getScriptLang(), + ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript()); } @Override @@ -65,7 +67,7 @@ public class TbLogNode implements TbNode { } ctx.logJsEvalRequest(); - Futures.addCallback(jsEngine.executeToStringAsync(msg), new FutureCallback() { + Futures.addCallback(scriptEngine.executeToStringAsync(msg), new FutureCallback() { @Override public void onSuccess(@Nullable String result) { ctx.logJsEvalResponse(); @@ -94,8 +96,8 @@ public class TbLogNode implements TbNode { @Override public void destroy() { - if (jsEngine != null) { - jsEngine.destroy(); + if (scriptEngine != null) { + scriptEngine.destroy(); } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java index 453ade72a7..85eb3b8e04 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbLogNodeConfiguration.java @@ -17,16 +17,21 @@ package org.thingsboard.rule.engine.action; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.script.ScriptLanguage; @Data public class TbLogNodeConfiguration implements NodeConfiguration { + private ScriptLanguage scriptLang; private String jsScript; + private String mvelScript; @Override public TbLogNodeConfiguration defaultConfiguration() { TbLogNodeConfiguration configuration = new TbLogNodeConfiguration(); + configuration.setScriptLang(ScriptLanguage.MVEL); configuration.setJsScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"); + configuration.setMvelScript("return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);"); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java index 32ec34ef39..14825af121 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNode.java @@ -31,6 +31,7 @@ import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityIdFactory; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.common.msg.queue.PartitionChangeMsg; @@ -60,7 +61,7 @@ public class TbMsgGeneratorNode implements TbNode { private static final String TB_MSG_GENERATOR_NODE_MSG = "TbMsgGeneratorNodeMsg"; private TbMsgGeneratorNodeConfiguration config; - private ScriptEngine jsEngine; + private ScriptEngine scriptEngine; private long delay; private long lastScheduledTs; private int currentMsgCount; @@ -77,6 +78,7 @@ public class TbMsgGeneratorNode implements TbNode { this.currentMsgCount = 0; if (!StringUtils.isEmpty(config.getOriginatorId())) { originatorId = EntityIdFactory.getByTypeAndUuid(config.getOriginatorType(), config.getOriginatorId()); + ctx.checkTenantEntity(originatorId); } else { originatorId = ctx.getSelfId(); } @@ -93,7 +95,8 @@ public class TbMsgGeneratorNode implements TbNode { log.trace("updateGeneratorState, config {}", config); if (ctx.isLocalEntity(originatorId)) { if (initialized.compareAndSet(false, true)) { - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType"); + this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(), + ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript(), "prevMsg", "prevMetadata", "prevMsgType"); scheduleTickMsg(ctx); } } else if (initialized.compareAndSet(true, false)) { @@ -116,7 +119,7 @@ public class TbMsgGeneratorNode implements TbNode { } }, t -> { - log.warn("onMsg onFailure callback, took {}ms, config {}, msg {}, exception {}", sw.stopAndGetTotalTimeMillis(), config, msg, t); + log.trace("onMsg onFailure callback, took {}ms, config {}, msg {}", sw.stopAndGetTotalTimeMillis(), config, msg, t); if (initialized.get() && (config.getMsgCount() == TbMsgGeneratorNodeConfiguration.UNLIMITED_MSG_COUNT || currentMsgCount < config.getMsgCount())) { ctx.tellFailure(msg, t); scheduleTickMsg(ctx); @@ -146,7 +149,7 @@ public class TbMsgGeneratorNode implements TbNode { } if (initialized.get()) { ctx.logJsEvalRequest(); - return Futures.transformAsync(jsEngine.executeGenerateAsync(prevMsg), generated -> { + return Futures.transformAsync(scriptEngine.executeGenerateAsync(prevMsg), generated -> { log.trace("generate process response, generated {}, config {}", generated, config); ctx.logJsEvalResponse(); prevMsg = ctx.newMsg(null, generated.getType(), originatorId, msg.getCustomerId(), generated.getMetaData(), generated.getData()); @@ -161,9 +164,9 @@ public class TbMsgGeneratorNode implements TbNode { public void destroy() { log.trace("destroy, config {}", config); prevMsg = null; - if (jsEngine != null) { - jsEngine.destroy(); - jsEngine = null; + if (scriptEngine != null) { + scriptEngine.destroy(); + scriptEngine = null; } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java index 540a3cc01d..9b037c9478 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/debug/TbMsgGeneratorNodeConfiguration.java @@ -18,27 +18,33 @@ package org.thingsboard.rule.engine.debug; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.script.ScriptLanguage; @Data public class TbMsgGeneratorNodeConfiguration implements NodeConfiguration { public static final int UNLIMITED_MSG_COUNT = 0; + public static final String DEFAULT_SCRIPT = "var msg = { temp: 42, humidity: 77 };\n" + + "var metadata = { data: 40 };\n" + + "var msgType = \"POST_TELEMETRY_REQUEST\";\n\n" + + "return { msg: msg, metadata: metadata, msgType: msgType };"; private int msgCount; private int periodInSeconds; private String originatorId; private EntityType originatorType; + private ScriptLanguage scriptLang; private String jsScript; + private String mvelScript; @Override public TbMsgGeneratorNodeConfiguration defaultConfiguration() { TbMsgGeneratorNodeConfiguration configuration = new TbMsgGeneratorNodeConfiguration(); configuration.setMsgCount(UNLIMITED_MSG_COUNT); configuration.setPeriodInSeconds(1); - configuration.setJsScript("var msg = { temp: 42, humidity: 77 };\n" + - "var metadata = { data: 40 };\n" + - "var msgType = \"POST_TELEMETRY_REQUEST\";\n\n" + - "return { msg: msg, metadata: metadata, msgType: msgType };"); + configuration.setScriptLang(ScriptLanguage.MVEL); + configuration.setJsScript(DEFAULT_SCRIPT); + configuration.setMvelScript(DEFAULT_SCRIPT); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java index 28dc64c068..f120276df3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/edge/AbstractTbMsgPushNode.java @@ -142,8 +142,11 @@ public abstract class AbstractTbMsgPushNodemetadata property. For example metadata.customerName === 'John';
" + "Message type can be accessed via msgType property.", uiResources = {"static/rulenode/rulenode-core-config.js"}, - configDirective = "tbFilterNodeScriptConfig") - + configDirective = "tbFilterNodeScriptConfig" +) public class TbJsFilterNode implements TbNode { private TbJsFilterNodeConfiguration config; - private ScriptEngine jsEngine; + private ScriptEngine scriptEngine; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbJsFilterNodeConfiguration.class); - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript()); + scriptEngine = ctx.createScriptEngine(config.getScriptLang(), + ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript()); } @Override public void onMsg(TbContext ctx, TbMsg msg) { ctx.logJsEvalRequest(); - withCallback(jsEngine.executeFilterAsync(msg), + withCallback(scriptEngine.executeFilterAsync(msg), filterResult -> { ctx.logJsEvalResponse(); ctx.tellNext(msg, filterResult ? "True" : "False"); @@ -69,8 +72,8 @@ public class TbJsFilterNode implements TbNode { @Override public void destroy() { - if (jsEngine != null) { - jsEngine.destroy(); + if (scriptEngine != null) { + scriptEngine.destroy(); } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java index f0db3f55e3..c284a79b53 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeConfiguration.java @@ -17,16 +17,21 @@ package org.thingsboard.rule.engine.filter; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.script.ScriptLanguage; @Data public class TbJsFilterNodeConfiguration implements NodeConfiguration { + private ScriptLanguage scriptLang; private String jsScript; + private String mvelScript; @Override public TbJsFilterNodeConfiguration defaultConfiguration() { TbJsFilterNodeConfiguration configuration = new TbJsFilterNodeConfiguration(); + configuration.setScriptLang(ScriptLanguage.MVEL); configuration.setJsScript("return msg.temperature > 20;"); + configuration.setMvelScript("return msg.temperature > 20;"); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java index d35a383a0d..9fddb5ca8f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNode.java @@ -20,7 +20,6 @@ import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.MoreExecutors; import lombok.extern.slf4j.Slf4j; import org.checkerframework.checker.nullness.qual.Nullable; -import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.ScriptEngine; import org.thingsboard.rule.engine.api.TbContext; @@ -29,6 +28,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import java.util.Set; @@ -50,18 +50,19 @@ import java.util.Set; public class TbJsSwitchNode implements TbNode { private TbJsSwitchNodeConfiguration config; - private ScriptEngine jsEngine; + private ScriptEngine scriptEngine; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class); - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript()); + this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(), + ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript()); } @Override public void onMsg(TbContext ctx, TbMsg msg) { ctx.logJsEvalRequest(); - Futures.addCallback(jsEngine.executeSwitchAsync(msg), new FutureCallback>() { + Futures.addCallback(scriptEngine.executeSwitchAsync(msg), new FutureCallback<>() { @Override public void onSuccess(@Nullable Set result) { ctx.logJsEvalResponse(); @@ -82,8 +83,8 @@ public class TbJsSwitchNode implements TbNode { @Override public void destroy() { - if (jsEngine != null) { - jsEngine.destroy(); + if (scriptEngine != null) { + scriptEngine.destroy(); } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java index ab5d978b1c..d0640af982 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeConfiguration.java @@ -18,24 +18,39 @@ package org.thingsboard.rule.engine.filter; import com.google.common.collect.Sets; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.script.ScriptLanguage; import java.util.Set; @Data public class TbJsSwitchNodeConfiguration implements NodeConfiguration { + private static final String DEFAULT_JS_SCRIPT = "function nextRelation(metadata, msg) {\n" + + " return ['one','nine'];\n" + + "}\n" + + "if(msgType === 'POST_TELEMETRY_REQUEST') {\n" + + " return ['two'];\n" + + "}\n" + + "return nextRelation(metadata, msg);"; + + private static final String DEFAULT_MVEL_SCRIPT = "function nextRelation(metadata, msg) {\n" + + " return ['one','nine'];\n" + + "}\n" + + "if(msgType == 'POST_TELEMETRY_REQUEST') {\n" + + " return ['two'];\n" + + "}\n" + + "return nextRelation(metadata, msg);"; + + private ScriptLanguage scriptLang; private String jsScript; + private String mvelScript; @Override public TbJsSwitchNodeConfiguration defaultConfiguration() { TbJsSwitchNodeConfiguration configuration = new TbJsSwitchNodeConfiguration(); - configuration.setJsScript("function nextRelation(metadata, msg) {\n" + - " return ['one','nine'];\n" + - "}\n" + - "if(msgType === 'POST_TELEMETRY_REQUEST') {\n" + - " return ['two'];\n" + - "}\n" + - "return nextRelation(metadata, msg);"); + configuration.setScriptLang(ScriptLanguage.MVEL); + configuration.setJsScript(DEFAULT_JS_SCRIPT); + configuration.setMvelScript(DEFAULT_MVEL_SCRIPT); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java index b9c7d53b1d..fa7efd2dac 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNode.java @@ -17,14 +17,11 @@ package org.thingsboard.rule.engine.kafka; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.BooleanUtils; -import org.thingsboard.server.common.data.StringUtils; import org.apache.kafka.clients.producer.KafkaProducer; import org.apache.kafka.clients.producer.Producer; import org.apache.kafka.clients.producer.ProducerConfig; import org.apache.kafka.clients.producer.ProducerRecord; import org.apache.kafka.clients.producer.RecordMetadata; -import org.apache.kafka.common.errors.TimeoutException; -import org.apache.kafka.common.serialization.StringSerializer; import org.apache.kafka.common.header.Headers; import org.apache.kafka.common.header.internals.RecordHeader; import org.apache.kafka.common.header.internals.RecordHeaders; @@ -76,7 +73,7 @@ public class TbKafkaNode implements TbNode { private boolean addMetadataKeyValuesAsKafkaHeaders; private Charset toBytesCharset; - private Producer producer; + private Producer producer; private Throwable initError; @Override @@ -115,12 +112,20 @@ public class TbKafkaNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { String topic = TbNodeUtils.processPattern(config.getTopicPattern(), msg); + String keyPattern = config.getKeyPattern(); try { if (initError != null) { ctx.tellFailure(msg, new RuntimeException("Failed to initialize Kafka rule node producer: " + initError.getMessage())); } else { ctx.getExternalCallExecutor().executeAsync(() -> { - publish(ctx, msg, topic); + publish( + ctx, + msg, + topic, + keyPattern == null || keyPattern.isEmpty() + ? null + : TbNodeUtils.processPattern(config.getKeyPattern(), msg) + ); return null; }); } @@ -129,16 +134,16 @@ public class TbKafkaNode implements TbNode { } } - protected void publish(TbContext ctx, TbMsg msg, String topic) { + protected void publish(TbContext ctx, TbMsg msg, String topic, String key) { try { if (!addMetadataKeyValuesAsKafkaHeaders) { //TODO: external system executor - producer.send(new ProducerRecord<>(topic, msg.getData()), + producer.send(new ProducerRecord<>(topic, key, msg.getData()), (metadata, e) -> processRecord(ctx, msg, metadata, e)); } else { Headers headers = new RecordHeaders(); - msg.getMetaData().values().forEach((key, value) -> headers.add(new RecordHeader(TB_MSG_MD_PREFIX + key, value.getBytes(toBytesCharset)))); - producer.send(new ProducerRecord<>(topic, null, null, null, msg.getData(), headers), + msg.getMetaData().values().forEach((k, v) -> headers.add(new RecordHeader(TB_MSG_MD_PREFIX + k, v.getBytes(toBytesCharset)))); + producer.send(new ProducerRecord<>(topic, null, null, key, msg.getData(), headers), (metadata, e) -> processRecord(ctx, msg, metadata, e)); } } catch (Exception e) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java index 2db833b6a2..0e696f3c25 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/kafka/TbKafkaNodeConfiguration.java @@ -26,6 +26,7 @@ import java.util.Map; public class TbKafkaNodeConfiguration implements NodeConfiguration { private String topicPattern; + private String keyPattern; private String bootstrapServers; private int retries; private int batchSize; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java index 6ad0ae9352..fc23a2ff6c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbMathNode.java @@ -220,10 +220,10 @@ public class TbMathNode implements TbNode { private TbMsg addToBodyAndMeta(TbMsg msg, Optional msgBodyOpt, double result, TbMathResult mathResultDef) { TbMsg tmpMsg = msg; if (mathResultDef.isAddToBody()) { - tmpMsg = addToBody(msg, mathResultDef, msgBodyOpt, result); + tmpMsg = addToBody(tmpMsg, mathResultDef, msgBodyOpt, result); } if (mathResultDef.isAddToMetadata()) { - tmpMsg = addToMeta(msg, mathResultDef, result); + tmpMsg = addToMeta(tmpMsg, mathResultDef, result); } return tmpMsg; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbRuleNodeMathFunctionType.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbRuleNodeMathFunctionType.java index ffcd518f24..2ac3d481f7 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbRuleNodeMathFunctionType.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/math/TbRuleNodeMathFunctionType.java @@ -23,7 +23,7 @@ public enum TbRuleNodeMathFunctionType { SIN, SINH, COS, COSH, TAN, TANH, ACOS, ASIN, ATAN, ATAN2(2), EXP, EXPM1, SQRT, CBRT, GET_EXP(1, 1, true), HYPOT(2), LOG, LOG10, LOG1P, CEIL(1, 1, true), FLOOR(1, 1, true), FLOOR_DIV(2), FLOOR_MOD(2), - ABS, MIN(2), MAX(2), POW, SIGNUM, RAD, DEG, + ABS, MIN(2), MAX(2), POW(2), SIGNUM, RAD, DEG, CUSTOM(0, 16, false); //Custom function based on exp4j diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java index b2c029589c..d9352f6e4e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNode.java @@ -15,16 +15,15 @@ */ package org.thingsboard.rule.engine.metadata; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.json.JsonWriteFeature; +import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; -import com.google.gson.JsonParseException; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.BooleanUtils; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; @@ -32,13 +31,18 @@ import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; -import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; @@ -51,18 +55,20 @@ import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; public abstract class TbAbstractGetAttributesNode implements TbNode { - private static ObjectMapper mapper = new ObjectMapper(); - private static final String VALUE = "value"; private static final String TS = "ts"; protected C config; + private boolean fetchToData; + private boolean isTellFailureIfAbsent; + private boolean getLatestValueWithTs; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = loadGetAttributesNodeConfig(configuration); - mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false); - mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); + this.fetchToData = config.isFetchToData(); + this.getLatestValueWithTs = config.isGetLatestValueWithTs(); + this.isTellFailureIfAbsent = BooleanUtils.toBooleanDefaultIfNull(this.config.isTellFailureIfAbsent(), true); } protected abstract C loadGetAttributesNodeConfig(TbNodeConfiguration configuration) throws TbNodeException; @@ -86,97 +92,110 @@ public abstract class TbAbstractGetAttributesNode> failuresMap = new ConcurrentHashMap<>(); - ListenableFuture> allFutures = Futures.allAsList( - putLatestTelemetry(ctx, entityId, msg, LATEST_TS, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg), failuresMap), - putAttrAsync(ctx, entityId, msg, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg), failuresMap, "cs_"), - putAttrAsync(ctx, entityId, msg, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg), failuresMap, "shared_"), - putAttrAsync(ctx, entityId, msg, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresMap, "ss_") + ListenableFuture>>> allFutures = Futures.allAsList( + getLatestTelemetry(ctx, entityId, TbNodeUtils.processPatterns(config.getLatestTsKeyNames(), msg), failuresMap), + getAttrAsync(ctx, entityId, CLIENT_SCOPE, TbNodeUtils.processPatterns(config.getClientAttributeNames(), msg), failuresMap), + getAttrAsync(ctx, entityId, SHARED_SCOPE, TbNodeUtils.processPatterns(config.getSharedAttributeNames(), msg), failuresMap), + getAttrAsync(ctx, entityId, SERVER_SCOPE, TbNodeUtils.processPatterns(config.getServerAttributeNames(), msg), failuresMap) ); - withCallback(allFutures, i -> { + withCallback(allFutures, futuresList -> { if (!failuresMap.isEmpty()) { throw reportFailures(failuresMap); } - ctx.tellSuccess(msg); + TbMsgMetaData msgMetaData = msg.getMetaData().copy(); + futuresList.stream().filter(Objects::nonNull).forEach(kvEntriesMap -> { + kvEntriesMap.forEach((keyScope, kvEntryList) -> { + String prefix = getPrefix(keyScope); + kvEntryList.forEach(kvEntry -> { + String key = prefix + kvEntry.getKey(); + if (fetchToData) { + JacksonUtil.addKvEntry((ObjectNode) msgDataNode, kvEntry, key); + } else { + msgMetaData.putValue(key, kvEntry.getValueAsString()); + } + }); + }); + }); + if (fetchToData) { + ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msgMetaData, JacksonUtil.toString(msgDataNode))); + } else { + ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msgMetaData, msg.getData())); + } }, t -> ctx.tellFailure(msg, t), ctx.getDbCallbackExecutor()); } - private ListenableFuture putAttrAsync(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List keys, ConcurrentHashMap> failuresMap, String prefix) { + private ListenableFuture>> getAttrAsync(TbContext ctx, EntityId entityId, String scope, List keys, ConcurrentHashMap> failuresMap) { if (CollectionUtils.isEmpty(keys)) { return Futures.immediateFuture(null); } ListenableFuture> attributeKvEntryListFuture = ctx.getAttributesService().find(ctx.getTenantId(), entityId, scope, keys); return Futures.transform(attributeKvEntryListFuture, attributeKvEntryList -> { - if (!CollectionUtils.isEmpty(attributeKvEntryList)) { - List existingAttributesKvEntry = attributeKvEntryList.stream().filter(attributeKvEntry -> keys.contains(attributeKvEntry.getKey())).collect(Collectors.toList()); - existingAttributesKvEntry.forEach(kvEntry -> msg.getMetaData().putValue(prefix + kvEntry.getKey(), kvEntry.getValueAsString())); - if (existingAttributesKvEntry.size() != keys.size() && BooleanUtils.toBooleanDefaultIfNull(this.config.isTellFailureIfAbsent(), true)) { - getNotExistingKeys(existingAttributesKvEntry, keys).forEach(key -> computeFailuresMap(scope, failuresMap, key)); - } - } else { - if (BooleanUtils.toBooleanDefaultIfNull(this.config.isTellFailureIfAbsent(), true)) { - keys.forEach(key -> computeFailuresMap(scope, failuresMap, key)); - } + if (isTellFailureIfAbsent && attributeKvEntryList.size() != keys.size()) { + getNotExistingKeys(attributeKvEntryList, keys).forEach(key -> computeFailuresMap(scope, failuresMap, key)); } - return null; + Map> mapAttributeKvEntry = new HashMap<>(); + mapAttributeKvEntry.put(scope, attributeKvEntryList); + return mapAttributeKvEntry; }, MoreExecutors.directExecutor()); } - private ListenableFuture putLatestTelemetry(TbContext ctx, EntityId entityId, TbMsg msg, String scope, List keys, ConcurrentHashMap> failuresMap) { + private ListenableFuture>> getLatestTelemetry(TbContext ctx, EntityId entityId, List keys, ConcurrentHashMap> failuresMap) { if (CollectionUtils.isEmpty(keys)) { return Futures.immediateFuture(null); } - ListenableFuture> latest = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), entityId, keys); - return Futures.transform(latest, l -> { - l.forEach(r -> { - boolean getLatestValueWithTs = BooleanUtils.toBooleanDefaultIfNull(this.config.isGetLatestValueWithTs(), false); - if (BooleanUtils.toBooleanDefaultIfNull(this.config.isTellFailureIfAbsent(), true)) { - if (r.getValue() == null) { - computeFailuresMap(scope, failuresMap, r.getKey()); - } else if (getLatestValueWithTs) { - putValueWithTs(msg, r); - } else { - msg.getMetaData().putValue(r.getKey(), r.getValueAsString()); + ListenableFuture> latestTelemetryFutures = ctx.getTimeseriesService().findLatest(ctx.getTenantId(), entityId, keys); + return Futures.transform(latestTelemetryFutures, tsKvEntries -> { + List listTsKvEntry = new ArrayList<>(); + tsKvEntries.forEach(tsKvEntry -> { + if (tsKvEntry.getValue() == null) { + if (isTellFailureIfAbsent) { + computeFailuresMap(LATEST_TS, failuresMap, tsKvEntry.getKey()); } + } else if (getLatestValueWithTs) { + listTsKvEntry.add(getValueWithTs(tsKvEntry)); } else { - if (r.getValue() != null) { - if (getLatestValueWithTs) { - putValueWithTs(msg, r); - } else { - msg.getMetaData().putValue(r.getKey(), r.getValueAsString()); - } - } + listTsKvEntry.add(new BasicTsKvEntry(tsKvEntry.getTs(), tsKvEntry)); } }); - return null; + Map> mapTsKvEntry = new HashMap<>(); + mapTsKvEntry.put(LATEST_TS, listTsKvEntry); + return mapTsKvEntry; }, MoreExecutors.directExecutor()); } - private void putValueWithTs(TbMsg msg, TsKvEntry r) { - ObjectNode value = mapper.createObjectNode(); - value.put(TS, r.getTs()); - switch (r.getDataType()) { - case STRING: - value.put(VALUE, r.getValueAsString()); - break; - case LONG: - value.put(VALUE, r.getLongValue().get()); - break; - case BOOLEAN: - value.put(VALUE, r.getBooleanValue().get()); + private TsKvEntry getValueWithTs(TsKvEntry tsKvEntry) { + ObjectMapper mapper = fetchToData ? JacksonUtil.OBJECT_MAPPER : JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER; + ObjectNode value = JacksonUtil.newObjectNode(mapper); + value.put(TS, tsKvEntry.getTs()); + JacksonUtil.addKvEntry(value, tsKvEntry, VALUE, mapper); + return new BasicTsKvEntry(tsKvEntry.getTs(), new JsonDataEntry(tsKvEntry.getKey(), value.toString())); + } + + private String getPrefix(String scope) { + String prefix = ""; + switch (scope) { + case CLIENT_SCOPE: + prefix = "cs_"; break; - case DOUBLE: - value.put(VALUE, r.getDoubleValue().get()); + case SHARED_SCOPE: + prefix = "shared_"; break; - case JSON: - try { - value.set(VALUE, mapper.readTree(r.getJsonValue().get())); - } catch (IOException e) { - throw new JsonParseException("Can't parse jsonValue: " + r.getJsonValue().get(), e); - } + case SERVER_SCOPE: + prefix = "ss_"; break; } - msg.getMetaData().putValue(r.getKey(), value.toString()); + return prefix; } private List getNotExistingKeys(List existingAttributesKvEntry, List allKeys) { diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java index 490a42a72c..e90b3ead76 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbAbstractGetEntityDetailsNode.java @@ -119,6 +119,9 @@ public abstract class TbAbstractGetEntityDetailsNodecs/shared/ss. Latest telemetry value added into metadata without prefix. " + + nodeDescription = "Add Message Originator Attributes or Latest Telemetry into Message Data or Metadata", + nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message data/metadata " + + "with specific prefix: cs/shared/ss. Latest telemetry value added into Message data/metadata without prefix. " + "To access those attributes in other nodes this template can be used " + "metadata.cs_temperature or metadata.shared_limit ", uiResources = {"static/rulenode/rulenode-core-config.js"}, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java index 4fc892296f..67766e5b51 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetAttributesNodeConfiguration.java @@ -35,6 +35,7 @@ public class TbGetAttributesNodeConfiguration implements NodeConfigurationcs/shared/ss. Latest telemetry value added into metadata without prefix. " + + nodeDescription = "Add Originators Related Device Attributes and Latest Telemetry value into Message Data or Metadata", + nodeDetails = "If Attributes enrichment configured, CLIENT/SHARED/SERVER attributes are added into Message data/metadata " + + "with specific prefix: cs/shared/ss. Latest telemetry value added into Message data/metadata without prefix. " + "To access those attributes in other nodes this template can be used " + "metadata.cs_temperature or metadata.shared_limit ", uiResources = {"static/rulenode/rulenode-core-config.js"}, diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java index 646e2dfef0..049fe39367 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetDeviceAttrNodeConfiguration.java @@ -36,6 +36,7 @@ public class TbGetDeviceAttrNodeConfiguration extends TbGetAttributesNodeConfigu configuration.setLatestTsKeyNames(Collections.emptyList()); configuration.setTellFailureIfAbsent(true); configuration.setGetLatestValueWithTs(false); + configuration.setFetchToData(false); DeviceRelationsQuery deviceRelationsQuery = new DeviceRelationsQuery(); deviceRelationsQuery.setDirection(EntitySearchDirection.FROM); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java index 77298cba77..045555eae8 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java @@ -15,25 +15,22 @@ */ package org.thingsboard.rule.engine.metadata; -import com.fasterxml.jackson.core.JsonParser; -import com.fasterxml.jackson.core.json.JsonWriteFeature; -import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; import com.google.common.util.concurrent.ListenableFuture; -import com.google.gson.JsonParseException; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.StringUtils; import org.apache.commons.lang3.math.NumberUtils; import org.thingsboard.common.util.DonAsynchron; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.RuleNode; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNode; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; @@ -41,7 +38,6 @@ import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; -import java.io.IOException; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -75,7 +71,6 @@ public class TbGetTelemetryNode implements TbNode { private TbGetTelemetryNodeConfiguration config; private List tsKeyNames; private int limit; - private ObjectMapper mapper; private String fetchMode; private String orderByFetchAll; private Aggregation aggregation; @@ -91,10 +86,6 @@ public class TbGetTelemetryNode implements TbNode { orderByFetchAll = ASC_ORDER; } aggregation = parseAggregationConfig(config.getAggregation()); - - mapper = new ObjectMapper(); - mapper.configure(JsonWriteFeature.QUOTE_FIELD_NAMES.mappedFeature(), false); - mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_FIELD_NAMES, true); } Aggregation parseAggregationConfig(String aggName) { @@ -146,7 +137,7 @@ public class TbGetTelemetryNode implements TbNode { } private void process(List entries, TbMsg msg, List keys) { - ObjectNode resultNode = mapper.createObjectNode(); + ObjectNode resultNode = JacksonUtil.newObjectNode(JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); if (FETCH_MODE_ALL.equals(fetchMode)) { entries.forEach(entry -> processArray(resultNode, entry)); } else { @@ -169,36 +160,16 @@ public class TbGetTelemetryNode implements TbNode { ArrayNode arrayNode = (ArrayNode) node.get(entry.getKey()); arrayNode.add(buildNode(entry)); } else { - ArrayNode arrayNode = mapper.createArrayNode(); + ArrayNode arrayNode = JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER.createArrayNode(); arrayNode.add(buildNode(entry)); node.set(entry.getKey(), arrayNode); } } private ObjectNode buildNode(TsKvEntry entry) { - ObjectNode obj = mapper.createObjectNode() - .put("ts", entry.getTs()); - switch (entry.getDataType()) { - case STRING: - obj.put("value", entry.getValueAsString()); - break; - case LONG: - obj.put("value", entry.getLongValue().get()); - break; - case BOOLEAN: - obj.put("value", entry.getBooleanValue().get()); - break; - case DOUBLE: - obj.put("value", entry.getDoubleValue().get()); - break; - case JSON: - try { - obj.set("value", mapper.readTree(entry.getJsonValue().get())); - } catch (IOException e) { - throw new JsonParseException("Can't parse jsonValue: " + entry.getJsonValue().get(), e); - } - break; - } + ObjectNode obj = JacksonUtil.newObjectNode(JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); + obj.put("ts", entry.getTs()); + JacksonUtil.addKvEntry(obj, entry, "value", JacksonUtil.ALLOW_UNQUOTED_FIELD_NAMES_MAPPER); return obj; } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java index 6dd632aa97..77b3eb6ff1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNode.java @@ -76,7 +76,7 @@ public class TbMqttNode implements TbNode { @Override public void onMsg(TbContext ctx, TbMsg msg) { String topic = TbNodeUtils.processPattern(this.mqttNodeConfiguration.getTopicPattern(), msg); - this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE) + this.mqttClient.publish(topic, Unpooled.wrappedBuffer(msg.getData().getBytes(UTF8)), MqttQoS.AT_LEAST_ONCE, mqttNodeConfiguration.isRetainedMessage()) .addListener(future -> { if (future.isSuccess()) { ctx.tellSuccess(msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java index 8ae95134ad..e4441a85c1 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/mqtt/TbMqttNodeConfiguration.java @@ -29,6 +29,7 @@ public class TbMqttNodeConfiguration implements NodeConfiguration future = ctx.getEdgeEventService().saveAsync(edgeEvent); + Futures.addCallback(future, new FutureCallback<>() { + @Override + public void onSuccess(Void result) { + ctx.onEdgeEventUpdate(ctx.getTenantId(), edgeId); + ctx.tellSuccess(msg); + } + + @Override + public void onFailure(Throwable t) { + ctx.tellFailure(msg, t); + } + }, ctx.getDbCallbackExecutor()); + } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java index f80c76331f..8a801cbb7f 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbChangeOriginatorNode.java @@ -57,7 +57,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { protected static final String TENANT_SOURCE = "TENANT"; protected static final String RELATED_SOURCE = "RELATED"; protected static final String ALARM_ORIGINATOR_SOURCE = "ALARM_ORIGINATOR"; - protected static final String ENTITY_SOURCE = "ENTITY_SOURCE"; + protected static final String ENTITY_SOURCE = "ENTITY"; private TbChangeOriginatorNodeConfiguration config; diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java index 9a2dd85731..fd33bb4554 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNode.java @@ -22,7 +22,9 @@ import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.util.TbNodeUtils; +import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import java.util.List; @@ -40,23 +42,25 @@ import java.util.List; "{ msg: new payload,
   metadata: new metadata,
   msgType: new msgType }

" + "All fields in resulting object are optional and will be taken from original message if not specified.", uiResources = {"static/rulenode/rulenode-core-config.js"}, - configDirective = "tbTransformationNodeScriptConfig") + configDirective = "tbTransformationNodeScriptConfig" +) public class TbTransformMsgNode extends TbAbstractTransformNode { private TbTransformMsgNodeConfiguration config; - private ScriptEngine jsEngine; + private ScriptEngine scriptEngine; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class); - this.jsEngine = ctx.createJsScriptEngine(config.getJsScript()); + scriptEngine = ctx.createScriptEngine(config.getScriptLang(), + ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript()); setConfig(config); } @Override protected ListenableFuture> transform(TbContext ctx, TbMsg msg) { ctx.logJsEvalRequest(); - return jsEngine.executeUpdateAsync(msg); + return scriptEngine.executeUpdateAsync(msg); } @Override @@ -73,8 +77,8 @@ public class TbTransformMsgNode extends TbAbstractTransformNode { @Override public void destroy() { - if (jsEngine != null) { - jsEngine.destroy(); + if (scriptEngine != null) { + scriptEngine.destroy(); } } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java index 01d9fc81e3..8779fa2541 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeConfiguration.java @@ -17,16 +17,21 @@ package org.thingsboard.rule.engine.transform; import lombok.Data; import org.thingsboard.rule.engine.api.NodeConfiguration; +import org.thingsboard.server.common.data.script.ScriptLanguage; @Data public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguration implements NodeConfiguration { + private ScriptLanguage scriptLang; private String jsScript; + private String mvelScript; @Override public TbTransformMsgNodeConfiguration defaultConfiguration() { TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); + configuration.setScriptLang(ScriptLanguage.MVEL); configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};"); + configuration.setMvelScript("return {msg: msg, metadata: metadata, msgType: msgType};"); return configuration; } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java deleted file mode 100644 index 4d57e89fda..0000000000 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesTenantIdAsyncLoader.java +++ /dev/null @@ -1,66 +0,0 @@ -/** - * Copyright © 2016-2022 The Thingsboard Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package org.thingsboard.rule.engine.util; - -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; -import com.google.common.util.concurrent.MoreExecutors; -import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.rule.engine.api.TbNodeException; -import org.thingsboard.server.common.data.HasTenantId; -import org.thingsboard.server.common.data.id.AlarmId; -import org.thingsboard.server.common.data.id.AssetId; -import org.thingsboard.server.common.data.id.CustomerId; -import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.common.data.id.EntityId; -import org.thingsboard.server.common.data.id.RuleChainId; -import org.thingsboard.server.common.data.id.TenantId; -import org.thingsboard.server.common.data.id.UserId; - -public class EntitiesTenantIdAsyncLoader { - /** - * @deprecated consider to remove since tenantId is already defined in the TbContext. - */ - @Deprecated - public static ListenableFuture findEntityIdAsync(TbContext ctx, EntityId original) { - - switch (original.getEntityType()) { - case TENANT: - return Futures.immediateFuture((TenantId) original); - case CUSTOMER: - return getTenantAsync(ctx.getCustomerService().findCustomerByIdAsync(ctx.getTenantId(), (CustomerId) original)); - case USER: - return getTenantAsync(ctx.getUserService().findUserByIdAsync(ctx.getTenantId(), (UserId) original)); - case ASSET: - return getTenantAsync(ctx.getAssetService().findAssetByIdAsync(ctx.getTenantId(), (AssetId) original)); - case DEVICE: - return getTenantAsync(ctx.getDeviceService().findDeviceByIdAsync(ctx.getTenantId(), (DeviceId) original)); - case ALARM: - return getTenantAsync(ctx.getAlarmService().findAlarmByIdAsync(ctx.getTenantId(), (AlarmId) original)); - case RULE_CHAIN: - return getTenantAsync(ctx.getRuleChainService().findRuleChainByIdAsync(ctx.getTenantId(), (RuleChainId) original)); - default: - return Futures.immediateFailedFuture(new TbNodeException("Unexpected original EntityType " + original.getEntityType())); - } - } - - private static ListenableFuture getTenantAsync(ListenableFuture future) { - return Futures.transformAsync(future, in -> { - return in != null ? Futures.immediateFuture(in.getTenantId()) - : Futures.immediateFuture(null); - }, MoreExecutors.directExecutor()); - } -} diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityDetails.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityDetails.java index c97848e05f..a1dc21f8dd 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityDetails.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntityDetails.java @@ -17,6 +17,6 @@ package org.thingsboard.rule.engine.util; public enum EntityDetails { - TITLE, COUNTRY, CITY, STATE, ZIP, ADDRESS, ADDRESS2, PHONE, EMAIL, ADDITIONAL_INFO + ID, TITLE, COUNTRY, CITY, STATE, ZIP, ADDRESS, ADDRESS2, PHONE, EMAIL, ADDITIONAL_INFO } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java new file mode 100644 index 0000000000..b687246789 --- /dev/null +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/TenantIdLoader.java @@ -0,0 +1,132 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.util; + +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.HasTenantId; +import org.thingsboard.server.common.data.id.AlarmId; +import org.thingsboard.server.common.data.id.ApiUsageStateId; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.AssetProfileId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EdgeId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.OtaPackageId; +import org.thingsboard.server.common.data.id.QueueId; +import org.thingsboard.server.common.data.id.RpcId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TbResourceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.rule.RuleNode; + +import java.util.UUID; + +public class TenantIdLoader { + + public static TenantId findTenantId(TbContext ctx, EntityId entityId) { + UUID id = entityId.getId(); + EntityType entityType = entityId.getEntityType(); + TenantId ctxTenantId = ctx.getTenantId(); + + HasTenantId tenantEntity; + switch (entityType) { + case TENANT: + return new TenantId(id); + case CUSTOMER: + tenantEntity = ctx.getCustomerService().findCustomerById(ctxTenantId, new CustomerId(id)); + break; + case USER: + tenantEntity = ctx.getUserService().findUserById(ctxTenantId, new UserId(id)); + break; + case ASSET: + tenantEntity = ctx.getAssetService().findAssetById(ctxTenantId, new AssetId(id)); + break; + case DEVICE: + tenantEntity = ctx.getDeviceService().findDeviceById(ctxTenantId, new DeviceId(id)); + break; + case ALARM: + tenantEntity = ctx.getAlarmService().findAlarmById(ctxTenantId, new AlarmId(id)); + break; + case RULE_CHAIN: + tenantEntity = ctx.getRuleChainService().findRuleChainById(ctxTenantId, new RuleChainId(id)); + break; + case ENTITY_VIEW: + tenantEntity = ctx.getEntityViewService().findEntityViewById(ctxTenantId, new EntityViewId(id)); + break; + case DASHBOARD: + tenantEntity = ctx.getDashboardService().findDashboardById(ctxTenantId, new DashboardId(id)); + break; + case EDGE: + tenantEntity = ctx.getEdgeService().findEdgeById(ctxTenantId, new EdgeId(id)); + break; + case OTA_PACKAGE: + tenantEntity = ctx.getOtaPackageService().findOtaPackageInfoById(ctxTenantId, new OtaPackageId(id)); + break; + case ASSET_PROFILE: + tenantEntity = ctx.getAssetProfileCache().get(ctxTenantId, new AssetProfileId(id)); + break; + case DEVICE_PROFILE: + tenantEntity = ctx.getDeviceProfileCache().get(ctxTenantId, new DeviceProfileId(id)); + break; + case WIDGET_TYPE: + tenantEntity = ctx.getWidgetTypeService().findWidgetTypeById(ctxTenantId, new WidgetTypeId(id)); + break; + case WIDGETS_BUNDLE: + tenantEntity = ctx.getWidgetBundleService().findWidgetsBundleById(ctxTenantId, new WidgetsBundleId(id)); + break; + case RPC: + tenantEntity = ctx.getRpcService().findRpcById(ctxTenantId, new RpcId(id)); + break; + case QUEUE: + tenantEntity = ctx.getQueueService().findQueueById(ctxTenantId, new QueueId(id)); + break; + case API_USAGE_STATE: + tenantEntity = ctx.getRuleEngineApiUsageStateService().findApiUsageStateById(ctxTenantId, new ApiUsageStateId(id)); + break; + case TB_RESOURCE: + tenantEntity = ctx.getResourceService().findResourceInfoById(ctxTenantId, new TbResourceId(id)); + break; + case RULE_NODE: + RuleNode ruleNode = ctx.getRuleChainService().findRuleNodeById(ctxTenantId, new RuleNodeId(id)); + if (ruleNode != null) { + tenantEntity = ctx.getRuleChainService().findRuleChainById(ctxTenantId, ruleNode.getRuleChainId()); + } else { + tenantEntity = null; + } + break; + case TENANT_PROFILE: + if (ctx.getTenantProfile().getId().equals(entityId)) { + return ctxTenantId; + } else { + tenantEntity = null; + } + break; + default: + throw new RuntimeException("Unexpected entity type: " + entityId.getEntityType()); + } + return tenantEntity != null ? tenantEntity.getTenantId() : null; + } + +} diff --git a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js index 25ea0336c9..2ee538bdfd 100644 --- a/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js +++ b/rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js @@ -1 +1 @@ -System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/material/form-field","@angular/material/checkbox","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/input","@angular/common","@angular/platform-browser","@angular/material/select","@angular/material/core","@angular/material/expansion","@shared/components/button/toggle-password.component","@shared/components/file-input.component","@shared/components/queue/queue-autocomplete.component","@core/public-api","@shared/components/js-func.component","@angular/material/button","@angular/cdk/keycodes","@angular/material/chips","@angular/material/icon","@angular/flex-layout/extended","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/material/tooltip","rxjs/operators","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@angular/material/list","@angular/cdk/drag-drop","@home/components/public-api","@shared/components/relation/relation-type-autocomplete.component","@shared/components/entity/entity-subtype-list.component","@home/components/relation/relation-filters.component","rxjs","@angular/material/slide-toggle","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component"],(function(e){"use strict";var t,o,r,a,n,l,i,s,m,u,p,d,f,c,g,x,y,b,h,C,F,L,v,I,N,T,k,M,q,S,A,G,D,E,V,P,R,O,w,H,U,B,K,j,_,z,Q,$,Y,W,J,X,Z,ee,te,oe,re,ae,ne,le,ie,se,me,ue,pe,de,fe,ce,ge,xe,ye,be,he,Ce,Fe,Le,ve,Ie,Ne,Te,ke,Me;return{setters:[function(e){t=e,o=e.Component,r=e.Pipe,a=e.ViewChild,n=e.forwardRef,l=e.Input,i=e.NgModule},function(e){s=e.RuleNodeConfigurationComponent,m=e.AttributeScope,u=e.telemetryTypeTranslations,p=e.ServiceType,d=e.AlarmSeverity,f=e.alarmSeverityTranslations,c=e.EntitySearchDirection,g=e.entitySearchDirectionTranslations,x=e.EntityType,y=e.PageComponent,b=e.MessageType,h=e.messageTypeNames,C=e,F=e.SharedModule,L=e.AggregationType,v=e.aggregationTranslations,I=e.alarmStatusTranslations,N=e.AlarmStatus},function(e){T=e},function(e){k=e,M=e.Validators,q=e.NgControl,S=e.NG_VALUE_ACCESSOR,A=e.NG_VALIDATORS,G=e.FormControl},function(e){D=e},function(e){E=e},function(e){V=e},function(e){P=e},function(e){R=e},function(e){O=e,w=e.CommonModule},function(e){H=e},function(e){U=e},function(e){B=e},function(e){K=e},function(e){j=e},function(e){_=e},function(e){z=e},function(e){Q=e,$=e.isDefinedAndNotNull,Y=e.isNotEmptyStr},function(e){W=e},function(e){J=e},function(e){X=e.ENTER,Z=e.COMMA,ee=e.SEMICOLON},function(e){te=e},function(e){oe=e},function(e){re=e},function(e){ae=e},function(e){ne=e},function(e){le=e.coerceBooleanProperty},function(e){ie=e},function(e){se=e},function(e){me=e.distinctUntilChanged,ue=e.tap,pe=e.map,de=e.startWith,fe=e.mergeMap,ce=e.share},function(e){ge=e},function(e){xe=e},function(e){ye=e},function(e){be=e},function(e){he=e},function(e){Ce=e},function(e){Fe=e.HomeComponentsModule},function(e){Le=e},function(e){ve=e},function(e){Ie=e},function(e){Ne=e.of},function(e){Te=e},function(e){ke=e},function(e){Me=e}],execute:function(){class qe extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.emptyConfigForm}onConfigurationSet(e){this.emptyConfigForm=this.fb.group({})}}e("EmptyConfigComponent",qe),qe.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:qe,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qe.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:qe,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:t,template:"

",isInline:!0}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:qe,decorators:[{type:o,args:[{selector:"tb-node-empty-config",template:"
",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Se{constructor(e){this.sanitizer=e}transform(e){return this.sanitizer.bypassSecurityTrustHtml(e)}}e("SafeHtmlPipe",Se),Se.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Se,deps:[{token:H.DomSanitizer}],target:t.ɵɵFactoryTarget.Pipe}),Se.ɵpipe=t.ɵɵngDeclarePipe({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Se,name:"safeHtml"}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Se,decorators:[{type:r,args:[{name:"safeHtml"}]}],ctorParameters:function(){return[{type:H.DomSanitizer}]}});class Ae extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(e){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[M.required,M.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!e&&e.createCustomerIfNotExists,[]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[M.required,M.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("AssignCustomerConfigComponent",Ae),Ae.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ae,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ae.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ae,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ae,decorators:[{type:o,args:[{selector:"tb-action-node-assign-to-customer-config",templateUrl:"./assign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Ge extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.attributesConfigForm}onConfigurationSet(e){this.attributesConfigForm=this.fb.group({scope:[e?e.scope:null,[M.required]],notifyDevice:[!e||e.notifyDevice,[]]})}}var De;e("AttributesConfigComponent",Ge),Ge.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ge,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ge.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ge,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ge,decorators:[{type:o,args:[{selector:"tb-action-node-attributes-config",templateUrl:"./attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}}),function(e){e.CUSTOMER="CUSTOMER",e.TENANT="TENANT",e.RELATED="RELATED",e.ALARM_ORIGINATOR="ALARM_ORIGINATOR",e.ENTITY="ENTITY"}(De||(De={}));const Ee=new Map([[De.CUSTOMER,"tb.rulenode.originator-customer"],[De.TENANT,"tb.rulenode.originator-tenant"],[De.RELATED,"tb.rulenode.originator-related"],[De.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[De.ENTITY,"tb.rulenode.originator-entity"]]);var Ve;!function(e){e.CIRCLE="CIRCLE",e.POLYGON="POLYGON"}(Ve||(Ve={}));const Pe=new Map([[Ve.CIRCLE,"tb.rulenode.perimeter-circle"],[Ve.POLYGON,"tb.rulenode.perimeter-polygon"]]);var Re;!function(e){e.MILLISECONDS="MILLISECONDS",e.SECONDS="SECONDS",e.MINUTES="MINUTES",e.HOURS="HOURS",e.DAYS="DAYS"}(Re||(Re={}));const Oe=new Map([[Re.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[Re.SECONDS,"tb.rulenode.time-unit-seconds"],[Re.MINUTES,"tb.rulenode.time-unit-minutes"],[Re.HOURS,"tb.rulenode.time-unit-hours"],[Re.DAYS,"tb.rulenode.time-unit-days"]]);var we;!function(e){e.METER="METER",e.KILOMETER="KILOMETER",e.FOOT="FOOT",e.MILE="MILE",e.NAUTICAL_MILE="NAUTICAL_MILE"}(we||(we={}));const He=new Map([[we.METER,"tb.rulenode.range-unit-meter"],[we.KILOMETER,"tb.rulenode.range-unit-kilometer"],[we.FOOT,"tb.rulenode.range-unit-foot"],[we.MILE,"tb.rulenode.range-unit-mile"],[we.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var Ue;!function(e){e.TITLE="TITLE",e.COUNTRY="COUNTRY",e.STATE="STATE",e.CITY="CITY",e.ZIP="ZIP",e.ADDRESS="ADDRESS",e.ADDRESS2="ADDRESS2",e.PHONE="PHONE",e.EMAIL="EMAIL",e.ADDITIONAL_INFO="ADDITIONAL_INFO"}(Ue||(Ue={}));const Be=new Map([[Ue.TITLE,"tb.rulenode.entity-details-title"],[Ue.COUNTRY,"tb.rulenode.entity-details-country"],[Ue.STATE,"tb.rulenode.entity-details-state"],[Ue.CITY,"tb.rulenode.entity-details-city"],[Ue.ZIP,"tb.rulenode.entity-details-zip"],[Ue.ADDRESS,"tb.rulenode.entity-details-address"],[Ue.ADDRESS2,"tb.rulenode.entity-details-address2"],[Ue.PHONE,"tb.rulenode.entity-details-phone"],[Ue.EMAIL,"tb.rulenode.entity-details-email"],[Ue.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Ke,je,_e;!function(e){e.FIRST="FIRST",e.LAST="LAST",e.ALL="ALL"}(Ke||(Ke={})),function(e){e.ASC="ASC",e.DESC="DESC"}(je||(je={})),function(e){e.STANDARD="STANDARD",e.FIFO="FIFO"}(_e||(_e={}));const ze=new Map([[_e.STANDARD,"tb.rulenode.sqs-queue-standard"],[_e.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Qe=["anonymous","basic","cert.PEM"],$e=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Ye=["sas","cert.PEM"],We=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var Je;!function(e){e.GET="GET",e.POST="POST",e.PUT="PUT",e.DELETE="DELETE"}(Je||(Je={}));const Xe=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],Ze=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);var et;!function(e){e.CUSTOM="CUSTOM",e.ADD="ADD",e.SUB="SUB",e.MULT="MULT",e.DIV="DIV",e.SIN="SIN",e.SINH="SINH",e.COS="COS",e.COSH="COSH",e.TAN="TAN",e.TANH="TANH",e.ACOS="ACOS",e.ASIN="ASIN",e.ATAN="ATAN",e.ATAN2="ATAN2",e.EXP="EXP",e.EXPM1="EXPM1",e.SQRT="SQRT",e.CBRT="CBRT",e.GET_EXP="GET_EXP",e.HYPOT="HYPOT",e.LOG="LOG",e.LOG10="LOG10",e.LOG1P="LOG1P",e.CEIL="CEIL",e.FLOOR="FLOOR",e.FLOOR_DIV="FLOOR_DIV",e.FLOOR_MOD="FLOOR_MOD",e.ABS="ABS",e.MIN="MIN",e.MAX="MAX",e.POW="POW",e.SIGNUM="SIGNUM",e.RAD="RAD",e.DEG="DEG"}(et||(et={}));const tt=new Map([[et.CUSTOM,{value:et.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[et.ADD,{value:et.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[et.SUB,{value:et.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[et.MULT,{value:et.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[et.DIV,{value:et.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[et.SIN,{value:et.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[et.SINH,{value:et.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[et.COS,{value:et.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[et.COSH,{value:et.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[et.TAN,{value:et.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[et.TANH,{value:et.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[et.ACOS,{value:et.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[et.ASIN,{value:et.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[et.ATAN,{value:et.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[et.ATAN2,{value:et.ATAN2,name:"2-argument arc tangent",description:"Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)",minArgs:2,maxArgs:2}],[et.EXP,{value:et.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[et.EXPM1,{value:et.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[et.SQRT,{value:et.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[et.CBRT,{value:et.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[et.GET_EXP,{value:et.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[et.HYPOT,{value:et.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[et.LOG,{value:et.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[et.LOG10,{value:et.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[et.LOG1P,{value:et.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[et.CEIL,{value:et.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[et.FLOOR,{value:et.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[et.FLOOR_DIV,{value:et.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[et.FLOOR_MOD,{value:et.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[et.ABS,{value:et.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[et.MIN,{value:et.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[et.MAX,{value:et.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[et.POW,{value:et.POW,name:"Raise to a power",description:"Returns the value of the first argument raised to the power of the second argument",minArgs:2,maxArgs:2}],[et.SIGNUM,{value:et.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[et.RAD,{value:et.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[et.DEG,{value:et.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var ot,rt;!function(e){e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.CONSTANT="CONSTANT",e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA"}(ot||(ot={})),function(e){e.ATTRIBUTE="ATTRIBUTE",e.TIME_SERIES="TIME_SERIES",e.MESSAGE_BODY="MESSAGE_BODY",e.MESSAGE_METADATA="MESSAGE_METADATA"}(rt||(rt={}));const at=new Map([[ot.ATTRIBUTE,"tb.rulenode.attribute-type"],[ot.TIME_SERIES,"tb.rulenode.time-series-type"],[ot.CONSTANT,"tb.rulenode.constant-type"],[ot.MESSAGE_BODY,"tb.rulenode.message-body-type"],[ot.MESSAGE_METADATA,"tb.rulenode.message-metadata-type"]]),nt=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var lt,it;!function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE",e.CLIENT_SCOPE="CLIENT_SCOPE"}(lt||(lt={})),function(e){e.SHARED_SCOPE="SHARED_SCOPE",e.SERVER_SCOPE="SERVER_SCOPE"}(it||(it={}));const st=new Map([[lt.SHARED_SCOPE,"tb.rulenode.shared-scope"],[lt.SERVER_SCOPE,"tb.rulenode.server-scope"],[lt.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);class mt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allAzureIotHubCredentialsTypes=Ye,this.azureIotHubCredentialsTypeTranslationsMap=We}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(e){this.azureIotHubConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[M.required]],host:[e?e.host:null,[M.required]],port:[e?e.port:null,[M.required,M.min(1),M.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[M.required,M.min(1),M.max(200)]],clientId:[e?e.clientId:null,[M.required]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:this.fb.group({type:[e&&e.credentials?e.credentials.type:null,[M.required]],sasKey:[e&&e.credentials?e.credentials.sasKey:null,[]],caCert:[e&&e.credentials?e.credentials.caCert:null,[]],caCertFileName:[e&&e.credentials?e.credentials.caCertFileName:null,[]],privateKey:[e&&e.credentials?e.credentials.privateKey:null,[]],privateKeyFileName:[e&&e.credentials?e.credentials.privateKeyFileName:null,[]],cert:[e&&e.credentials?e.credentials.cert:null,[]],certFileName:[e&&e.credentials?e.credentials.certFileName:null,[]],password:[e&&e.credentials?e.credentials.password:null,[]]})})}prepareOutputConfig(e){const t=e.credentials.type;return"sas"===t&&(e.credentials={type:t,sasKey:e.credentials.sasKey,caCert:e.credentials.caCert,caCertFileName:e.credentials.caCertFileName}),e}validatorTriggers(){return["credentials.type"]}updateValidators(e){const t=this.azureIotHubConfigForm.get("credentials"),o=t.get("type").value;switch(e&&t.reset({type:o},{emitEvent:!1}),t.get("sasKey").setValidators([]),t.get("privateKey").setValidators([]),t.get("privateKeyFileName").setValidators([]),t.get("cert").setValidators([]),t.get("certFileName").setValidators([]),o){case"sas":t.get("sasKey").setValidators([M.required]);break;case"cert.PEM":t.get("privateKey").setValidators([M.required]),t.get("privateKeyFileName").setValidators([M.required]),t.get("cert").setValidators([M.required]),t.get("certFileName").setValidators([M.required])}t.get("sasKey").updateValueAndValidity({emitEvent:e}),t.get("privateKey").updateValueAndValidity({emitEvent:e}),t.get("privateKeyFileName").updateValueAndValidity({emitEvent:e}),t.get("cert").updateValueAndValidity({emitEvent:e}),t.get("certFileName").updateValueAndValidity({emitEvent:e})}}e("AzureIotHubConfigComponent",mt),mt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:mt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:mt,selector:"tb-action-node-azure-iot-hub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:K.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:K.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:_.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:K.MatAccordion,selector:"mat-accordion",inputs:["multi","displayMode","togglePosition","hideToggle"],exportAs:["matAccordion"]},{type:K.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:K.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:k.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:O.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:O.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:mt,decorators:[{type:o,args:[{selector:"tb-action-node-azure-iot-hub-config",templateUrl:"./azure-iot-hub-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class ut extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.serviceType=p.TB_RULE_ENGINE}configForm(){return this.checkPointConfigForm}onConfigurationSet(e){this.checkPointConfigForm=this.fb.group({queueName:[e?e.queueName:null,[M.required]]})}}e("CheckPointConfigComponent",ut),ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ut,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ut,selector:"tb-action-node-check-point-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',components:[{type:z.QueueAutocompleteComponent,selector:"tb-queue-autocomplete",inputs:["labelText","requiredText","required","queueType","disabled"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ut,decorators:[{type:o,args:[{selector:"tb-action-node-check-point-config",templateUrl:"./check-point-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class pt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(e){this.clearAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[M.required]],alarmType:[e?e.alarmType:null,[M.required]]})}testScript(){const e=this.clearAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(e,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/clear_alarm_node_script_fn").subscribe((e=>{e&&this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("ClearAlarmConfigComponent",pt),pt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:pt,deps:[{token:T.Store},{token:k.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),pt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:pt,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n',components:[{type:W.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:pt,decorators:[{type:o,args:[{selector:"tb-action-node-clear-alarm-config",templateUrl:"./clear-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class dt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r,this.alarmSeverities=Object.keys(d),this.alarmSeverityTranslationMap=f,this.separatorKeysCodes=[X,Z,ee]}configForm(){return this.createAlarmConfigForm}onConfigurationSet(e){this.createAlarmConfigForm=this.fb.group({alarmDetailsBuildJs:[e?e.alarmDetailsBuildJs:null,[M.required]],useMessageAlarmData:[!!e&&e.useMessageAlarmData,[]],overwriteAlarmDetails:[!!e&&e.overwriteAlarmDetails,[]],alarmType:[e?e.alarmType:null,[]],severity:[e?e.severity:null,[]],propagate:[!!e&&e.propagate,[]],relationTypes:[e?e.relationTypes:null,[]],propagateToOwner:[!!e&&e.propagateToOwner,[]],propagateToTenant:[!!e&&e.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((e=>{e?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData"]}updateValidators(e){this.createAlarmConfigForm.get("useMessageAlarmData").value?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([M.required]),this.createAlarmConfigForm.get("severity").setValidators([M.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:e}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:e})}testScript(){const e=this.createAlarmConfigForm.get("alarmDetailsBuildJs").value;this.nodeScriptTestService.testNodeScript(e,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/create_alarm_node_script_fn").subscribe((e=>{e&&this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValue(e)}))}removeKey(e,t){const o=this.createAlarmConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.createAlarmConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.createAlarmConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.createAlarmConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}onValidate(){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,t=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;e&&!t||this.jsFuncComponent.validateOnSubmit()}}e("CreateAlarmConfigComponent",dt),dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:dt,deps:[{token:T.Store},{token:k.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:dt,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
\n \n \n \n
\n \n
\n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:W.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:re.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:dt,decorators:[{type:o,args:[{selector:"tb-action-node-create-alarm-config",templateUrl:"./create-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class ft extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x}configForm(){return this.createRelationConfigForm}onConfigurationSet(e){this.createRelationConfigForm=this.fb.group({direction:[e?e.direction:null,[M.required]],entityType:[e?e.entityType:null,[M.required]],entityNamePattern:[e?e.entityNamePattern:null,[]],entityTypePattern:[e?e.entityTypePattern:null,[]],relationType:[e?e.relationType:null,[M.required]],createEntityIfNotExists:[!!e&&e.createEntityIfNotExists,[]],removeCurrentRelations:[!!e&&e.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!e&&e.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[M.required,M.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(e){const t=this.createRelationConfigForm.get("entityType").value;t?this.createRelationConfigForm.get("entityNamePattern").setValidators([M.required,M.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!t||t!==x.DEVICE&&t!==x.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([M.required,M.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e.entityTypePattern=e.entityTypePattern?e.entityTypePattern.trim():null,e}}e("CreateRelationConfigComponent",ft),ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ft,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ft,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ft,decorators:[{type:o,args:[{selector:"tb-action-node-create-relation-config",templateUrl:"./create-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class ct extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(e){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!e&&e.deleteForSingleEntity,[]],direction:[e?e.direction:null,[M.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationType:[e?e.relationType:null,[M.required]],entityCacheExpiration:[e?e.entityCacheExpiration:null,[M.required,M.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(e){const t=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,o=this.deleteRelationConfigForm.get("entityType").value;t?this.deleteRelationConfigForm.get("entityType").setValidators([M.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),t&&o?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([M.required,M.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}prepareOutputConfig(e){return e.entityNamePattern=e.entityNamePattern?e.entityNamePattern.trim():null,e}}e("DeleteRelationConfigComponent",ct),ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ct,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ct,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ct,decorators:[{type:o,args:[{selector:"tb-action-node-delete-relation-config",templateUrl:"./delete-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class gt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.deviceProfile}onConfigurationSet(e){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!e&&e.persistAlarmRulesState,M.required],fetchAlarmRulesStateOnStart:[!!e&&e.fetchAlarmRulesStateOnStart,M.required]})}}e("DeviceProfileConfigComponent",gt),gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:gt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:gt,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:gt,decorators:[{type:o,args:[{selector:"tb-device-profile-config",templateUrl:"./device-profile-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class xt extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.generatorConfigForm}onConfigurationSet(e){this.generatorConfigForm=this.fb.group({msgCount:[e?e.msgCount:null,[M.required,M.min(0)]],periodInSeconds:[e?e.periodInSeconds:null,[M.required,M.min(1)]],originator:[e?e.originator:null,[]],jsScript:[e?e.jsScript:null,[M.required]]})}prepareInputConfig(e){return e&&(e.originatorId&&e.originatorType?e.originator={id:e.originatorId,entityType:e.originatorType}:e.originator=null,delete e.originatorId,delete e.originatorType),e}prepareOutputConfig(e){return e.originator?(e.originatorId=e.originator.id,e.originatorType=e.originator.entityType):(e.originatorId=null,e.originatorType=null),delete e.originator,e}testScript(){const e=this.generatorConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,"rulenode/generator_node_script_fn").subscribe((e=>{e&&this.generatorConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("GeneratorConfigComponent",xt),xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:xt,deps:[{token:T.Store},{token:k.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:xt,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n
\n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:ne.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{type:W.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:xt,decorators:[{type:o,args:[{selector:"tb-action-node-generator-config",templateUrl:"./generator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class yt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Ve,this.perimeterTypes=Object.keys(Ve),this.perimeterTypeTranslationMap=Pe,this.rangeUnits=Object.keys(we),this.rangeUnitTranslationMap=He,this.timeUnits=Object.keys(Re),this.timeUnitsTranslationMap=Oe}configForm(){return this.geoActionConfigForm}onConfigurationSet(e){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[M.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[M.required]],perimeterType:[e?e.perimeterType:null,[M.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]],minInsideDuration:[e?e.minInsideDuration:null,[M.required,M.min(1),M.max(2147483647)]],minInsideDurationTimeUnit:[e?e.minInsideDurationTimeUnit:null,[M.required]],minOutsideDuration:[e?e.minOutsideDuration:null,[M.required,M.min(1),M.max(2147483647)]],minOutsideDurationTimeUnit:[e?e.minOutsideDurationTimeUnit:null,[M.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoActionConfigForm.get("perimeterType").value;t?this.geoActionConfigForm.get("perimeterKeyName").setValidators([M.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),t||o!==Ve.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([M.required,M.min(-90),M.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([M.required,M.min(-180),M.max(180)]),this.geoActionConfigForm.get("range").setValidators([M.required,M.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([M.required])),t||o!==Ve.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([M.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoActionConfigComponent",yt),yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:yt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:yt,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:yt,decorators:[{type:o,args:[{selector:"tb-action-node-gps-geofencing-config",templateUrl:"./gps-geo-action-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class bt extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.injector=o,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.ngControl=this.injector.get(q),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(e){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const t=[];if(e)for(const o of Object.keys(e))Object.prototype.hasOwnProperty.call(e,o)&&t.push(this.fb.group({key:[o,[M.required]],value:[e[o],[M.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(t)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(e){this.kvListFormGroup.get("keyVals").removeAt(e)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[M.required]],value:["",[M.required]]}))}validate(e){return!this.kvListFormGroup.get("keyVals").value.length&&this.required?{kvMapRequired:!0}:this.kvListFormGroup.valid?null:{kvFieldsRequired:!0}}updateModel(){const e=this.kvListFormGroup.get("keyVals").value;if(this.required&&!e.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const t={};e.forEach((e=>{t[e.key]=e.value})),this.propagateChange(t)}}}e("KvMapConfigComponent",bt),bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:bt,deps:[{token:T.Store},{token:P.TranslateService},{token:t.Injector},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:bt,selector:"tb-kv-map-config",inputs:{disabled:"disabled",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:S,useExisting:n((()=>bt)),multi:!0},{provide:A,useExisting:n((()=>bt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#0000008a;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0;align-self:baseline}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:re.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{type:k.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{type:D.MatLabel,selector:"mat-label"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:se.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]}],pipes:{translate:P.TranslatePipe,async:O.AsyncPipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:bt,decorators:[{type:o,args:[{selector:"tb-kv-map-config",templateUrl:"./kv-map-config.component.html",styleUrls:["./kv-map-config.component.scss"],providers:[{provide:S,useExisting:n((()=>bt)),multi:!0},{provide:A,useExisting:n((()=>bt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:t.Injector},{type:k.FormBuilder}]},propDecorators:{disabled:[{type:l}],requiredText:[{type:l}],keyText:[{type:l}],keyRequiredText:[{type:l}],valText:[{type:l}],valRequiredText:[{type:l}],hintText:[{type:l}],required:[{type:l}]}});class ht extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=Xe,this.ToByteStandartCharsetTypeTranslationMap=Ze}configForm(){return this.kafkaConfigForm}onConfigurationSet(e){this.kafkaConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[M.required]],bootstrapServers:[e?e.bootstrapServers:null,[M.required]],retries:[e?e.retries:null,[M.min(0)]],batchSize:[e?e.batchSize:null,[M.min(0)]],linger:[e?e.linger:null,[M.min(0)]],bufferMemory:[e?e.bufferMemory:null,[M.min(0)]],acks:[e?e.acks:null,[M.required]],keySerializer:[e?e.keySerializer:null,[M.required]],valueSerializer:[e?e.valueSerializer:null,[M.required]],otherProperties:[e?e.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!e&&e.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[e?e.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(e){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([M.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:e})}}e("KafkaConfigComponent",ht),ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ht,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ht,selector:"tb-action-node-kafka-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ht,decorators:[{type:o,args:[{selector:"tb-action-node-kafka-config",templateUrl:"./kafka-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Ct extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.logConfigForm}onConfigurationSet(e){this.logConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[M.required]]})}testScript(){const e=this.logConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/log_node_script_fn").subscribe((e=>{e&&this.logConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("LogConfigComponent",Ct),Ct.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ct,deps:[{token:T.Store},{token:k.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),Ct.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ct,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:W.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ct,decorators:[{type:o,args:[{selector:"tb-action-node-log-config",templateUrl:"./log-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class Ft extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRquired=!0,this.allCredentialsTypes=Qe,this.credentialsTypeTranslationsMap=$e,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[M.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(me()).subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(e){for(const t of Object.keys(e)){const o=e[t];if(!o.firstChange&&o.currentValue!==o.previousValue&&o.currentValue&&"disableCertPemCredentials"===t){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}writeValue(e){$(e)&&(this.credentialsConfigFormGroup.reset(e,{emitEvent:!1}),this.updateValidators(!1))}setDisabledState(e){e?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())}updateView(){let e=this.credentialsConfigFormGroup.value;const t=e.type;switch(t){case"anonymous":e={type:t};break;case"basic":e={type:t,username:e.username,password:e.password};break;case"cert.PEM":delete e.username}this.propagateChange(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}validate(e){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(e=!1){const t=this.credentialsConfigFormGroup.get("type").value;switch(e&&this.credentialsConfigFormGroup.reset({type:t},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),t){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([M.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRquired?[M.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(M.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:e}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:e})}requiredFilesSelected(e,t=null){return o=>{t||(t=[Object.keys(o.controls)]);return(null==o?void 0:o.controls)&&t.some((t=>t.every((t=>!e(o.controls[t])))))?null:{notAllRequiredFilesSelected:!0}}}}e("CredentialsConfigComponent",Ft),Ft.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ft,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ft.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ft,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRquired:"passwordFieldRquired"},providers:[{provide:S,useExisting:n((()=>Ft)),multi:!0},{provide:A,useExisting:n((()=>Ft)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:t,template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',components:[{type:K.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:K.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:_.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:K.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:K.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:K.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{type:D.MatLabel,selector:"mat-label"},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:O.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:O.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ft,decorators:[{type:o,args:[{selector:"tb-credentials-config",templateUrl:"./credentials-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>Ft)),multi:!0},{provide:A,useExisting:n((()=>Ft)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]},propDecorators:{required:[{type:l}],disableCertPemCredentials:[{type:l}],passwordFieldRquired:[{type:l}]}});class Lt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.subscriptions=[]}configForm(){return this.mqttConfigForm}onConfigurationSet(e){this.mqttConfigForm=this.fb.group({topicPattern:[e?e.topicPattern:null,[M.required]],host:[e?e.host:null,[M.required]],port:[e?e.port:null,[M.required,M.min(1),M.max(65535)]],connectTimeoutSec:[e?e.connectTimeoutSec:null,[M.required,M.min(1),M.max(200)]],clientId:[e?e.clientId:null,[]],appendClientIdSuffix:[{value:!!e&&e.appendClientIdSuffix,disabled:!(e&&Y(e.clientId))},[]],cleanSession:[!!e&&e.cleanSession,[]],ssl:[!!e&&e.ssl,[]],credentials:[e?e.credentials:null,[]]}),this.subscriptions.push(this.mqttConfigForm.get("clientId").valueChanges.subscribe((e=>{Y(e)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1})})))}ngOnDestroy(){this.subscriptions.forEach((e=>e.unsubscribe()))}}e("MqttConfigComponent",Lt),Lt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Lt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Lt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Lt,selector:"tb-action-node-mqtt-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n
tb.rulenode.client-id-hint
\n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Ft,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Lt,decorators:[{type:o,args:[{selector:"tb-action-node-mqtt-config",templateUrl:"./mqtt-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class vt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgCountConfigForm}onConfigurationSet(e){this.msgCountConfigForm=this.fb.group({interval:[e?e.interval:null,[M.required,M.min(1)]],telemetryPrefix:[e?e.telemetryPrefix:null,[M.required]]})}}e("MsgCountConfigComponent",vt),vt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:vt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),vt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:vt,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:vt,decorators:[{type:o,args:[{selector:"tb-action-node-msg-count-config",templateUrl:"./msg-count-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class It extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.msgDelayConfigForm}onConfigurationSet(e){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!e&&e.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[e?e.periodInSeconds:null,[]],periodInSecondsPattern:[e?e.periodInSecondsPattern:null,[]],maxPendingMsgs:[e?e.maxPendingMsgs:null,[M.required,M.min(1),M.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(e){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([M.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([M.required,M.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:e}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:e})}}e("MsgDelayConfigComponent",It),It.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:It,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),It.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:It,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:It,decorators:[{type:o,args:[{selector:"tb-action-node-msg-delay-config",templateUrl:"./msg-delay-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Nt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.pubSubConfigForm}onConfigurationSet(e){this.pubSubConfigForm=this.fb.group({projectId:[e?e.projectId:null,[M.required]],topicName:[e?e.topicName:null,[M.required]],serviceAccountKey:[e?e.serviceAccountKey:null,[M.required]],serviceAccountKeyFileName:[e?e.serviceAccountKeyFileName:null,[M.required]],messageAttributes:[e?e.messageAttributes:null,[]]})}}e("PubSubConfigComponent",Nt),Nt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Nt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Nt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Nt,selector:"tb-action-node-pub-sub-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:_.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Nt,decorators:[{type:o,args:[{selector:"tb-action-node-pub-sub-config",templateUrl:"./pubsub-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Tt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(e){this.pushToCloudConfigForm=this.fb.group({scope:[e?e.scope:null,[M.required]]})}}e("PushToCloudConfigComponent",Tt),Tt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Tt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Tt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Tt,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Tt,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-cloud-config",templateUrl:"./push-to-cloud-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class kt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(e){this.pushToEdgeConfigForm=this.fb.group({scope:[e?e.scope:null,[M.required]]})}}e("PushToEdgeConfigComponent",kt),kt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:kt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),kt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:kt,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:t,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:kt,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-edge-config",templateUrl:"./push-to-edge-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Mt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(e){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[e?e.exchangeNamePattern:null,[]],routingKeyPattern:[e?e.routingKeyPattern:null,[]],messageProperties:[e?e.messageProperties:null,[]],host:[e?e.host:null,[M.required]],port:[e?e.port:null,[M.required,M.min(1),M.max(65535)]],virtualHost:[e?e.virtualHost:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]],automaticRecoveryEnabled:[!!e&&e.automaticRecoveryEnabled,[]],connectionTimeout:[e?e.connectionTimeout:null,[M.min(0)]],handshakeTimeout:[e?e.handshakeTimeout:null,[M.min(0)]],clientProperties:[e?e.clientProperties:null,[]]})}}e("RabbitMqConfigComponent",Mt),Mt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Mt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Mt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Mt,selector:"tb-action-node-rabbit-mq-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Mt,decorators:[{type:o,args:[{selector:"tb-action-node-rabbit-mq-config",templateUrl:"./rabbit-mq-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class qt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys(Je)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(e){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[e?e.restEndpointUrlPattern:null,[M.required]],requestMethod:[e?e.requestMethod:null,[M.required]],useSimpleClientHttpFactory:[!!e&&e.useSimpleClientHttpFactory,[]],ignoreRequestBody:[!!e&&e.ignoreRequestBody,[]],enableProxy:[!!e&&e.enableProxy,[]],useSystemProxyProperties:[!!e&&e.enableProxy,[]],proxyScheme:[e?e.proxyHost:null,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],readTimeoutMs:[e?e.readTimeoutMs:null,[]],maxParallelRequestsCount:[e?e.maxParallelRequestsCount:null,[M.min(0)]],headers:[e?e.headers:null,[]],useRedisQueueForMsgPersistence:[!!e&&e.useRedisQueueForMsgPersistence,[]],trimQueue:[!!e&&e.trimQueue,[]],maxQueueSize:[e?e.maxQueueSize:null,[]],credentials:[e?e.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(e){const t=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,o=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[M.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[M.required,M.min(1),M.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),t?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([M.min(0)])),o?this.restApiCallConfigForm.get("maxQueueSize").setValidators([M.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:e})}}e("RestApiCallConfigComponent",qt),qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:qt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:qt,selector:"tb-action-node-rest-api-call-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:Ft,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:qt,decorators:[{type:o,args:[{selector:"tb-action-node-rest-api-call-config",templateUrl:"./rest-api-call-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class St extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(e){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[e?e.requestIdMetaDataAttribute:null,[]]})}}e("RpcReplyConfigComponent",St),St.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:St,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),St.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:St,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:St,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-reply-config",templateUrl:"./rpc-reply-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class At extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(e){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[e?e.timeoutInSeconds:null,[M.required,M.min(0)]]})}}e("RpcRequestConfigComponent",At),At.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:At,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),At.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:At,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:At,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-request-config",templateUrl:"./rpc-request-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Gt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(e){this.saveToCustomTableConfigForm=this.fb.group({tableName:[e?e.tableName:null,[M.required,M.pattern(/.*\S.*/)]],fieldsMapping:[e?e.fieldsMapping:null,[M.required]]})}prepareOutputConfig(e){return e.tableName=e.tableName.trim(),e}}e("SaveToCustomTableConfigComponent",Gt),Gt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Gt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Gt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Gt,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Gt,decorators:[{type:o,args:[{selector:"tb-action-node-custom-table-config",templateUrl:"./save-to-custom-table-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Dt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(e){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!e&&e.useSystemSmtpSettings,[]],smtpProtocol:[e?e.smtpProtocol:null,[]],smtpHost:[e?e.smtpHost:null,[]],smtpPort:[e?e.smtpPort:null,[]],timeout:[e?e.timeout:null,[]],enableTls:[!!e&&e.enableTls,[]],tlsVersion:[e?e.tlsVersion:null,[]],enableProxy:[!!e&&e.enableProxy,[]],proxyHost:[e?e.proxyHost:null,[]],proxyPort:[e?e.proxyPort:null,[]],proxyUser:[e?e.proxyUser:null,[]],proxyPassword:[e?e.proxyPassword:null,[]],username:[e?e.username:null,[]],password:[e?e.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(e){const t=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,o=this.sendEmailConfigForm.get("enableProxy").value;t?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([M.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([M.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([M.required,M.min(1),M.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([M.required,M.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(o?[M.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(o?[M.required,M.min(1),M.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:e}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:e})}}e("SendEmailConfigComponent",Dt),Dt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Dt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Dt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Dt,selector:"tb-action-node-send-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ge.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{type:j.TogglePasswordComponent,selector:"tb-toggle-password"}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:D.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Dt,decorators:[{type:o,args:[{selector:"tb-action-node-send-email-config",templateUrl:"./send-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Et extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.sendSmsConfigForm}onConfigurationSet(e){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[e?e.numbersToTemplate:null,[M.required]],smsMessageTemplate:[e?e.smsMessageTemplate:null,[M.required]],useSystemSmsSettings:[!!e&&e.useSystemSmsSettings,[]],smsProviderConfiguration:[e?e.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(e){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([M.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:e})}}e("SendSmsConfigComponent",Et),Et.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Et,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Et.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Et,selector:"tb-action-node-send-sms-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:xe.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Et,decorators:[{type:o,args:[{selector:"tb-action-node-send-sms-config",templateUrl:"./send-sms-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Vt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.snsConfigForm}onConfigurationSet(e){this.snsConfigForm=this.fb.group({topicArnPattern:[e?e.topicArnPattern:null,[M.required]],accessKeyId:[e?e.accessKeyId:null,[M.required]],secretAccessKey:[e?e.secretAccessKey:null,[M.required]],region:[e?e.region:null,[M.required]]})}}e("SnsConfigComponent",Vt),Vt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Vt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Vt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Vt,selector:"tb-action-node-sns-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Vt,decorators:[{type:o,args:[{selector:"tb-action-node-sns-config",templateUrl:"./sns-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Pt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.sqsQueueType=_e,this.sqsQueueTypes=Object.keys(_e),this.sqsQueueTypeTranslationsMap=ze}configForm(){return this.sqsConfigForm}onConfigurationSet(e){this.sqsConfigForm=this.fb.group({queueType:[e?e.queueType:null,[M.required]],queueUrlPattern:[e?e.queueUrlPattern:null,[M.required]],delaySeconds:[e?e.delaySeconds:null,[M.min(0),M.max(900)]],messageAttributes:[e?e.messageAttributes:null,[]],accessKeyId:[e?e.accessKeyId:null,[M.required]],secretAccessKey:[e?e.secretAccessKey:null,[M.required]],region:[e?e.region:null,[M.required]]})}}e("SqsConfigComponent",Pt),Pt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Pt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Pt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Pt,selector:"tb-action-node-sqs-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Pt,decorators:[{type:o,args:[{selector:"tb-action-node-sqs-config",templateUrl:"./sqs-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Rt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.timeseriesConfigForm}onConfigurationSet(e){this.timeseriesConfigForm=this.fb.group({defaultTTL:[e?e.defaultTTL:null,[M.required,M.min(0)]],skipLatestPersistence:[!!e&&e.skipLatestPersistence,[]],useServerTs:[!!e&&e.useServerTs,[]]})}}e("TimeseriesConfigComponent",Rt),Rt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Rt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Rt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Rt,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Rt,decorators:[{type:o,args:[{selector:"tb-action-node-timeseries-config",templateUrl:"./timeseries-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Ot extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(e){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[e?e.customerNamePattern:null,[M.required,M.pattern(/.*\S.*/)]],customerCacheExpiration:[e?e.customerCacheExpiration:null,[M.required,M.min(0)]]})}prepareOutputConfig(e){return e.customerNamePattern=e.customerNamePattern.trim(),e}}e("UnassignCustomerConfigComponent",Ot),Ot.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ot,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ot.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ot,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ot,decorators:[{type:o,args:[{selector:"tb-action-node-un-assign-to-customer-config",templateUrl:"./unassign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class wt extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.injector=o,this.fb=r,this.searchText="",this.dirty=!1,this.mathOperation=[...tt.values()],this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(ue((e=>{let t;t="string"==typeof e&&et[e]?et[e]:null,this.updateView(t)})),pe((e=>(this.searchText=e||"",e?this._filter(e):this.mathOperation.slice()))))}_filter(e){const t=e.toLowerCase();return this.mathOperation.filter((e=>e.name.toLowerCase().includes(t)||e.value.toLowerCase().includes(t)))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.mathFunctionForm.disable({emitEvent:!1}):this.mathFunctionForm.enable({emitEvent:!1})}mathFunctionDisplayFn(e){if(e){const t=tt.get(e);return t.value+" | "+t.name}return""}writeValue(e){this.modelValue=e,this.mathFunctionForm.get("operation").setValue(e,{emitEvent:!1}),this.dirty=!0}updateView(e){this.modelValue!==e&&(this.modelValue=e,this.propagateChange(this.modelValue))}onFocus(){this.dirty&&(this.mathFunctionForm.get("operation").updateValueAndValidity({onlySelf:!0}),this.dirty=!1)}clear(){this.mathFunctionForm.get("operation").patchValue(""),setTimeout((()=>{this.operationInput.nativeElement.blur(),this.operationInput.nativeElement.focus()}),0)}}e("MathFunctionAutocompleteComponent",wt),wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:wt,deps:[{token:T.Store},{token:P.TranslateService},{token:t.Injector},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),wt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:wt,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:S,useExisting:n((()=>wt)),multi:!0}],viewQueries:[{propertyName:"operationInput",first:!0,predicate:["operationInput"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:ye.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:ye.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatSuffix,selector:"[matSuffix]"},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{async:O.AsyncPipe,highlight:be.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:wt,decorators:[{type:o,args:[{selector:"tb-math-function-autocomplete",templateUrl:"./math-function-autocomplete.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>wt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:t.Injector},{type:k.FormBuilder}]},propDecorators:{required:[{type:l}],disabled:[{type:l}],operationInput:[{type:a,args:["operationInput",{static:!0}]}]}});class Ht extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.injector=o,this.fb=r,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=tt,this.ArgumentType=ot,this.attributeScopeMap=st,this.argumentTypeResultMap=at,this.arguments=Object.values(ot),this.attributeScope=Object.values(lt),this.propagateChange=null,this.valueChangeSubscription=[]}get function(){return this.functionValue}set function(e){e&&this.functionValue!==e&&(this.functionValue=e,this.setupArgumentsFormGroup())}ngOnInit(){this.ngControl=this.injector.get(q),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.argumentsFormGroup=this.fb.group({}),this.argumentsFormGroup.addControl("arguments",this.fb.array([])),this.setupArgumentsFormGroup()}onDrop(e){const t=this.argumentsFormArray(),o=t.at(e.previousIndex);t.removeAt(e.previousIndex),t.insert(e.currentIndex,o),this.updateArgumentNames()}argumentsFormArray(){return this.argumentsFormGroup.get("arguments")}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.argumentsFormGroup.disable({emitEvent:!1}):this.argumentsFormGroup.enable({emitEvent:!1})}ngOnDestroy(){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((e=>e.unsubscribe()))}writeValue(e){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((e=>e.unsubscribe()));const t=[];e&&e.forEach(((e,o)=>{t.push(this.createArgumentControl(e,o))})),this.argumentsFormGroup.setControl("arguments",this.fb.array(t)),this.setupArgumentsFormGroup(),this.valueChangeSubscription.push(this.argumentsFormGroup.valueChanges.subscribe((()=>{this.updateModel()})))}removeArgument(e){this.argumentsFormGroup.get("arguments").removeAt(e),this.updateArgumentNames()}addArgument(){const e=this.argumentsFormGroup.get("arguments"),t=this.createArgumentControl(null,e.length);e.push(t)}validate(e){return this.argumentsFormGroup.valid?null:{argumentsRequired:!0}}setupArgumentsFormGroup(){if(this.function&&(this.maxArgs=this.mathFunctionMap.get(this.function).maxArgs,this.minArgs=this.mathFunctionMap.get(this.function).minArgs,this.displayArgumentName=this.function===et.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([M.minLength(this.minArgs),M.maxLength(this.maxArgs)]),this.argumentsFormGroup.get("arguments").value.length>this.maxArgs&&(this.argumentsFormGroup.get("arguments").controls.length=this.maxArgs);this.argumentsFormGroup.get("arguments").value.length{this.updateArgumentControlValidators(o),o.get("attributeScope").updateValueAndValidity({emitEvent:!0}),o.get("defaultValue").updateValueAndValidity({emitEvent:!0})}))),o}updateArgumentControlValidators(e){const t=e.get("type").value;t===ot.ATTRIBUTE?e.get("attributeScope").enable():e.get("attributeScope").disable(),t&&t!==ot.CONSTANT?e.get("defaultValue").enable():e.get("defaultValue").disable()}updateArgumentNames(){this.argumentsFormGroup.get("arguments").controls.forEach(((e,t)=>{e.get("name").setValue(nt[t])}))}updateModel(){const e=this.argumentsFormGroup.get("arguments").value;e.length&&this.argumentsFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}}e("ArgumentsMapConfigComponent",Ht),Ht.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ht,deps:[{token:T.Store},{token:P.TranslateService},{token:t.Injector},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ht.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ht,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:S,useExisting:n((()=>Ht)),multi:!0},{provide:A,useExisting:n((()=>Ht)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n
\n \n tb.rulenode.argument-type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.argument-type-field-input-required\n \n \n \n tb.rulenode.argument-key-field-input\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n
\n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
\n
\n \n
\n
\n
\n
\n
\n
\n tb.rulenode.no-arguments-prompt\n
\n
\n \n
\n
\n',styles:[":host mat-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:10px}\n"],components:[{type:he.MatList,selector:"mat-list, mat-action-list",inputs:["disableRipple","disabled"],exportAs:["matList"]},{type:he.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["disableRipple","disabled"],exportAs:["matListItem"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:O.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{type:re.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{type:Ce.CdkDropList,selector:"[cdkDropList], cdk-drop-list",inputs:["cdkDropListConnectedTo","id","cdkDropListEnterPredicate","cdkDropListSortPredicate","cdkDropListDisabled","cdkDropListSortingDisabled","cdkDropListAutoScrollDisabled","cdkDropListOrientation","cdkDropListLockAxis","cdkDropListData","cdkDropListAutoScrollStep"],outputs:["cdkDropListDropped","cdkDropListEntered","cdkDropListExited","cdkDropListSorted"],exportAs:["cdkDropList"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:k.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{type:Ce.CdkDrag,selector:"[cdkDrag]",inputs:["cdkDragDisabled","cdkDragStartDelay","cdkDragLockAxis","cdkDragConstrainPosition","cdkDragPreviewClass","cdkDragBoundary","cdkDragRootElement","cdkDragPreviewContainer","cdkDragData","cdkDragFreeDragPosition"],outputs:["cdkDragStarted","cdkDragReleased","cdkDragEnded","cdkDragEntered","cdkDragExited","cdkDragDropped","cdkDragMoved"],exportAs:["cdkDrag"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:Ce.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{type:se.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ht,decorators:[{type:o,args:[{selector:"tb-arguments-map-config",templateUrl:"./arguments-map-config.component.html",styleUrls:["./arguments-map-config.component.scss"],providers:[{provide:S,useExisting:n((()=>Ht)),multi:!0},{provide:A,useExisting:n((()=>Ht)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:t.Injector},{type:k.FormBuilder}]},propDecorators:{disabled:[{type:l}],function:[{type:l}]}});class Ut extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.MathFunction=et,this.ArgumentTypeResult=rt,this.argumentTypeResultMap=at,this.attributeScopeMap=st,this.argumentsResult=Object.values(rt),this.attributeScopeResult=Object.values(it)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(e){this.mathFunctionConfigForm=this.fb.group({operation:[e?e.operation:null,[M.required]],arguments:[e?e.arguments:null,[M.required]],customFunction:[e?e.customFunction:"",[M.required]],result:this.fb.group({type:[e?e.result.type:null,[M.required]],attributeScope:[e?e.result.attributeScope:null],key:[e?e.result.key:"",[M.required]],resultValuePrecision:[e?e.result.resultValuePrecision:0],addToBody:[!!e&&e.result.addToBody],addToMetadata:[!!e&&e.result.addToMetadata]})})}updateValidators(e){const t=this.mathFunctionConfigForm.get("operation").value,o=this.mathFunctionConfigForm.get("result").get("type").value;t===et.CUSTOM?this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),o===rt.ATTRIBUTE?this.mathFunctionConfigForm.get("result").get("attributeScope").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("result").get("attributeScope").disable({emitEvent:!1}),this.mathFunctionConfigForm.get("customFunction").updateValueAndValidity({emitEvent:e}),this.mathFunctionConfigForm.get("result").get("attributeScope").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["operation","result.type"]}}e("MathFunctionConfigComponent",Ut),Ut.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ut,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Ut.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ut,selector:"tb-action-node-math-function-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n tb.rulenode.argument-tile\n \n \n
\n
\n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n \n \n
\n
\n tb.rulenode.result-title\n
\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.type-field-input-required\n \n \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n \n tb.rulenode.key-field-input-required\n \n \n
\n
\n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
\n
\n
\n \n {{\'tb.rulenode.add-to-body-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
\n
\n
\n
\n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block;margin-top:16px}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"],components:[{type:wt,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{type:Ht,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{type:D.MatLabel,selector:"mat-label"},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:V.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Ut,decorators:[{type:o,args:[{selector:"tb-action-node-math-function-config",templateUrl:"./math-function-config.component.html",styleUrls:["./math-function-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Bt extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.entityType=x,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[M.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[M.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((e=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.deviceRelationsQueryFormGroup.reset(e,{emitEvent:!1})}}e("DeviceRelationsQueryConfigComponent",Bt),Bt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Bt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Bt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Bt,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:S,useExisting:n((()=>Bt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:Le.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]},{type:ve.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","disabled","entityType"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Bt,decorators:[{type:o,args:[{selector:"tb-device-relations-query-config",templateUrl:"./device-relations-query-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>Bt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class Kt extends y{constructor(e,t){super(e),this.store=e,this.fb=t,this.directionTypes=Object.keys(c),this.directionTypeTranslations=g,this.propagateChange=null}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[M.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((e=>{this.relationsQueryFormGroup.valid?this.propagateChange(e):this.propagateChange(null)}))}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}setDisabledState(e){this.disabled=e,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(e){this.relationsQueryFormGroup.reset(e||{},{emitEvent:!1})}}e("RelationsQueryConfigComponent",Kt),Kt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Kt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Kt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Kt,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:S,useExisting:n((()=>Kt)),multi:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:Ie.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Kt,decorators:[{type:o,args:[{selector:"tb-relations-query-config",templateUrl:"./relations-query-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>Kt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class jt extends y{constructor(e,t,o,r){super(e),this.store=e,this.translate=t,this.truncate=o,this.fb=r,this.placeholder="tb.rulenode.message-type",this.separatorKeysCodes=[X,Z,ee],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=e=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const e of Object.keys(b))this.messageTypesList.push({name:h.get(b[e]),value:e})}get required(){return this.requiredValue}set required(e){this.requiredValue=le(e)}registerOnChange(e){this.propagateChange=e}registerOnTouched(e){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(de(""),pe((e=>e||"")),fe((e=>this.fetchMessageTypes(e))),ce())}ngAfterViewInit(){}setDisabledState(e){this.disabled=e,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(e){this.searchText="",this.messageTypes.length=0,e&&e.forEach((e=>{const t=this.messageTypesList.find((t=>t.value===e));t?this.messageTypes.push({name:t.name,value:t.value}):this.messageTypes.push({name:e,value:e})}))}displayMessageTypeFn(e){return e?e.name:void 0}textIsNotEmpty(e){return!!(e&&null!=e&&e.length>0)}createMessageType(e,t){e.preventDefault(),this.transformMessageType(t)}add(e){this.transformMessageType(e.value)}fetchMessageTypes(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ne(this.messageTypesList.filter((t=>t.name.toUpperCase().includes(e))))}return Ne(this.messageTypesList)}transformMessageType(e){if((e||"").trim()){let t=null;const o=e.trim(),r=this.messageTypesList.find((e=>e.name===o));t=r?{name:r.name,value:r.value}:{name:o,value:o},t&&this.addMessageType(t)}this.clear("")}remove(e){const t=this.messageTypes.indexOf(e);t>=0&&(this.messageTypes.splice(t,1),this.updateModel())}selected(e){this.addMessageType(e.option.value),this.clear("")}addMessageType(e){-1===this.messageTypes.findIndex((t=>t.value===e.value))&&(this.messageTypes.push(e),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.messageTypeInput.nativeElement.value=e,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const e=this.messageTypes.map((e=>e.value));this.required?(this.chipList.errorState=!e.length,this.propagateChange(e.length>0?e:null)):(this.chipList.errorState=!1,this.propagateChange(e))}}e("MessageTypesConfigComponent",jt),jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:jt,deps:[{token:T.Store},{token:P.TranslateService},{token:C.TruncatePipe},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:jt,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:S,useExisting:n((()=>jt)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:ye.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:ye.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:ye.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,async:O.AsyncPipe,highlight:be.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:jt,decorators:[{type:o,args:[{selector:"tb-message-types-config",templateUrl:"./message-types-config.component.html",styleUrls:[],providers:[{provide:S,useExisting:n((()=>jt)),multi:!0}]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:C.TruncatePipe},{type:k.FormBuilder}]},propDecorators:{required:[{type:l}],label:[{type:l}],placeholder:[{type:l}],disabled:[{type:l}],chipList:[{type:a,args:["chipList",{static:!1}]}],matAutocomplete:[{type:a,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:a,args:["messageTypeInput",{static:!1}]}]}});class _t{}e("RulenodeCoreConfigCommonModule",_t),_t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:_t,deps:[],target:t.ɵɵFactoryTarget.NgModule}),_t.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:_t,declarations:[bt,Bt,Kt,jt,Ft,Se,Ht,wt],imports:[w,F,Fe],exports:[bt,Bt,Kt,jt,Ft,Se,Ht,wt]}),_t.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:_t,imports:[[w,F,Fe]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:_t,decorators:[{type:i,args:[{declarations:[bt,Bt,Kt,jt,Ft,Se,Ht,wt],imports:[w,F,Fe],exports:[bt,Bt,Kt,jt,Ft,Se,Ht,wt]}]}]});class zt{}e("RuleNodeCoreConfigActionModule",zt),zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:zt,deps:[],target:t.ɵɵFactoryTarget.NgModule}),zt.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:zt,declarations:[Ge,Rt,At,Ct,Ae,pt,dt,ft,It,ct,xt,yt,vt,St,Gt,Ot,Vt,Pt,Nt,ht,Lt,Mt,qt,Dt,ut,mt,gt,Et,kt,Tt,Ut],imports:[w,F,Fe,_t],exports:[Ge,Rt,At,Ct,Ae,pt,dt,ft,It,ct,xt,yt,vt,St,Gt,Ot,Vt,Pt,Nt,ht,Lt,Mt,qt,Dt,ut,mt,gt,Et,kt,Tt,Ut]}),zt.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:zt,imports:[[w,F,Fe,_t]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:zt,decorators:[{type:i,args:[{declarations:[Ge,Rt,At,Ct,Ae,pt,dt,ft,It,ct,xt,yt,vt,St,Gt,Ot,Vt,Pt,Nt,ht,Lt,Mt,qt,Dt,ut,mt,gt,Et,kt,Tt,Ut],imports:[w,F,Fe,_t],exports:[Ge,Rt,At,Ct,Ae,pt,dt,ft,It,ct,xt,yt,vt,St,Gt,Ot,Vt,Pt,Nt,ht,Lt,Mt,qt,Dt,ut,mt,gt,Et,kt,Tt,Ut]}]}]});class Qt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[X,Z,ee]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(e){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[e?e.inputValueKey:null,[M.required]],outputValueKey:[e?e.outputValueKey:null,[M.required]],useCache:[e?e.useCache:null,[]],addPeriodBetweenMsgs:[!!e&&e.addPeriodBetweenMsgs,[]],periodValueKey:[e?e.periodValueKey:null,[]],round:[e?e.round:null,[M.min(0),M.max(15)]],tellFailureIfDeltaIsNegative:[e?e.tellFailureIfDeltaIsNegative:null,[]]})}updateValidators(e){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([M.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:e})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}e("CalculateDeltaConfigComponent",Qt),Qt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Qt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Qt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Qt,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:t,template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Qt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-calculate-delta-config",templateUrl:"./calculate-delta-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class $t extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.customerAttributesConfigForm}onConfigurationSet(e){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[M.required]]})}}e("CustomerAttributesConfigComponent",$t),$t.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:$t,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),$t.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:$t,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:$t,decorators:[{type:o,args:[{selector:"tb-enrichment-node-customer-attributes-config",templateUrl:"./customer-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Yt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[X,Z,ee]}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(e){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[e?e.deviceRelationsQuery:null,[M.required]],tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})}removeKey(e,t){const o=this.deviceAttributesConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.deviceAttributesConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.deviceAttributesConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.deviceAttributesConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("DeviceAttributesConfigComponent",Yt),Yt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Yt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Yt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Yt,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:Bt,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatLabel,selector:"mat-label"},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Yt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-device-attributes-config",templateUrl:"./device-attributes-config.component.html",styleUrls:["./device-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Wt extends s{constructor(e,t,o){super(e),this.store=e,this.translate=t,this.fb=o,this.entityDetailsTranslationsMap=Be,this.entityDetailsList=[],this.searchText="",this.displayDetailsFn=this.displayDetails.bind(this);for(const e of Object.keys(Ue))this.entityDetailsList.push(Ue[e]);this.detailsFormControl=new G(""),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(de(""),pe((e=>e||"")),fe((e=>this.fetchEntityDetails(e))),ce())}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(e){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.entityDetailsConfigForm=this.fb.group({detailsList:[e?e.detailsList:null,[M.required]],addToMetadata:[!!e&&e.addToMetadata,[]]})}displayDetails(e){return e?this.translate.instant(Be.get(e)):void 0}fetchEntityDetails(e){if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ne(this.entityDetailsList.filter((t=>this.translate.instant(Be.get(Ue[t])).toUpperCase().includes(e))))}return Ne(this.entityDetailsList)}detailsFieldSelected(e){this.addDetailsField(e.option.value),this.clear("")}removeDetailsField(e){const t=this.entityDetailsConfigForm.get("detailsList").value;if(t){const o=t.indexOf(e);o>=0&&(t.splice(o,1),this.entityDetailsConfigForm.get("detailsList").setValue(t))}}addDetailsField(e){let t=this.entityDetailsConfigForm.get("detailsList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.entityDetailsConfigForm.get("detailsList").setValue(t))}onEntityDetailsInputFocus(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.detailsInput.nativeElement.value=e,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}}e("EntityDetailsConfigComponent",Wt),Wt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Wt,deps:[{token:T.Store},{token:P.TranslateService},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Wt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Wt,selector:"tb-enrichment-node-entity-details-config",viewQueries:[{propertyName:"detailsInput",first:!0,predicate:["detailsInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:ye.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:ye.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:ye.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe,async:O.AsyncPipe,highlight:be.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Wt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-entity-details-config",templateUrl:"./entity-details-config.component.html",styleUrls:["./entity-details-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:k.FormBuilder}]},propDecorators:{detailsInput:[{type:a,args:["detailsInput",{static:!1}]}]}});class Jt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[X,Z,ee],this.aggregationTypes=L,this.aggregations=Object.keys(L),this.aggregationTypesTranslations=v,this.fetchMode=Ke,this.fetchModes=Object.keys(Ke),this.samplingOrders=Object.keys(je),this.timeUnits=Object.values(Re),this.timeUnitsTranslationMap=Oe}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(e){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],aggregation:[e?e.aggregation:null,[M.required]],fetchMode:[e?e.fetchMode:null,[M.required]],orderBy:[e?e.orderBy:null,[]],limit:[e?e.limit:null,[]],useMetadataIntervalPatterns:[!!e&&e.useMetadataIntervalPatterns,[]],startInterval:[e?e.startInterval:null,[]],startIntervalTimeUnit:[e?e.startIntervalTimeUnit:null,[]],endInterval:[e?e.endInterval:null,[]],endIntervalTimeUnit:[e?e.endIntervalTimeUnit:null,[]],startIntervalPattern:[e?e.startIntervalPattern:null,[]],endIntervalPattern:[e?e.endIntervalPattern:null,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}updateValidators(e){const t=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,o=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;t&&t===Ke.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([M.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([M.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([M.required,M.min(2),M.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),o?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([M.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([M.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([M.required,M.min(1),M.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([M.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([M.required,M.min(1),M.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([M.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:e}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:e})}removeKey(e,t){const o=this.getTelemetryFromDatabaseConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.getTelemetryFromDatabaseConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.getTelemetryFromDatabaseConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("GetTelemetryFromDatabaseConfigComponent",Jt),Jt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Jt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Jt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Jt,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n aggregation.function\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:D.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Jt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",templateUrl:"./get-telemetry-from-database-config.component.html",styleUrls:["./get-telemetry-from-database-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Xt extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[X,Z,ee]}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(e){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!e&&e.tellFailureIfAbsent,[]],clientAttributeNames:[e?e.clientAttributeNames:null,[]],sharedAttributeNames:[e?e.sharedAttributeNames:null,[]],serverAttributeNames:[e?e.serverAttributeNames:null,[]],latestTsKeyNames:[e?e.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!e&&e.getLatestValueWithTs,[]]})}removeKey(e,t){const o=this.originatorAttributesConfigForm.get(t).value,r=o.indexOf(e);r>=0&&(o.splice(r,1),this.originatorAttributesConfigForm.get(t).setValue(o,{emitEvent:!0}))}addKey(e,t){const o=e.input;let r=e.value;if((r||"").trim()){r=r.trim();let e=this.originatorAttributesConfigForm.get(t).value;e&&-1!==e.indexOf(r)||(e||(e=[]),e.push(r),this.originatorAttributesConfigForm.get(t).setValue(e,{emitEvent:!0}))}o&&(o.value="")}}e("OriginatorAttributesConfigComponent",Xt),Xt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Xt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Xt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Xt,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Xt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-attributes-config",templateUrl:"./originator-attributes-config.component.html",styleUrls:["./originator-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Zt extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.originatorFieldsConfigForm}onConfigurationSet(e){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[e?e.fieldsMapping:null,[M.required]],ignoreNullStrings:[e?e.ignoreNullStrings:null]})}}e("OriginatorFieldsConfigComponent",Zt),Zt.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Zt,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),Zt.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Zt,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n {{ "tb.rulenode.ignore-null-strings" | translate }}\n
{{ "tb.rulenode.ignore-null-strings-hint" | translate }}
\n
\n',components:[{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Zt,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-fields-config",templateUrl:"./originator-fields-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class eo extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.relatedAttributesConfigForm}onConfigurationSet(e){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[e?e.relationsQuery:null,[M.required]],telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[M.required]]})}}e("RelatedAttributesConfigComponent",eo),eo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:eo,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),eo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:eo,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:Kt,selector:"tb-relations-query-config",inputs:["disabled","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:eo,decorators:[{type:o,args:[{selector:"tb-enrichment-node-related-attributes-config",templateUrl:"./related-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class to extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.tenantAttributesConfigForm}onConfigurationSet(e){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!e&&e.telemetry,[]],attrMapping:[e?e.attrMapping:null,[M.required]]})}}e("TenantAttributesConfigComponent",to),to.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:to,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),to.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:to,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:bt,selector:"tb-kv-map-config",inputs:["disabled","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:to,decorators:[{type:o,args:[{selector:"tb-enrichment-node-tenant-attributes-config",templateUrl:"./tenant-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class oo extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.fetchDeviceCredentialsConfigForm}onConfigurationSet(e){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchToMetadata:[e?e.fetchToMetadata:null,[]]})}}e("FetchDeviceCredentialsConfigComponent",oo),oo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:oo,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),oo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:oo,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:t,template:'
\n {{ \'tb.rulenode.fetch-credentials-to-metadata\' | translate }}\n
\n',components:[{type:Te.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex","name","id","labelPosition","aria-label","aria-labelledby","required","checked","aria-describedby"],outputs:["change","toggleChange"],exportAs:["matSlideToggle"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:oo,decorators:[{type:o,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",templateUrl:"./fetch-device-credentials-config.component.html"}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class ro{}e("RulenodeCoreConfigEnrichmentModule",ro),ro.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ro,deps:[],target:t.ɵɵFactoryTarget.NgModule}),ro.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ro,declarations:[$t,Wt,Yt,Xt,Zt,Jt,eo,to,Qt,oo],imports:[w,F,_t],exports:[$t,Wt,Yt,Xt,Zt,Jt,eo,to,Qt,oo]}),ro.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ro,imports:[[w,F,_t]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ro,decorators:[{type:i,args:[{declarations:[$t,Wt,Yt,Xt,Zt,Jt,eo,to,Qt,oo],imports:[w,F,_t],exports:[$t,Wt,Yt,Xt,Zt,Jt,eo,to,Qt,oo]}]}]});class ao extends s{constructor(e,t,o){super(e),this.store=e,this.translate=t,this.fb=o,this.alarmStatusTranslationsMap=I,this.alarmStatusList=[],this.searchText="",this.displayStatusFn=this.displayStatus.bind(this);for(const e of Object.keys(N))this.alarmStatusList.push(N[e]);this.statusFormControl=new G(""),this.filteredAlarmStatus=this.statusFormControl.valueChanges.pipe(de(""),pe((e=>e||"")),fe((e=>this.fetchAlarmStatus(e))),ce())}ngOnInit(){super.ngOnInit()}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(e){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),e}onConfigurationSet(e){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[e?e.alarmStatusList:null,[M.required]]})}displayStatus(e){return e?this.translate.instant(I.get(e)):void 0}fetchAlarmStatus(e){const t=this.getAlarmStatusList();if(this.searchText=e,this.searchText&&this.searchText.length){const e=this.searchText.toUpperCase();return Ne(t.filter((t=>this.translate.instant(I.get(N[t])).toUpperCase().includes(e))))}return Ne(t)}alarmStatusSelected(e){this.addAlarmStatus(e.option.value),this.clear("")}removeAlarmStatus(e){const t=this.alarmStatusConfigForm.get("alarmStatusList").value;if(t){const o=t.indexOf(e);o>=0&&(t.splice(o,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}}addAlarmStatus(e){let t=this.alarmStatusConfigForm.get("alarmStatusList").value;t||(t=[]);-1===t.indexOf(e)&&(t.push(e),this.alarmStatusConfigForm.get("alarmStatusList").setValue(t))}getAlarmStatusList(){return this.alarmStatusList.filter((e=>-1===this.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(e)))}onAlarmStatusInputFocus(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(e=""){this.alarmStatusInput.nativeElement.value=e,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.alarmStatusInput.nativeElement.blur(),this.alarmStatusInput.nativeElement.focus()}),0)}}e("CheckAlarmStatusComponent",ao),ao.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ao,deps:[{token:T.Store},{token:P.TranslateService},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ao.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ao,selector:"tb-filter-node-check-alarm-status-config",viewQueries:[{propertyName:"alarmStatusInput",first:!0,predicate:["alarmStatusInput"],descendants:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:ye.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ie.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:ye.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:ye.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe,async:O.AsyncPipe,highlight:be.HighlightPipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ao,decorators:[{type:o,args:[{selector:"tb-filter-node-check-alarm-status-config",templateUrl:"./check-alarm-status.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:P.TranslateService},{type:k.FormBuilder}]},propDecorators:{alarmStatusInput:[{type:a,args:["alarmStatusInput",{static:!1}]}]}});class no extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.separatorKeysCodes=[X,Z,ee]}configForm(){return this.checkMessageConfigForm}onConfigurationSet(e){this.checkMessageConfigForm=this.fb.group({messageNames:[e?e.messageNames:null,[]],metadataNames:[e?e.metadataNames:null,[]],checkAllKeys:[!!e&&e.checkAllKeys,[]]})}validateConfig(){const e=this.checkMessageConfigForm.get("messageNames").value,t=this.checkMessageConfigForm.get("metadataNames").value;return e.length>0||t.length>0}removeMessageName(e){const t=this.checkMessageConfigForm.get("messageNames").value,o=t.indexOf(e);o>=0&&(t.splice(o,1),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))}removeMetadataName(e){const t=this.checkMessageConfigForm.get("metadataNames").value,o=t.indexOf(e);o>=0&&(t.splice(o,1),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))}addMessageName(e){const t=e.input;let o=e.value;if((o||"").trim()){o=o.trim();let e=this.checkMessageConfigForm.get("messageNames").value;e&&-1!==e.indexOf(o)||(e||(e=[]),e.push(o),this.checkMessageConfigForm.get("messageNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}addMetadataName(e){const t=e.input;let o=e.value;if((o||"").trim()){o=o.trim();let e=this.checkMessageConfigForm.get("metadataNames").value;e&&-1!==e.indexOf(o)||(e||(e=[]),e.push(o),this.checkMessageConfigForm.get("metadataNames").setValue(e,{emitEvent:!0}))}t&&(t.value="")}}e("CheckMessageConfigComponent",no),no.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:no,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),no.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:no,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:te.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:oe.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:te.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:te.MatChipRemove,selector:"[matChipRemove]"},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:te.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:no,decorators:[{type:o,args:[{selector:"tb-filter-node-check-message-config",templateUrl:"./check-message-config.component.html",styleUrls:["./check-message-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class lo extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entitySearchDirection=Object.keys(c),this.entitySearchDirectionTranslationsMap=g}configForm(){return this.checkRelationConfigForm}onConfigurationSet(e){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!e&&e.checkForSingleEntity,[]],direction:[e?e.direction:null,[]],entityType:[e?e.entityType:null,e&&e.checkForSingleEntity?[M.required]:[]],entityId:[e?e.entityId:null,e&&e.checkForSingleEntity?[M.required]:[]],relationType:[e?e.relationType:null,[M.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(e){const t=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(t?[M.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.checkRelationConfigForm.get("entityId").setValidators(t?[M.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:e})}}e("CheckRelationConfigComponent",lo),lo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:lo,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),lo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:lo,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:t,template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{type:ke.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]},{type:Le.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:D.MatLabel,selector:"mat-label"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:lo,decorators:[{type:o,args:[{selector:"tb-filter-node-check-relation-config",templateUrl:"./check-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class io extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.perimeterType=Ve,this.perimeterTypes=Object.keys(Ve),this.perimeterTypeTranslationMap=Pe,this.rangeUnits=Object.keys(we),this.rangeUnitTranslationMap=He}configForm(){return this.geoFilterConfigForm}onConfigurationSet(e){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[e?e.latitudeKeyName:null,[M.required]],longitudeKeyName:[e?e.longitudeKeyName:null,[M.required]],perimeterType:[e?e.perimeterType:null,[M.required]],fetchPerimeterInfoFromMessageMetadata:[!!e&&e.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[e?e.perimeterKeyName:null,[]],centerLatitude:[e?e.centerLatitude:null,[]],centerLongitude:[e?e.centerLatitude:null,[]],range:[e?e.range:null,[]],rangeUnit:[e?e.rangeUnit:null,[]],polygonsDefinition:[e?e.polygonsDefinition:null,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(e){const t=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoFilterConfigForm.get("perimeterType").value;t?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([M.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),t||o!==Ve.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([M.required,M.min(-90),M.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([M.required,M.min(-180),M.max(180)]),this.geoFilterConfigForm.get("range").setValidators([M.required,M.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([M.required])),t||o!==Ve.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([M.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:e}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:e})}}e("GpsGeoFilterConfigComponent",io),io.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:io,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),io.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:io,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:k.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:k.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:k.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:io,decorators:[{type:o,args:[{selector:"tb-filter-node-gps-geofencing-config",templateUrl:"./gps-geo-filter-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class so extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.messageTypeConfigForm}onConfigurationSet(e){this.messageTypeConfigForm=this.fb.group({messageTypes:[e?e.messageTypes:null,[M.required]]})}}e("MessageTypeConfigComponent",so),so.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:so,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),so.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:so,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n
\n',components:[{type:jt,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:so,decorators:[{type:o,args:[{selector:"tb-filter-node-message-type-config",templateUrl:"./message-type-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class mo extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.allowedEntityTypes=[x.DEVICE,x.ASSET,x.ENTITY_VIEW,x.TENANT,x.CUSTOMER,x.USER,x.DASHBOARD,x.RULE_CHAIN,x.RULE_NODE]}configForm(){return this.originatorTypeConfigForm}onConfigurationSet(e){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[e?e.originatorTypes:null,[M.required]]})}}e("OriginatorTypeConfigComponent",mo),mo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:mo,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),mo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:mo,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}\n"],components:[{type:Me.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","disabled","allowedEntityTypes","ignoreAuthorityFilter"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:mo,decorators:[{type:o,args:[{selector:"tb-filter-node-originator-type-config",templateUrl:"./originator-type-config.component.html",styleUrls:["./originator-type-config.component.scss"]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class uo extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[M.required]]})}testScript(){const e=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/filter_node_script_fn").subscribe((e=>{e&&this.scriptConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("ScriptConfigComponent",uo),uo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:uo,deps:[{token:T.Store},{token:k.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),uo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:uo,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:W.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:uo,decorators:[{type:o,args:[{selector:"tb-filter-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class po extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.switchConfigForm}onConfigurationSet(e){this.switchConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[M.required]]})}testScript(){const e=this.switchConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/switch_node_script_fn").subscribe((e=>{e&&this.switchConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("SwitchConfigComponent",po),po.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:po,deps:[{token:T.Store},{token:k.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),po.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:po,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:W.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:po,decorators:[{type:o,args:[{selector:"tb-filter-node-switch-config",templateUrl:"./switch-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class fo{}e("RuleNodeCoreConfigFilterModule",fo),fo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:fo,deps:[],target:t.ɵɵFactoryTarget.NgModule}),fo.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:fo,declarations:[no,lo,io,so,mo,uo,po,ao],imports:[w,F,_t],exports:[no,lo,io,so,mo,uo,po,ao]}),fo.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:fo,imports:[[w,F,_t]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:fo,decorators:[{type:i,args:[{declarations:[no,lo,io,so,mo,uo,po,ao],imports:[w,F,_t],exports:[no,lo,io,so,mo,uo,po,ao]}]}]});class co extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.originatorSource=De,this.originatorSources=Object.keys(De),this.originatorSourceTranslationMap=Ee}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(e){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[e?e.originatorSource:null,[M.required]],entityType:[e?e.entityType:null,[]],entityNamePattern:[e?e.entityNamePattern:null,[]],relationsQuery:[e?e.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(e){const t=this.changeOriginatorConfigForm.get("originatorSource").value;t===De.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([M.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),t===De.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([M.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([M.required,M.pattern(/.*\S.*/)])):(this.changeOriginatorConfigForm.get("entityType").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").patchValue(null,{emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").setValidators([]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([])),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityType").updateValueAndValidity({emitEvent:e}),this.changeOriginatorConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:e})}}e("ChangeOriginatorConfigComponent",co),co.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:co,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),co.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:co,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n
\n \n \n \n
\n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:ae.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{type:Kt,selector:"tb-relations-query-config",inputs:["disabled","required"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:co,decorators:[{type:o,args:[{selector:"tb-transformation-node-change-originator-config",templateUrl:"./change-originator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class go extends s{constructor(e,t,o,r){super(e),this.store=e,this.fb=t,this.nodeScriptTestService=o,this.translate=r}configForm(){return this.scriptConfigForm}onConfigurationSet(e){this.scriptConfigForm=this.fb.group({jsScript:[e?e.jsScript:null,[M.required]]})}testScript(){const e=this.scriptConfigForm.get("jsScript").value;this.nodeScriptTestService.testNodeScript(e,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/transformation_node_script_fn").subscribe((e=>{e&&this.scriptConfigForm.get("jsScript").setValue(e)}))}onValidate(){this.jsFuncComponent.validateOnSubmit()}}e("TransformScriptConfigComponent",go),go.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:go,deps:[{token:T.Store},{token:k.FormBuilder},{token:Q.NodeScriptTestService},{token:P.TranslateService}],target:t.ɵɵFactoryTarget.Component}),go.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:go,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:t,template:'
\n \n \n \n
\n \n
\n
\n',components:[{type:W.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:J.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:go,decorators:[{type:o,args:[{selector:"tb-transformation-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder},{type:Q.NodeScriptTestService},{type:P.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!0}]}]}});class xo extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",value:"false"},{name:"tb.mail-body-type.html",value:"true"},{name:"tb.mail-body-type.dynamic",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(e){this.toEmailConfigForm=this.fb.group({fromTemplate:[e?e.fromTemplate:null,[M.required]],toTemplate:[e?e.toTemplate:null,[M.required]],ccTemplate:[e?e.ccTemplate:null,[]],bccTemplate:[e?e.bccTemplate:null,[]],subjectTemplate:[e?e.subjectTemplate:null,[M.required]],mailBodyType:[e?e.mailBodyType:null],isHtmlTemplate:[e?e.isHtmlTemplate:null],bodyTemplate:[e?e.bodyTemplate:null,[M.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.pipe(de([null==e?void 0:e.subjectTemplate])).subscribe((e=>{"dynamic"===e?(this.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").setValidators(M.required)):this.toEmailConfigForm.get("isHtmlTemplate").clearValidators(),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity()}))}}e("ToEmailConfigComponent",xo),xo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:xo,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),xo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:xo,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:t,template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n',components:[{type:D.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:B.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:D.MatLabel,selector:"mat-label"},{type:P.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:R.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:k.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:O.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:D.MatError,selector:"mat-error",inputs:["id"]},{type:D.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:O.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:P.TranslatePipe,safeHtml:Se}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:xo,decorators:[{type:o,args:[{selector:"tb-transformation-node-to-email-config",templateUrl:"./to-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class yo{}e("RulenodeCoreConfigTransformModule",yo),yo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:yo,deps:[],target:t.ɵɵFactoryTarget.NgModule}),yo.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:yo,declarations:[co,go,xo],imports:[w,F,_t],exports:[co,go,xo]}),yo.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:yo,imports:[[w,F,_t]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:yo,decorators:[{type:i,args:[{declarations:[co,go,xo],imports:[w,F,_t],exports:[co,go,xo]}]}]});class bo extends s{constructor(e,t){super(e),this.store=e,this.fb=t,this.entityType=x}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(e){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[e?e.ruleChainId:null,[M.required]]})}}e("RuleChainInputComponent",bo),bo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:bo,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),bo.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:bo,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:t,template:'
\n \n \n
\n',components:[{type:ke.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]}],directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:k.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:k.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:k.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:bo,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-input-config",templateUrl:"./rule-chain-input.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class ho extends s{constructor(e,t){super(e),this.store=e,this.fb=t}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(e){this.ruleChainOutputConfigForm=this.fb.group({})}}e("RuleChainOutputComponent",ho),ho.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ho,deps:[{token:T.Store},{token:k.FormBuilder}],target:t.ɵɵFactoryTarget.Component}),ho.ɵcmp=t.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ho,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:t,template:'
\n
\n
\n',directives:[{type:V.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:k.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:k.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]}],pipes:{translate:P.TranslatePipe}}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:ho,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-output-config",templateUrl:"./rule-chain-output.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:T.Store},{type:k.FormBuilder}]}});class Co{}e("RuleNodeCoreConfigFlowModule",Co),Co.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Co,deps:[],target:t.ɵɵFactoryTarget.NgModule}),Co.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Co,declarations:[bo,ho],imports:[w,F,_t],exports:[bo,ho]}),Co.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Co,imports:[[w,F,_t]]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Co,decorators:[{type:i,args:[{declarations:[bo,ho],imports:[w,F,_t],exports:[bo,ho]}]}]});class Fo{constructor(e){!function(e){e.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","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","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","notify-device":"Notify Device","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","timeseries-key":"Timeseries key","data-keys":"Message data","metadata-keys":"Message metadata","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","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","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity","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.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern 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","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",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","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","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","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',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",topic:"Topic","topic-required":"Topic is required","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","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","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","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"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","client-id-hint":'Hint: Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Hint: Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","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","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client 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","use-metadata-interval-patterns":"Use interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","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","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","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","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-key-name":"Perimeter key name","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","ignore-null-strings":"Ignore null strings","ignore-null-strings-hint":"If selected rule node will ignore entity fields with empty value.","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"alarm-severity-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body to substitute "Source" and "Target" key names',"shared-scope":"Shared scope","server-scope":"Server scope","client-scope":"Client scope","attribute-type":"Attribute","constant-type":"Constant","time-series-type":"Time series","message-body-type":"Message body","message-metadata-type":"Message metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-type-field-input":"Type","argument-type-field-input-required":"Argument type is required.","argument-key-field-input":"Key","argument-key-field-input-required":"Argument key is required.","constant-value-field-input":"Constant value","constant-value-field-input-required":"Constant value is required.","attribute-scope-field-input":"Attribute scope","attribute-scope-field-input-required":"Attribute scope os required.","default-value-field-input":"Default value","type-field-input":"Type","type-field-input-required":"Type is required.","key-field-input":"Key","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Hint: use 0 to convert result to integer","add-to-body-field-input":"Add to message body","add-to-metadata-field-input":"Add to message metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Hint: specify a mathematical expression to evaluate. For example, transform Fahrenheit to Celsius using (x - 32) / 1.8)"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry"},"mail-body-type":{"plain-text":"Plain Text",html:"HTML",dynamic:"Dynamic"}}},!0)}(e)}}e("RuleNodeCoreConfigModule",Fo),Fo.ɵfac=t.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Fo,deps:[{token:P.TranslateService}],target:t.ɵɵFactoryTarget.NgModule}),Fo.ɵmod=t.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Fo,declarations:[qe],imports:[w,F],exports:[zt,fo,ro,yo,Co,qe]}),Fo.ɵinj=t.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Fo,imports:[[w,F],zt,fo,ro,yo,Co]}),t.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:t,type:Fo,decorators:[{type:i,args:[{declarations:[qe],imports:[w,F],exports:[zt,fo,ro,yo,Co,qe]}]}],ctorParameters:function(){return[{type:P.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map +System.register(["@angular/core","@shared/public-api","@ngrx/store","@angular/forms","@angular/material/form-field","@angular/material/checkbox","@angular/flex-layout/flex","@ngx-translate/core","@angular/material/input","@angular/common","@angular/platform-browser","@angular/material/select","@angular/material/core","@angular/material/expansion","@shared/components/button/toggle-password.component","@shared/components/file-input.component","@shared/components/queue/queue-autocomplete.component","@core/public-api","@shared/components/script-lang.component","@shared/components/js-func.component","@angular/material/button","@angular/cdk/keycodes","@angular/material/chips","@angular/material/icon","@shared/components/entity/entity-type-select.component","@shared/components/entity/entity-select.component","@angular/cdk/coercion","@shared/components/tb-error.component","@angular/flex-layout/extended","@angular/material/tooltip","rxjs/operators","@shared/components/tb-checkbox.component","@home/components/sms/sms-provider-configuration.component","@angular/material/autocomplete","@shared/pipe/highlight.pipe","@angular/material/list","@angular/cdk/drag-drop","@home/components/public-api","@shared/components/relation/relation-type-autocomplete.component","@shared/components/entity/entity-subtype-list.component","@home/components/relation/relation-filters.component","rxjs","@angular/material/radio","@angular/material/slide-toggle","@shared/components/entity/entity-autocomplete.component","@shared/components/entity/entity-type-list.component"],(function(t){"use strict";var e,o,r,a,n,l,i,s,m,u,p,d,f,c,g,x,y,b,h,C,F,L,v,I,N,T,k,M,q,S,A,G,D,V,E,P,R,O,w,H,U,B,K,j,_,z,J,Q,$,W,Y,X,Z,tt,et,ot,rt,at,nt,lt,it,st,mt,ut,pt,dt,ft,ct,gt,xt,yt,bt,ht,Ct,Ft,Lt,vt,It,Nt,Tt,kt,Mt,qt,St,At,Gt;return{setters:[function(t){e=t,o=t.Component,r=t.Pipe,a=t.ViewChild,n=t.forwardRef,l=t.Input,i=t.NgModule},function(t){s=t.RuleNodeConfigurationComponent,m=t.AttributeScope,u=t.telemetryTypeTranslations,p=t.ServiceType,d=t.ScriptLanguage,f=t.AlarmSeverity,c=t.alarmSeverityTranslations,g=t.EntitySearchDirection,x=t.entitySearchDirectionTranslations,y=t.EntityType,b=t.PageComponent,h=t.MessageType,C=t.messageTypeNames,F=t,L=t.SharedModule,v=t.AggregationType,I=t.aggregationTranslations,N=t.alarmStatusTranslations,T=t.AlarmStatus},function(t){k=t},function(t){M=t,q=t.Validators,S=t.NgControl,A=t.NG_VALUE_ACCESSOR,G=t.NG_VALIDATORS,D=t.FormControl},function(t){V=t},function(t){E=t},function(t){P=t},function(t){R=t},function(t){O=t},function(t){w=t,H=t.CommonModule},function(t){U=t},function(t){B=t},function(t){K=t},function(t){j=t},function(t){_=t},function(t){z=t},function(t){J=t},function(t){Q=t.getCurrentAuthState,$=t,W=t.isDefinedAndNotNull,Y=t.isNotEmptyStr},function(t){X=t},function(t){Z=t},function(t){tt=t},function(t){et=t.ENTER,ot=t.COMMA,rt=t.SEMICOLON},function(t){at=t},function(t){nt=t},function(t){lt=t},function(t){it=t},function(t){st=t.coerceBooleanProperty},function(t){mt=t},function(t){ut=t},function(t){pt=t},function(t){dt=t.distinctUntilChanged,ft=t.tap,ct=t.map,gt=t.startWith,xt=t.mergeMap,yt=t.share},function(t){bt=t},function(t){ht=t},function(t){Ct=t},function(t){Ft=t},function(t){Lt=t},function(t){vt=t},function(t){It=t.HomeComponentsModule},function(t){Nt=t},function(t){Tt=t},function(t){kt=t},function(t){Mt=t.of},function(t){qt=t},function(t){St=t},function(t){At=t},function(t){Gt=t}],execute:function(){class Dt extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.emptyConfigForm}onConfigurationSet(t){this.emptyConfigForm=this.fb.group({})}}t("EmptyConfigComponent",Dt),Dt.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Dt,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Dt.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Dt,selector:"tb-node-empty-config",usesInheritance:!0,ngImport:e,template:"
",isInline:!0}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Dt,decorators:[{type:o,args:[{selector:"tb-node-empty-config",template:"
",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Vt{constructor(t){this.sanitizer=t}transform(t){return this.sanitizer.bypassSecurityTrustHtml(t)}}t("SafeHtmlPipe",Vt),Vt.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Vt,deps:[{token:U.DomSanitizer}],target:e.ɵɵFactoryTarget.Pipe}),Vt.ɵpipe=e.ɵɵngDeclarePipe({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Vt,name:"safeHtml"}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Vt,decorators:[{type:r,args:[{name:"safeHtml"}]}],ctorParameters:function(){return[{type:U.DomSanitizer}]}});class Et extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.assignCustomerConfigForm}onConfigurationSet(t){this.assignCustomerConfigForm=this.fb.group({customerNamePattern:[t?t.customerNamePattern:null,[q.required,q.pattern(/.*\S.*/)]],createCustomerIfNotExists:[!!t&&t.createCustomerIfNotExists,[]],customerCacheExpiration:[t?t.customerCacheExpiration:null,[q.required,q.min(0)]]})}prepareOutputConfig(t){return t.customerNamePattern=t.customerNamePattern.trim(),t}}t("AssignCustomerConfigComponent",Et),Et.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Et,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Et.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Et,selector:"tb-action-node-assign-to-customer-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.create-customer-if-not-exists\' | translate }}\n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Et,decorators:[{type:o,args:[{selector:"tb-action-node-assign-to-customer-config",templateUrl:"./assign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Pt extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.attributesConfigForm}onConfigurationSet(t){this.attributesConfigForm=this.fb.group({scope:[t?t.scope:null,[q.required]],notifyDevice:[!t||t.notifyDevice,[]]})}}var Rt;t("AttributesConfigComponent",Pt),Pt.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Pt,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Pt.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Pt,selector:"tb-action-node-attributes-config",usesInheritance:!0,ngImport:e,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-hint
\n
\n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Pt,decorators:[{type:o,args:[{selector:"tb-action-node-attributes-config",templateUrl:"./attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}}),function(t){t.CUSTOMER="CUSTOMER",t.TENANT="TENANT",t.RELATED="RELATED",t.ALARM_ORIGINATOR="ALARM_ORIGINATOR",t.ENTITY="ENTITY"}(Rt||(Rt={}));const Ot=new Map([[Rt.CUSTOMER,"tb.rulenode.originator-customer"],[Rt.TENANT,"tb.rulenode.originator-tenant"],[Rt.RELATED,"tb.rulenode.originator-related"],[Rt.ALARM_ORIGINATOR,"tb.rulenode.originator-alarm-originator"],[Rt.ENTITY,"tb.rulenode.originator-entity"]]);var wt;!function(t){t.CIRCLE="CIRCLE",t.POLYGON="POLYGON"}(wt||(wt={}));const Ht=new Map([[wt.CIRCLE,"tb.rulenode.perimeter-circle"],[wt.POLYGON,"tb.rulenode.perimeter-polygon"]]);var Ut;!function(t){t.MILLISECONDS="MILLISECONDS",t.SECONDS="SECONDS",t.MINUTES="MINUTES",t.HOURS="HOURS",t.DAYS="DAYS"}(Ut||(Ut={}));const Bt=new Map([[Ut.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[Ut.SECONDS,"tb.rulenode.time-unit-seconds"],[Ut.MINUTES,"tb.rulenode.time-unit-minutes"],[Ut.HOURS,"tb.rulenode.time-unit-hours"],[Ut.DAYS,"tb.rulenode.time-unit-days"]]);var Kt;!function(t){t.METER="METER",t.KILOMETER="KILOMETER",t.FOOT="FOOT",t.MILE="MILE",t.NAUTICAL_MILE="NAUTICAL_MILE"}(Kt||(Kt={}));const jt=new Map([[Kt.METER,"tb.rulenode.range-unit-meter"],[Kt.KILOMETER,"tb.rulenode.range-unit-kilometer"],[Kt.FOOT,"tb.rulenode.range-unit-foot"],[Kt.MILE,"tb.rulenode.range-unit-mile"],[Kt.NAUTICAL_MILE,"tb.rulenode.range-unit-nautical-mile"]]);var _t;!function(t){t.ID="ID",t.TITLE="TITLE",t.COUNTRY="COUNTRY",t.STATE="STATE",t.CITY="CITY",t.ZIP="ZIP",t.ADDRESS="ADDRESS",t.ADDRESS2="ADDRESS2",t.PHONE="PHONE",t.EMAIL="EMAIL",t.ADDITIONAL_INFO="ADDITIONAL_INFO"}(_t||(_t={}));const zt=new Map([[_t.TITLE,"tb.rulenode.entity-details-title"],[_t.COUNTRY,"tb.rulenode.entity-details-country"],[_t.STATE,"tb.rulenode.entity-details-state"],[_t.CITY,"tb.rulenode.entity-details-city"],[_t.ZIP,"tb.rulenode.entity-details-zip"],[_t.ADDRESS,"tb.rulenode.entity-details-address"],[_t.ADDRESS2,"tb.rulenode.entity-details-address2"],[_t.PHONE,"tb.rulenode.entity-details-phone"],[_t.EMAIL,"tb.rulenode.entity-details-email"],[_t.ADDITIONAL_INFO,"tb.rulenode.entity-details-additional_info"]]);var Jt,Qt,$t;!function(t){t.FIRST="FIRST",t.LAST="LAST",t.ALL="ALL"}(Jt||(Jt={})),function(t){t.ASC="ASC",t.DESC="DESC"}(Qt||(Qt={})),function(t){t.STANDARD="STANDARD",t.FIFO="FIFO"}($t||($t={}));const Wt=new Map([[$t.STANDARD,"tb.rulenode.sqs-queue-standard"],[$t.FIFO,"tb.rulenode.sqs-queue-fifo"]]),Yt=["anonymous","basic","cert.PEM"],Xt=new Map([["anonymous","tb.rulenode.credentials-anonymous"],["basic","tb.rulenode.credentials-basic"],["cert.PEM","tb.rulenode.credentials-pem"]]),Zt=["sas","cert.PEM"],te=new Map([["sas","tb.rulenode.credentials-sas"],["cert.PEM","tb.rulenode.credentials-pem"]]);var ee;!function(t){t.GET="GET",t.POST="POST",t.PUT="PUT",t.DELETE="DELETE"}(ee||(ee={}));const oe=["US-ASCII","ISO-8859-1","UTF-8","UTF-16BE","UTF-16LE","UTF-16"],re=new Map([["US-ASCII","tb.rulenode.charset-us-ascii"],["ISO-8859-1","tb.rulenode.charset-iso-8859-1"],["UTF-8","tb.rulenode.charset-utf-8"],["UTF-16BE","tb.rulenode.charset-utf-16be"],["UTF-16LE","tb.rulenode.charset-utf-16le"],["UTF-16","tb.rulenode.charset-utf-16"]]);var ae;!function(t){t.CUSTOM="CUSTOM",t.ADD="ADD",t.SUB="SUB",t.MULT="MULT",t.DIV="DIV",t.SIN="SIN",t.SINH="SINH",t.COS="COS",t.COSH="COSH",t.TAN="TAN",t.TANH="TANH",t.ACOS="ACOS",t.ASIN="ASIN",t.ATAN="ATAN",t.ATAN2="ATAN2",t.EXP="EXP",t.EXPM1="EXPM1",t.SQRT="SQRT",t.CBRT="CBRT",t.GET_EXP="GET_EXP",t.HYPOT="HYPOT",t.LOG="LOG",t.LOG10="LOG10",t.LOG1P="LOG1P",t.CEIL="CEIL",t.FLOOR="FLOOR",t.FLOOR_DIV="FLOOR_DIV",t.FLOOR_MOD="FLOOR_MOD",t.ABS="ABS",t.MIN="MIN",t.MAX="MAX",t.POW="POW",t.SIGNUM="SIGNUM",t.RAD="RAD",t.DEG="DEG"}(ae||(ae={}));const ne=new Map([[ae.CUSTOM,{value:ae.CUSTOM,name:"Custom Function",description:"Use this function to specify complex mathematical expression.",minArgs:1,maxArgs:16}],[ae.ADD,{value:ae.ADD,name:"Addition",description:"x + y",minArgs:2,maxArgs:2}],[ae.SUB,{value:ae.SUB,name:"Subtraction",description:"x - y",minArgs:2,maxArgs:2}],[ae.MULT,{value:ae.MULT,name:"Multiplication",description:"x * y",minArgs:2,maxArgs:2}],[ae.DIV,{value:ae.DIV,name:"Division",description:"x / y",minArgs:2,maxArgs:2}],[ae.SIN,{value:ae.SIN,name:"Sine",description:"Returns the trigonometric sine of an angle in radians.",minArgs:1,maxArgs:1}],[ae.SINH,{value:ae.SINH,name:"Hyperbolic sine",description:"Returns the hyperbolic sine of an argument.",minArgs:1,maxArgs:1}],[ae.COS,{value:ae.COS,name:"Cosine",description:"Returns the trigonometric cosine of an angle in radians.",minArgs:1,maxArgs:1}],[ae.COSH,{value:ae.COSH,name:"Hyperbolic cosine",description:"Returns the hyperbolic cosine of an argument.",minArgs:1,maxArgs:1}],[ae.TAN,{value:ae.TAN,name:"Tangent",description:"Returns the trigonometric tangent of an angle in radians",minArgs:1,maxArgs:1}],[ae.TANH,{value:ae.TANH,name:"Hyperbolic tangent",description:"Returns the hyperbolic tangent of an argument",minArgs:1,maxArgs:1}],[ae.ACOS,{value:ae.ACOS,name:"Arc cosine",description:"Returns the arc cosine of an argument",minArgs:1,maxArgs:1}],[ae.ASIN,{value:ae.ASIN,name:"Arc sine",description:"Returns the arc sine of an argument",minArgs:1,maxArgs:1}],[ae.ATAN,{value:ae.ATAN,name:"Arc tangent",description:"Returns the arc tangent of an argument",minArgs:1,maxArgs:1}],[ae.ATAN2,{value:ae.ATAN2,name:"2-argument arc tangent",description:"Returns the angle theta from the conversion of rectangular coordinates (x, y) to polar coordinates (r, theta)",minArgs:2,maxArgs:2}],[ae.EXP,{value:ae.EXP,name:"Exponential",description:"Returns Euler's number e raised to the power of an argument",minArgs:1,maxArgs:1}],[ae.EXPM1,{value:ae.EXPM1,name:"Exponential minus one",description:"Returns Euler's number e raised to the power of an argument minus one",minArgs:1,maxArgs:1}],[ae.SQRT,{value:ae.SQRT,name:"Square",description:"Returns the correctly rounded positive square root of an argument",minArgs:1,maxArgs:1}],[ae.CBRT,{value:ae.CBRT,name:"Cube root",description:"Returns the cube root of an argument",minArgs:1,maxArgs:1}],[ae.GET_EXP,{value:ae.GET_EXP,name:"Get exponent",description:"Returns the unbiased exponent used in the representation of an argument",minArgs:1,maxArgs:1}],[ae.HYPOT,{value:ae.HYPOT,name:"Square root",description:"Returns the square root of the squares of the arguments",minArgs:2,maxArgs:2}],[ae.LOG,{value:ae.LOG,name:"Logarithm",description:"Returns the natural logarithm of an argument",minArgs:1,maxArgs:1}],[ae.LOG10,{value:ae.LOG10,name:"Base 10 logarithm",description:"Returns the base 10 logarithm of an argument",minArgs:1,maxArgs:1}],[ae.LOG1P,{value:ae.LOG1P,name:"Logarithm of the sum",description:"Returns the natural logarithm of the sum of an argument",minArgs:1,maxArgs:1}],[ae.CEIL,{value:ae.CEIL,name:"Ceiling",description:"Returns the smallest (closest to negative infinity) of an argument",minArgs:1,maxArgs:1}],[ae.FLOOR,{value:ae.FLOOR,name:"Floor",description:"Returns the largest (closest to positive infinity) of an argument",minArgs:1,maxArgs:1}],[ae.FLOOR_DIV,{value:ae.FLOOR_DIV,name:"Floor division",description:"Returns the largest (closest to positive infinity) of the arguments",minArgs:2,maxArgs:2}],[ae.FLOOR_MOD,{value:ae.FLOOR_MOD,name:"Floor modulus",description:"Returns the floor modulus of the arguments",minArgs:2,maxArgs:2}],[ae.ABS,{value:ae.ABS,name:"Absolute",description:"Returns the absolute value of an argument",minArgs:1,maxArgs:1}],[ae.MIN,{value:ae.MIN,name:"Min",description:"Returns the smaller of the arguments",minArgs:2,maxArgs:2}],[ae.MAX,{value:ae.MAX,name:"Max",description:"Returns the greater of the arguments",minArgs:2,maxArgs:2}],[ae.POW,{value:ae.POW,name:"Raise to a power",description:"Returns the value of the first argument raised to the power of the second argument",minArgs:2,maxArgs:2}],[ae.SIGNUM,{value:ae.SIGNUM,name:"Sign of a real number",description:"Returns the signum function of the argument",minArgs:1,maxArgs:1}],[ae.RAD,{value:ae.RAD,name:"Radian",description:"Converts an angle measured in degrees to an approximately equivalent angle measured in radians",minArgs:1,maxArgs:1}],[ae.DEG,{value:ae.DEG,name:"Degrees",description:"Converts an angle measured in radians to an approximately equivalent angle measured in degrees.",minArgs:1,maxArgs:1}]]);var le,ie;!function(t){t.ATTRIBUTE="ATTRIBUTE",t.TIME_SERIES="TIME_SERIES",t.CONSTANT="CONSTANT",t.MESSAGE_BODY="MESSAGE_BODY",t.MESSAGE_METADATA="MESSAGE_METADATA"}(le||(le={})),function(t){t.ATTRIBUTE="ATTRIBUTE",t.TIME_SERIES="TIME_SERIES",t.MESSAGE_BODY="MESSAGE_BODY",t.MESSAGE_METADATA="MESSAGE_METADATA"}(ie||(ie={}));const se=new Map([[le.ATTRIBUTE,"tb.rulenode.attribute-type"],[le.TIME_SERIES,"tb.rulenode.time-series-type"],[le.CONSTANT,"tb.rulenode.constant-type"],[le.MESSAGE_BODY,"tb.rulenode.message-body-type"],[le.MESSAGE_METADATA,"tb.rulenode.message-metadata-type"]]),me=["x","y","z","a","b","c","d","k","l","m","n","o","p","r","s","t"];var ue,pe;!function(t){t.SHARED_SCOPE="SHARED_SCOPE",t.SERVER_SCOPE="SERVER_SCOPE",t.CLIENT_SCOPE="CLIENT_SCOPE"}(ue||(ue={})),function(t){t.SHARED_SCOPE="SHARED_SCOPE",t.SERVER_SCOPE="SERVER_SCOPE"}(pe||(pe={}));const de=new Map([[ue.SHARED_SCOPE,"tb.rulenode.shared-scope"],[ue.SERVER_SCOPE,"tb.rulenode.server-scope"],[ue.CLIENT_SCOPE,"tb.rulenode.client-scope"]]);class fe extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.allAzureIotHubCredentialsTypes=Zt,this.azureIotHubCredentialsTypeTranslationsMap=te}configForm(){return this.azureIotHubConfigForm}onConfigurationSet(t){this.azureIotHubConfigForm=this.fb.group({topicPattern:[t?t.topicPattern:null,[q.required]],host:[t?t.host:null,[q.required]],port:[t?t.port:null,[q.required,q.min(1),q.max(65535)]],connectTimeoutSec:[t?t.connectTimeoutSec:null,[q.required,q.min(1),q.max(200)]],clientId:[t?t.clientId:null,[q.required]],cleanSession:[!!t&&t.cleanSession,[]],ssl:[!!t&&t.ssl,[]],credentials:this.fb.group({type:[t&&t.credentials?t.credentials.type:null,[q.required]],sasKey:[t&&t.credentials?t.credentials.sasKey:null,[]],caCert:[t&&t.credentials?t.credentials.caCert:null,[]],caCertFileName:[t&&t.credentials?t.credentials.caCertFileName:null,[]],privateKey:[t&&t.credentials?t.credentials.privateKey:null,[]],privateKeyFileName:[t&&t.credentials?t.credentials.privateKeyFileName:null,[]],cert:[t&&t.credentials?t.credentials.cert:null,[]],certFileName:[t&&t.credentials?t.credentials.certFileName:null,[]],password:[t&&t.credentials?t.credentials.password:null,[]]})})}prepareOutputConfig(t){const e=t.credentials.type;return"sas"===e&&(t.credentials={type:e,sasKey:t.credentials.sasKey,caCert:t.credentials.caCert,caCertFileName:t.credentials.caCertFileName}),t}validatorTriggers(){return["credentials.type"]}updateValidators(t){const e=this.azureIotHubConfigForm.get("credentials"),o=e.get("type").value;switch(t&&e.reset({type:o},{emitEvent:!1}),e.get("sasKey").setValidators([]),e.get("privateKey").setValidators([]),e.get("privateKeyFileName").setValidators([]),e.get("cert").setValidators([]),e.get("certFileName").setValidators([]),o){case"sas":e.get("sasKey").setValidators([q.required]);break;case"cert.PEM":e.get("privateKey").setValidators([q.required]),e.get("privateKeyFileName").setValidators([q.required]),e.get("cert").setValidators([q.required]),e.get("certFileName").setValidators([q.required])}e.get("sasKey").updateValueAndValidity({emitEvent:t}),e.get("privateKey").updateValueAndValidity({emitEvent:t}),e.get("privateKeyFileName").updateValueAndValidity({emitEvent:t}),e.get("cert").updateValueAndValidity({emitEvent:t}),e.get("certFileName").updateValueAndValidity({emitEvent:t})}}t("AzureIotHubConfigComponent",fe),fe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:fe,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),fe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:fe,selector:"tb-action-node-azure-iot-hub-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.topic\n \n \n {{ \'tb.rulenode.topic-required\' | translate }}\n \n \n \n \n tb.rulenode.hostname\n \n \n {{ \'tb.rulenode.hostname-required\' | translate }}\n \n \n \n tb.rulenode.device-id\n \n \n {{ \'tb.rulenode.device-id-required\' | translate }}\n \n \n \n \n \n tb.rulenode.credentials\n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(azureIotHubConfigForm.get(\'credentials.type\').value) | translate }}\n \n \n
\n \n tb.rulenode.credentials-type\n \n \n {{ azureIotHubCredentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.sas-key\n \n \n \n {{ \'tb.rulenode.sas-key-required\' | translate }}\n \n \n \n \n \n \n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n \n
\n
\n
\n
\n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:j.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:j.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:_.TogglePasswordComponent,selector:"tb-toggle-password"},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:j.MatAccordion,selector:"mat-accordion",inputs:["multi","displayMode","togglePosition","hideToggle"],exportAs:["matAccordion"]},{type:j.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:j.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:M.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:w.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:V.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:fe,decorators:[{type:o,args:[{selector:"tb-action-node-azure-iot-hub-config",templateUrl:"./azure-iot-hub-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class ce extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.serviceType=p.TB_RULE_ENGINE}configForm(){return this.checkPointConfigForm}onConfigurationSet(t){this.checkPointConfigForm=this.fb.group({queueName:[t?t.queueName:null,[q.required]]})}}t("CheckPointConfigComponent",ce),ce.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ce,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ce.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ce,selector:"tb-action-node-check-point-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n
\n',components:[{type:J.QueueAutocompleteComponent,selector:"tb-queue-autocomplete",inputs:["labelText","requiredText","required","queueType","disabled"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ce,decorators:[{type:o,args:[{selector:"tb-action-node-check-point-config",templateUrl:"./check-point-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class ge extends s{constructor(t,e,o,r){super(t),this.store=t,this.fb=e,this.nodeScriptTestService=o,this.translate=r,this.mvelEnabled=Q(this.store).mvelEnabled,this.scriptLanguage=d}configForm(){return this.clearAlarmConfigForm}onConfigurationSet(t){this.clearAlarmConfigForm=this.fb.group({scriptLang:[t?t.scriptLang:d.JS,[q.required]],alarmDetailsBuildJs:[t?t.alarmDetailsBuildJs:null,[]],alarmDetailsBuildMvel:[t?t.alarmDetailsBuildMvel:null,[]],alarmType:[t?t.alarmType:null,[q.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.clearAlarmConfigForm.get("scriptLang").value;e!==d.MVEL||this.mvelEnabled||(e=d.JS,this.clearAlarmConfigForm.get("scriptLang").patchValue(e,{emitEvent:!1}),setTimeout((()=>{this.clearAlarmConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(e===d.JS?[q.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:t}),this.clearAlarmConfigForm.get("alarmDetailsBuildMvel").setValidators(e===d.MVEL?[q.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildMvel").updateValueAndValidity({emitEvent:t})}prepareInputConfig(t){return t&&(t.scriptLang||(t.scriptLang=d.JS)),t}testScript(){const t=this.clearAlarmConfigForm.get("scriptLang").value,e=t===d.JS?"alarmDetailsBuildJs":"alarmDetailsBuildMvel",o=this.clearAlarmConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/clear_alarm_node_script_fn",t).subscribe((t=>{t&&this.clearAlarmConfigForm.get(e).setValue(t)}))}onValidate(){(this.clearAlarmConfigForm.get("scriptLang").value===d.JS?this.jsFuncComponent:this.mvelFuncComponent).validateOnSubmit()}}t("ClearAlarmConfigComponent",ge),ge.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ge,deps:[{token:k.Store},{token:M.FormBuilder},{token:$.NodeScriptTestService},{token:R.TranslateService}],target:e.ɵɵFactoryTarget.Component}),ge.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ge,selector:"tb-action-node-clear-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"mvelFuncComponent",first:!0,predicate:["mvelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n
\n \n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n
\n',components:[{type:X.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{type:Z.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ge,decorators:[{type:o,args:[{selector:"tb-action-node-clear-alarm-config",templateUrl:"./clear-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder},{type:$.NodeScriptTestService},{type:R.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],mvelFuncComponent:[{type:a,args:["mvelFuncComponent",{static:!1}]}]}});class xe extends s{constructor(t,e,o,r){super(t),this.store=t,this.fb=e,this.nodeScriptTestService=o,this.translate=r,this.alarmSeverities=Object.keys(f),this.alarmSeverityTranslationMap=c,this.separatorKeysCodes=[et,ot,rt],this.mvelEnabled=Q(this.store).mvelEnabled,this.scriptLanguage=d}configForm(){return this.createAlarmConfigForm}onConfigurationSet(t){this.createAlarmConfigForm=this.fb.group({scriptLang:[t?t.scriptLang:d.JS,[q.required]],alarmDetailsBuildJs:[t?t.alarmDetailsBuildJs:null,[]],alarmDetailsBuildMvel:[t?t.alarmDetailsBuildMvel:null,[]],useMessageAlarmData:[!!t&&t.useMessageAlarmData,[]],overwriteAlarmDetails:[!!t&&t.overwriteAlarmDetails,[]],alarmType:[t?t.alarmType:null,[]],severity:[t?t.severity:null,[]],propagate:[!!t&&t.propagate,[]],relationTypes:[t?t.relationTypes:null,[]],propagateToOwner:[!!t&&t.propagateToOwner,[]],propagateToTenant:[!!t&&t.propagateToTenant,[]],dynamicSeverity:!1}),this.createAlarmConfigForm.get("dynamicSeverity").valueChanges.subscribe((t=>{t?this.createAlarmConfigForm.get("severity").patchValue("",{emitEvent:!1}):this.createAlarmConfigForm.get("severity").patchValue(this.alarmSeverities[0],{emitEvent:!1})}))}validatorTriggers(){return["useMessageAlarmData","overwriteAlarmDetails","scriptLang"]}updateValidators(t){const e=this.createAlarmConfigForm.get("useMessageAlarmData").value,o=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;e?(this.createAlarmConfigForm.get("alarmType").setValidators([]),this.createAlarmConfigForm.get("severity").setValidators([])):(this.createAlarmConfigForm.get("alarmType").setValidators([q.required]),this.createAlarmConfigForm.get("severity").setValidators([q.required])),this.createAlarmConfigForm.get("alarmType").updateValueAndValidity({emitEvent:t}),this.createAlarmConfigForm.get("severity").updateValueAndValidity({emitEvent:t});let r=this.createAlarmConfigForm.get("scriptLang").value;r!==d.MVEL||this.mvelEnabled||(r=d.JS,this.createAlarmConfigForm.get("scriptLang").patchValue(r,{emitEvent:!1}),setTimeout((()=>{this.createAlarmConfigForm.updateValueAndValidity({emitEvent:!0})})));const a=!1===e||!0===o;this.createAlarmConfigForm.get("alarmDetailsBuildJs").setValidators(a&&r===d.JS?[q.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildMvel").setValidators(a&&r===d.MVEL?[q.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:t}),this.createAlarmConfigForm.get("alarmDetailsBuildMvel").updateValueAndValidity({emitEvent:t})}prepareInputConfig(t){return t&&(t.scriptLang||(t.scriptLang=d.JS)),t}testScript(){const t=this.createAlarmConfigForm.get("scriptLang").value,e=t===d.JS?"alarmDetailsBuildJs":"alarmDetailsBuildMvel",o=this.createAlarmConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(o,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/create_alarm_node_script_fn",t).subscribe((t=>{t&&this.createAlarmConfigForm.get(e).setValue(t)}))}removeKey(t,e){const o=this.createAlarmConfigForm.get(e).value,r=o.indexOf(t);r>=0&&(o.splice(r,1),this.createAlarmConfigForm.get(e).setValue(o,{emitEvent:!0}))}addKey(t,e){const o=t.input;let r=t.value;if((r||"").trim()){r=r.trim();let t=this.createAlarmConfigForm.get(e).value;t&&-1!==t.indexOf(r)||(t||(t=[]),t.push(r),this.createAlarmConfigForm.get(e).setValue(t,{emitEvent:!0}))}o&&(o.value="")}onValidate(){const t=this.createAlarmConfigForm.get("useMessageAlarmData").value,e=this.createAlarmConfigForm.get("overwriteAlarmDetails").value;if(!t||e){(this.createAlarmConfigForm.get("scriptLang").value===d.JS?this.jsFuncComponent:this.mvelFuncComponent).validateOnSubmit()}}}t("CreateAlarmConfigComponent",xe),xe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:xe,deps:[{token:k.Store},{token:M.FormBuilder},{token:$.NodeScriptTestService},{token:R.TranslateService}],target:e.ɵɵFactoryTarget.Component}),xe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:xe,selector:"tb-action-node-create-alarm-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"mvelFuncComponent",first:!0,predicate:["mvelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'tb.rulenode.use-message-alarm-data\' | translate }}\n \n \n {{ \'tb.rulenode.overwrite-alarm-details\' | translate }}\n \n
\n \n \n \n \n \n
\n \n
\n
\n
\n \n tb.rulenode.alarm-type\n \n \n {{ \'tb.rulenode.alarm-type-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-alarm-severity-pattern\' | translate }}\n \n \n tb.rulenode.alarm-severity\n \n \n {{ alarmSeverityTranslationMap.get(severity) | translate }}\n \n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n tb.rulenode.alarm-severity-pattern\n \n \n {{ \'tb.rulenode.alarm-severity-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.propagate\' | translate }}\n \n
\n \n tb.rulenode.relation-types-list\n \n \n {{key}}\n close\n \n \n \n tb.rulenode.relation-types-list-hint\n \n
\n \n {{ \'tb.rulenode.propagate-to-owner\' | translate }}\n \n \n {{ \'tb.rulenode.propagate-to-tenant\' | translate }}\n \n
\n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:X.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{type:Z.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:xe,decorators:[{type:o,args:[{selector:"tb-action-node-create-alarm-config",templateUrl:"./create-alarm-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder},{type:$.NodeScriptTestService},{type:R.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],mvelFuncComponent:[{type:a,args:["mvelFuncComponent",{static:!1}]}]}});class ye extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.directionTypes=Object.keys(g),this.directionTypeTranslations=x,this.entityType=y}configForm(){return this.createRelationConfigForm}onConfigurationSet(t){this.createRelationConfigForm=this.fb.group({direction:[t?t.direction:null,[q.required]],entityType:[t?t.entityType:null,[q.required]],entityNamePattern:[t?t.entityNamePattern:null,[]],entityTypePattern:[t?t.entityTypePattern:null,[]],relationType:[t?t.relationType:null,[q.required]],createEntityIfNotExists:[!!t&&t.createEntityIfNotExists,[]],removeCurrentRelations:[!!t&&t.removeCurrentRelations,[]],changeOriginatorToRelatedEntity:[!!t&&t.changeOriginatorToRelatedEntity,[]],entityCacheExpiration:[t?t.entityCacheExpiration:null,[q.required,q.min(0)]]})}validatorTriggers(){return["entityType"]}updateValidators(t){const e=this.createRelationConfigForm.get("entityType").value;e?this.createRelationConfigForm.get("entityNamePattern").setValidators([q.required,q.pattern(/.*\S.*/)]):this.createRelationConfigForm.get("entityNamePattern").setValidators([]),!e||e!==y.DEVICE&&e!==y.ASSET?this.createRelationConfigForm.get("entityTypePattern").setValidators([]):this.createRelationConfigForm.get("entityTypePattern").setValidators([q.required,q.pattern(/.*\S.*/)]),this.createRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:t}),this.createRelationConfigForm.get("entityTypePattern").updateValueAndValidity({emitEvent:t})}prepareOutputConfig(t){return t.entityNamePattern=t.entityNamePattern?t.entityNamePattern.trim():null,t.entityTypePattern=t.entityTypePattern?t.entityTypePattern.trim():null,t}}t("CreateRelationConfigComponent",ye),ye.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ye,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ye.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ye,selector:"tb-action-node-create-relation-config",usesInheritance:!0,ngImport:e,template:'
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-type-pattern\n \n \n {{ \'tb.rulenode.entity-type-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n
\n \n {{ \'tb.rulenode.create-entity-if-not-exists\' | translate }}\n \n
tb.rulenode.create-entity-if-not-exists-hint
\n
\n \n {{ \'tb.rulenode.remove-current-relations\' | translate }}\n \n
tb.rulenode.remove-current-relations-hint
\n \n {{ \'tb.rulenode.change-originator-to-related-entity\' | translate }}\n \n
tb.rulenode.change-originator-to-related-entity-hint
\n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:lt.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ye,decorators:[{type:o,args:[{selector:"tb-action-node-create-relation-config",templateUrl:"./create-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class be extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.directionTypes=Object.keys(g),this.directionTypeTranslations=x,this.entityType=y}configForm(){return this.deleteRelationConfigForm}onConfigurationSet(t){this.deleteRelationConfigForm=this.fb.group({deleteForSingleEntity:[!!t&&t.deleteForSingleEntity,[]],direction:[t?t.direction:null,[q.required]],entityType:[t?t.entityType:null,[]],entityNamePattern:[t?t.entityNamePattern:null,[]],relationType:[t?t.relationType:null,[q.required]],entityCacheExpiration:[t?t.entityCacheExpiration:null,[q.required,q.min(0)]]})}validatorTriggers(){return["deleteForSingleEntity","entityType"]}updateValidators(t){const e=this.deleteRelationConfigForm.get("deleteForSingleEntity").value,o=this.deleteRelationConfigForm.get("entityType").value;e?this.deleteRelationConfigForm.get("entityType").setValidators([q.required]):this.deleteRelationConfigForm.get("entityType").setValidators([]),e&&o?this.deleteRelationConfigForm.get("entityNamePattern").setValidators([q.required,q.pattern(/.*\S.*/)]):this.deleteRelationConfigForm.get("entityNamePattern").setValidators([]),this.deleteRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:!1}),this.deleteRelationConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:t})}prepareOutputConfig(t){return t.entityNamePattern=t.entityNamePattern?t.entityNamePattern.trim():null,t}}t("DeleteRelationConfigComponent",be),be.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:be,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),be.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:be,selector:"tb-action-node-delete-relation-config",usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'tb.rulenode.delete-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.delete-relation-hint
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.relation-type-pattern\n \n \n {{ \'tb.rulenode.relation-type-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.entity-cache-expiration\n \n \n {{ \'tb.rulenode.entity-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.entity-cache-expiration-range\' | translate }}\n \n tb.rulenode.entity-cache-expiration-hint\n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:lt.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.MatLabel,selector:"mat-label"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:be,decorators:[{type:o,args:[{selector:"tb-action-node-delete-relation-config",templateUrl:"./delete-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class he extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.deviceProfile}onConfigurationSet(t){this.deviceProfile=this.fb.group({persistAlarmRulesState:[!!t&&t.persistAlarmRulesState,q.required],fetchAlarmRulesStateOnStart:[!!t&&t.fetchAlarmRulesStateOnStart,q.required]})}}t("DeviceProfileConfigComponent",he),he.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:he,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),he.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:he,selector:"tb-device-profile-config",usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'tb.rulenode.persist-alarm-rules\' | translate }}\n \n \n {{ \'tb.rulenode.fetch-alarm-rules\' | translate }}\n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:he,decorators:[{type:o,args:[{selector:"tb-device-profile-config",templateUrl:"./device-profile-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ce extends s{constructor(t,e,o,r){super(t),this.store=t,this.fb=e,this.nodeScriptTestService=o,this.translate=r,this.mvelEnabled=Q(this.store).mvelEnabled,this.scriptLanguage=d}configForm(){return this.generatorConfigForm}onConfigurationSet(t){this.generatorConfigForm=this.fb.group({msgCount:[t?t.msgCount:null,[q.required,q.min(0)]],periodInSeconds:[t?t.periodInSeconds:null,[q.required,q.min(1)]],originator:[t?t.originator:null,[]],scriptLang:[t?t.scriptLang:d.JS,[q.required]],jsScript:[t?t.jsScript:null,[]],mvelScript:[t?t.mvelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.generatorConfigForm.get("scriptLang").value;e!==d.MVEL||this.mvelEnabled||(e=d.JS,this.generatorConfigForm.get("scriptLang").patchValue(e,{emitEvent:!1}),setTimeout((()=>{this.generatorConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.generatorConfigForm.get("jsScript").setValidators(e===d.JS?[q.required]:[]),this.generatorConfigForm.get("jsScript").updateValueAndValidity({emitEvent:t}),this.generatorConfigForm.get("mvelScript").setValidators(e===d.MVEL?[q.required]:[]),this.generatorConfigForm.get("mvelScript").updateValueAndValidity({emitEvent:t})}prepareInputConfig(t){return t&&(t.scriptLang||(t.scriptLang=d.JS),t.originatorId&&t.originatorType?t.originator={id:t.originatorId,entityType:t.originatorType}:t.originator=null,delete t.originatorId,delete t.originatorType),t}prepareOutputConfig(t){return t.originator?(t.originatorId=t.originator.id,t.originatorType=t.originator.entityType):(t.originatorId=null,t.originatorType=null),delete t.originator,t}testScript(){const t=this.generatorConfigForm.get("scriptLang").value,e=t===d.JS?"jsScript":"mvelScript",o=this.generatorConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(o,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,"rulenode/generator_node_script_fn",t).subscribe((t=>{t&&this.generatorConfigForm.get(e).setValue(t)}))}onValidate(){(this.generatorConfigForm.get("scriptLang").value===d.JS?this.jsFuncComponent:this.mvelFuncComponent).validateOnSubmit()}}t("GeneratorConfigComponent",Ce),Ce.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ce,deps:[{token:k.Store},{token:M.FormBuilder},{token:$.NodeScriptTestService},{token:R.TranslateService}],target:e.ɵɵFactoryTarget.Component}),Ce.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ce,selector:"tb-action-node-generator-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"mvelFuncComponent",first:!0,predicate:["mvelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.message-count\n \n \n {{ \'tb.rulenode.message-count-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-message-count-message\' | translate }}\n \n \n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-seconds-message\' | translate }}\n \n \n
\n \n \n \n
\n \n \n \n \n \n
\n \n
\n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:it.EntitySelectComponent,selector:"tb-entity-select",inputs:["allowedEntityTypes","useAliasEntityTypes","required","disabled"]},{type:X.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{type:Z.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ce,decorators:[{type:o,args:[{selector:"tb-action-node-generator-config",templateUrl:"./generator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder},{type:$.NodeScriptTestService},{type:R.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],mvelFuncComponent:[{type:a,args:["mvelFuncComponent",{static:!1}]}]}});class Fe extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.perimeterType=wt,this.perimeterTypes=Object.keys(wt),this.perimeterTypeTranslationMap=Ht,this.rangeUnits=Object.keys(Kt),this.rangeUnitTranslationMap=jt,this.timeUnits=Object.keys(Ut),this.timeUnitsTranslationMap=Bt}configForm(){return this.geoActionConfigForm}onConfigurationSet(t){this.geoActionConfigForm=this.fb.group({latitudeKeyName:[t?t.latitudeKeyName:null,[q.required]],longitudeKeyName:[t?t.longitudeKeyName:null,[q.required]],perimeterType:[t?t.perimeterType:null,[q.required]],fetchPerimeterInfoFromMessageMetadata:[!!t&&t.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[t?t.perimeterKeyName:null,[]],centerLatitude:[t?t.centerLatitude:null,[]],centerLongitude:[t?t.centerLatitude:null,[]],range:[t?t.range:null,[]],rangeUnit:[t?t.rangeUnit:null,[]],polygonsDefinition:[t?t.polygonsDefinition:null,[]],minInsideDuration:[t?t.minInsideDuration:null,[q.required,q.min(1),q.max(2147483647)]],minInsideDurationTimeUnit:[t?t.minInsideDurationTimeUnit:null,[q.required]],minOutsideDuration:[t?t.minOutsideDuration:null,[q.required,q.min(1),q.max(2147483647)]],minOutsideDurationTimeUnit:[t?t.minOutsideDurationTimeUnit:null,[q.required]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(t){const e=this.geoActionConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoActionConfigForm.get("perimeterType").value;e?this.geoActionConfigForm.get("perimeterKeyName").setValidators([q.required]):this.geoActionConfigForm.get("perimeterKeyName").setValidators([]),e||o!==wt.CIRCLE?(this.geoActionConfigForm.get("centerLatitude").setValidators([]),this.geoActionConfigForm.get("centerLongitude").setValidators([]),this.geoActionConfigForm.get("range").setValidators([]),this.geoActionConfigForm.get("rangeUnit").setValidators([])):(this.geoActionConfigForm.get("centerLatitude").setValidators([q.required,q.min(-90),q.max(90)]),this.geoActionConfigForm.get("centerLongitude").setValidators([q.required,q.min(-180),q.max(180)]),this.geoActionConfigForm.get("range").setValidators([q.required,q.min(0)]),this.geoActionConfigForm.get("rangeUnit").setValidators([q.required])),e||o!==wt.POLYGON?this.geoActionConfigForm.get("polygonsDefinition").setValidators([]):this.geoActionConfigForm.get("polygonsDefinition").setValidators([q.required]),this.geoActionConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:t}),this.geoActionConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:t}),this.geoActionConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:t}),this.geoActionConfigForm.get("range").updateValueAndValidity({emitEvent:t}),this.geoActionConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:t}),this.geoActionConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:t})}}t("GpsGeoActionConfigComponent",Fe),Fe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Fe,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Fe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Fe,selector:"tb-action-node-gps-geofencing-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.min-inside-duration\n \n \n {{ \'tb.rulenode.min-inside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-inside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.min-outside-duration\n \n \n {{ \'tb.rulenode.min-outside-duration-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.min-outside-duration-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Fe,decorators:[{type:o,args:[{selector:"tb-action-node-gps-geofencing-config",templateUrl:"./gps-geo-action-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Le extends b{constructor(t,e,o,r){super(t),this.store=t,this.translate=e,this.injector=o,this.fb=r,this.propagateChange=null,this.valueChangeSubscription=null}get required(){return this.requiredValue}set required(t){this.requiredValue=st(t)}ngOnInit(){this.ngControl=this.injector.get(S),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.kvListFormGroup=this.fb.group({}),this.kvListFormGroup.addControl("keyVals",this.fb.array([]))}keyValsFormArray(){return this.kvListFormGroup.get("keyVals")}registerOnChange(t){this.propagateChange=t}registerOnTouched(t){}setDisabledState(t){this.disabled=t,this.disabled?this.kvListFormGroup.disable({emitEvent:!1}):this.kvListFormGroup.enable({emitEvent:!1})}writeValue(t){this.valueChangeSubscription&&this.valueChangeSubscription.unsubscribe();const e=[];if(t)for(const o of Object.keys(t))Object.prototype.hasOwnProperty.call(t,o)&&e.push(this.fb.group({key:[o,[q.required]],value:[t[o],[q.required]]}));this.kvListFormGroup.setControl("keyVals",this.fb.array(e)),this.valueChangeSubscription=this.kvListFormGroup.valueChanges.subscribe((()=>{this.updateModel()}))}removeKeyVal(t){this.kvListFormGroup.get("keyVals").removeAt(t)}addKeyVal(){this.kvListFormGroup.get("keyVals").push(this.fb.group({key:["",[q.required]],value:["",[q.required]]}))}validate(t){const e=this.kvListFormGroup.get("keyVals").value;if(!e.length&&this.required)return{kvMapRequired:!0};if(!this.kvListFormGroup.valid)return{kvFieldsRequired:!0};if(this.uniqueKeyValuePairValidator)for(const t of e)if(t.key===t.value)return{uniqueKeyValuePair:!0};return null}updateModel(){const t=this.kvListFormGroup.get("keyVals").value;if(this.required&&!t.length||!this.kvListFormGroup.valid)this.propagateChange(null);else{const e={};t.forEach((t=>{e[t.key]=t.value})),this.propagateChange(e)}}}t("KvMapConfigComponent",Le),Le.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Le,deps:[{token:k.Store},{token:R.TranslateService},{token:e.Injector},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Le.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Le,selector:"tb-kv-map-config",inputs:{disabled:"disabled",uniqueKeyValuePairValidator:"uniqueKeyValuePairValidator",requiredText:"requiredText",keyText:"keyText",keyRequiredText:"keyRequiredText",valText:"valText",valRequiredText:"valRequiredText",hintText:"hintText",required:"required"},providers:[{provide:A,useExisting:n((()=>Le)),multi:!0},{provide:G,useExisting:n((()=>Le)),multi:!0}],usesInheritance:!0,ngImport:e,template:'
\n
\n {{ keyText | translate }}\n {{ valText | translate }}\n \n
\n
\n
\n \n \n \n \n {{ keyRequiredText | translate }}\n \n \n \n \n \n \n {{ valRequiredText | translate }}\n \n \n \n
\n
\n
\n \n \n
\n \n
\n
\n',styles:[":host .tb-kv-map-config{margin-bottom:16px}:host .tb-kv-map-config .header{padding-left:5px;padding-right:5px;padding-bottom:5px}:host .tb-kv-map-config .header .cell{padding-left:5px;padding-right:5px;color:#0000008a;font-size:12px;font-weight:700;white-space:nowrap}:host .tb-kv-map-config .body{padding-left:5px;padding-right:5px;padding-bottom:20px;max-height:300px;overflow:auto}:host .tb-kv-map-config .body .cell{padding-left:5px;padding-right:5px}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell{margin:0}:host ::ng-deep .tb-kv-map-config .body mat-form-field.cell .mat-form-field-infix{border-top:0}:host ::ng-deep .tb-kv-map-config .body button.mat-button{margin:0;align-self:baseline}\n"],components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:mt.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:ut.DefaultShowHideDirective,selector:" [fxShow], [fxShow.print], [fxShow.xs], [fxShow.sm], [fxShow.md], [fxShow.lg], [fxShow.xl], [fxShow.lt-sm], [fxShow.lt-md], [fxShow.lt-lg], [fxShow.lt-xl], [fxShow.gt-xs], [fxShow.gt-sm], [fxShow.gt-md], [fxShow.gt-lg], [fxHide], [fxHide.print], [fxHide.xs], [fxHide.sm], [fxHide.md], [fxHide.lg], [fxHide.xl], [fxHide.lt-sm], [fxHide.lt-md], [fxHide.lt-lg], [fxHide.lt-xl], [fxHide.gt-xs], [fxHide.gt-sm], [fxHide.gt-md], [fxHide.gt-lg]",inputs:["fxShow","fxShow.print","fxShow.xs","fxShow.sm","fxShow.md","fxShow.lg","fxShow.xl","fxShow.lt-sm","fxShow.lt-md","fxShow.lt-lg","fxShow.lt-xl","fxShow.gt-xs","fxShow.gt-sm","fxShow.gt-md","fxShow.gt-lg","fxHide","fxHide.print","fxHide.xs","fxHide.sm","fxHide.md","fxHide.lg","fxHide.xl","fxHide.lt-sm","fxHide.lt-md","fxHide.lt-lg","fxHide.lt-xl","fxHide.gt-xs","fxHide.gt-sm","fxHide.gt-md","fxHide.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{type:M.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{type:V.MatLabel,selector:"mat-label"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:pt.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]}],pipes:{translate:R.TranslatePipe,async:w.AsyncPipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Le,decorators:[{type:o,args:[{selector:"tb-kv-map-config",templateUrl:"./kv-map-config.component.html",styleUrls:["./kv-map-config.component.scss"],providers:[{provide:A,useExisting:n((()=>Le)),multi:!0},{provide:G,useExisting:n((()=>Le)),multi:!0}]}]}],ctorParameters:function(){return[{type:k.Store},{type:R.TranslateService},{type:e.Injector},{type:M.FormBuilder}]},propDecorators:{disabled:[{type:l}],uniqueKeyValuePairValidator:[{type:l}],requiredText:[{type:l}],keyText:[{type:l}],keyRequiredText:[{type:l}],valText:[{type:l}],valRequiredText:[{type:l}],hintText:[{type:l}],required:[{type:l}]}});class ve extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.ackValues=["all","-1","0","1"],this.ToByteStandartCharsetTypesValues=oe,this.ToByteStandartCharsetTypeTranslationMap=re}configForm(){return this.kafkaConfigForm}onConfigurationSet(t){this.kafkaConfigForm=this.fb.group({topicPattern:[t?t.topicPattern:null,[q.required]],keyPattern:[t?t.keyPattern:null],bootstrapServers:[t?t.bootstrapServers:null,[q.required]],retries:[t?t.retries:null,[q.min(0)]],batchSize:[t?t.batchSize:null,[q.min(0)]],linger:[t?t.linger:null,[q.min(0)]],bufferMemory:[t?t.bufferMemory:null,[q.min(0)]],acks:[t?t.acks:null,[q.required]],keySerializer:[t?t.keySerializer:null,[q.required]],valueSerializer:[t?t.valueSerializer:null,[q.required]],otherProperties:[t?t.otherProperties:null,[]],addMetadataKeyValuesAsKafkaHeaders:[!!t&&t.addMetadataKeyValuesAsKafkaHeaders,[]],kafkaHeadersCharset:[t?t.kafkaHeadersCharset:null,[]]})}validatorTriggers(){return["addMetadataKeyValuesAsKafkaHeaders"]}updateValidators(t){this.kafkaConfigForm.get("addMetadataKeyValuesAsKafkaHeaders").value?this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([q.required]):this.kafkaConfigForm.get("kafkaHeadersCharset").setValidators([]),this.kafkaConfigForm.get("kafkaHeadersCharset").updateValueAndValidity({emitEvent:t})}}t("KafkaConfigComponent",ve),ve.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ve,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ve.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ve,selector:"tb-action-node-kafka-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.key-pattern\n \n \n \n
tb.rulenode.key-pattern-hint
\n \n tb.rulenode.bootstrap-servers\n \n \n {{ \'tb.rulenode.bootstrap-servers-required\' | translate }}\n \n \n \n tb.rulenode.retries\n \n \n {{ \'tb.rulenode.min-retries-message\' | translate }}\n \n \n \n tb.rulenode.batch-size-bytes\n \n \n {{ \'tb.rulenode.min-batch-size-bytes-message\' | translate }}\n \n \n \n tb.rulenode.linger-ms\n \n \n {{ \'tb.rulenode.min-linger-ms-message\' | translate }}\n \n \n \n tb.rulenode.buffer-memory-bytes\n \n \n {{ \'tb.rulenode.min-buffer-memory-bytes-message\' | translate }}\n \n \n \n tb.rulenode.acks\n \n \n {{ ackValue }}\n \n \n \n \n tb.rulenode.key-serializer\n \n \n {{ \'tb.rulenode.key-serializer-required\' | translate }}\n \n \n \n tb.rulenode.value-serializer\n \n \n {{ \'tb.rulenode.value-serializer-required\' | translate }}\n \n \n \n \n \n \n {{ \'tb.rulenode.add-metadata-key-values-as-kafka-headers\' | translate }}\n \n
tb.rulenode.add-metadata-key-values-as-kafka-headers-hint
\n \n tb.rulenode.charset-encoding\n \n \n {{ ToByteStandartCharsetTypeTranslationMap.get(charset) | translate }}\n \n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ve,decorators:[{type:o,args:[{selector:"tb-action-node-kafka-config",templateUrl:"./kafka-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ie extends s{constructor(t,e,o,r){super(t),this.store=t,this.fb=e,this.nodeScriptTestService=o,this.translate=r,this.mvelEnabled=Q(this.store).mvelEnabled,this.scriptLanguage=d}configForm(){return this.logConfigForm}onConfigurationSet(t){this.logConfigForm=this.fb.group({scriptLang:[t?t.scriptLang:d.JS,[q.required]],jsScript:[t?t.jsScript:null,[]],mvelScript:[t?t.mvelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.logConfigForm.get("scriptLang").value;e!==d.MVEL||this.mvelEnabled||(e=d.JS,this.logConfigForm.get("scriptLang").patchValue(e,{emitEvent:!1}),setTimeout((()=>{this.logConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.logConfigForm.get("jsScript").setValidators(e===d.JS?[q.required]:[]),this.logConfigForm.get("jsScript").updateValueAndValidity({emitEvent:t}),this.logConfigForm.get("mvelScript").setValidators(e===d.MVEL?[q.required]:[]),this.logConfigForm.get("mvelScript").updateValueAndValidity({emitEvent:t})}prepareInputConfig(t){return t&&(t.scriptLang||(t.scriptLang=d.JS)),t}testScript(){const t=this.logConfigForm.get("scriptLang").value,e=t===d.JS?"jsScript":"mvelScript",o=this.logConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(o,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/log_node_script_fn",t).subscribe((t=>{t&&this.logConfigForm.get(e).setValue(t)}))}onValidate(){(this.logConfigForm.get("scriptLang").value===d.JS?this.jsFuncComponent:this.mvelFuncComponent).validateOnSubmit()}}t("LogConfigComponent",Ie),Ie.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ie,deps:[{token:k.Store},{token:M.FormBuilder},{token:$.NodeScriptTestService},{token:R.TranslateService}],target:e.ɵɵFactoryTarget.Component}),Ie.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ie,selector:"tb-action-node-log-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"mvelFuncComponent",first:!0,predicate:["mvelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n
\n \n
\n
\n',components:[{type:X.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{type:Z.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ie,decorators:[{type:o,args:[{selector:"tb-action-node-log-config",templateUrl:"./log-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder},{type:$.NodeScriptTestService},{type:R.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],mvelFuncComponent:[{type:a,args:["mvelFuncComponent",{static:!1}]}]}});class Ne extends b{constructor(t,e){super(t),this.store=t,this.fb=e,this.subscriptions=[],this.disableCertPemCredentials=!1,this.passwordFieldRquired=!0,this.allCredentialsTypes=Yt,this.credentialsTypeTranslationsMap=Xt,this.propagateChange=null}get required(){return this.requiredValue}set required(t){this.requiredValue=st(t)}ngOnInit(){this.credentialsConfigFormGroup=this.fb.group({type:[null,[q.required]],username:[null,[]],password:[null,[]],caCert:[null,[]],caCertFileName:[null,[]],privateKey:[null,[]],privateKeyFileName:[null,[]],cert:[null,[]],certFileName:[null,[]]}),this.subscriptions.push(this.credentialsConfigFormGroup.valueChanges.pipe(dt()).subscribe((()=>{this.updateView()}))),this.subscriptions.push(this.credentialsConfigFormGroup.get("type").valueChanges.subscribe((()=>{this.credentialsTypeChanged()})))}ngOnChanges(t){for(const e of Object.keys(t)){const o=t[e];if(!o.firstChange&&o.currentValue!==o.previousValue&&o.currentValue&&"disableCertPemCredentials"===e){"cert.PEM"===this.credentialsConfigFormGroup.get("type").value&&setTimeout((()=>{this.credentialsConfigFormGroup.get("type").patchValue("anonymous",{emitEvent:!0})}))}}}ngOnDestroy(){this.subscriptions.forEach((t=>t.unsubscribe()))}writeValue(t){W(t)&&(this.credentialsConfigFormGroup.reset(t,{emitEvent:!1}),this.updateValidators(!1))}setDisabledState(t){t?this.credentialsConfigFormGroup.disable():(this.credentialsConfigFormGroup.enable(),this.updateValidators())}updateView(){let t=this.credentialsConfigFormGroup.value;const e=t.type;switch(e){case"anonymous":t={type:e};break;case"basic":t={type:e,username:t.username,password:t.password};break;case"cert.PEM":delete t.username}this.propagateChange(t)}registerOnChange(t){this.propagateChange=t}registerOnTouched(t){}validate(t){return this.credentialsConfigFormGroup.valid?null:{credentialsConfig:{valid:!1}}}credentialsTypeChanged(){this.credentialsConfigFormGroup.patchValue({username:null,password:null,caCert:null,caCertFileName:null,privateKey:null,privateKeyFileName:null,cert:null,certFileName:null}),this.updateValidators()}updateValidators(t=!1){const e=this.credentialsConfigFormGroup.get("type").value;switch(t&&this.credentialsConfigFormGroup.reset({type:e},{emitEvent:!1}),this.credentialsConfigFormGroup.setValidators([]),this.credentialsConfigFormGroup.get("username").setValidators([]),this.credentialsConfigFormGroup.get("password").setValidators([]),e){case"anonymous":break;case"basic":this.credentialsConfigFormGroup.get("username").setValidators([q.required]),this.credentialsConfigFormGroup.get("password").setValidators(this.passwordFieldRquired?[q.required]:[]);break;case"cert.PEM":this.credentialsConfigFormGroup.setValidators([this.requiredFilesSelected(q.required,[["caCert","caCertFileName"],["privateKey","privateKeyFileName","cert","certFileName"]])])}this.credentialsConfigFormGroup.get("username").updateValueAndValidity({emitEvent:t}),this.credentialsConfigFormGroup.get("password").updateValueAndValidity({emitEvent:t}),this.credentialsConfigFormGroup.updateValueAndValidity({emitEvent:t})}requiredFilesSelected(t,e=null){return o=>{e||(e=[Object.keys(o.controls)]);return(null==o?void 0:o.controls)&&e.some((e=>e.every((e=>!t(o.controls[e])))))?null:{notAllRequiredFilesSelected:!0}}}}t("CredentialsConfigComponent",Ne),Ne.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ne,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ne.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ne,selector:"tb-credentials-config",inputs:{required:"required",disableCertPemCredentials:"disableCertPemCredentials",passwordFieldRquired:"passwordFieldRquired"},providers:[{provide:A,useExisting:n((()=>Ne)),multi:!0},{provide:G,useExisting:n((()=>Ne)),multi:!0}],usesInheritance:!0,usesOnChanges:!0,ngImport:e,template:'
\n \n \n tb.rulenode.credentials\n \n {{ credentialsTypeTranslationsMap.get(credentialsConfigFormGroup.get(\'type\').value) | translate }}\n \n \n \n \n tb.rulenode.credentials-type\n \n \n {{ credentialsTypeTranslationsMap.get(credentialsType) | translate }}\n \n \n \n {{ \'tb.rulenode.credentials-type-required\' | translate }}\n \n \n
\n \n \n \n \n tb.rulenode.username\n \n \n {{ \'tb.rulenode.username-required\' | translate }}\n \n \n \n tb.rulenode.password\n \n \n \n {{ \'tb.rulenode.password-required\' | translate }}\n \n \n \n \n
{{ \'tb.rulenode.credentials-pem-hint\' | translate }}
\n \n \n \n \n \n \n \n tb.rulenode.private-key-password\n \n \n \n
\n
\n
\n
\n
\n',components:[{type:j.MatExpansionPanel,selector:"mat-expansion-panel",inputs:["disabled","expanded","hideToggle","togglePosition"],outputs:["opened","closed","expandedChange","afterExpand","afterCollapse"],exportAs:["matExpansionPanel"]},{type:j.MatExpansionPanelHeader,selector:"mat-expansion-panel-header",inputs:["tabIndex","expandedHeight","collapsedHeight"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:_.TogglePasswordComponent,selector:"tb-toggle-password"},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:j.MatExpansionPanelTitle,selector:"mat-panel-title"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:j.MatExpansionPanelDescription,selector:"mat-panel-description"},{type:j.MatExpansionPanelContent,selector:"ng-template[matExpansionPanelContent]"},{type:V.MatLabel,selector:"mat-label"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:w.NgSwitch,selector:"[ngSwitch]",inputs:["ngSwitch"]},{type:w.NgSwitchCase,selector:"[ngSwitchCase]",inputs:["ngSwitchCase"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:V.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ne,decorators:[{type:o,args:[{selector:"tb-credentials-config",templateUrl:"./credentials-config.component.html",styleUrls:[],providers:[{provide:A,useExisting:n((()=>Ne)),multi:!0},{provide:G,useExisting:n((()=>Ne)),multi:!0}]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]},propDecorators:{required:[{type:l}],disableCertPemCredentials:[{type:l}],passwordFieldRquired:[{type:l}]}});class Te extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.subscriptions=[]}configForm(){return this.mqttConfigForm}onConfigurationSet(t){this.mqttConfigForm=this.fb.group({topicPattern:[t?t.topicPattern:null,[q.required]],host:[t?t.host:null,[q.required]],port:[t?t.port:null,[q.required,q.min(1),q.max(65535)]],connectTimeoutSec:[t?t.connectTimeoutSec:null,[q.required,q.min(1),q.max(200)]],clientId:[t?t.clientId:null,[]],appendClientIdSuffix:[{value:!!t&&t.appendClientIdSuffix,disabled:!(t&&Y(t.clientId))},[]],cleanSession:[!!t&&t.cleanSession,[]],retainedMessage:[!!t&&t.retainedMessage,[]],ssl:[!!t&&t.ssl,[]],credentials:[t?t.credentials:null,[]]}),this.subscriptions.push(this.mqttConfigForm.get("clientId").valueChanges.subscribe((t=>{Y(t)?this.mqttConfigForm.get("appendClientIdSuffix").enable({emitEvent:!1}):this.mqttConfigForm.get("appendClientIdSuffix").disable({emitEvent:!1})})))}ngOnDestroy(){this.subscriptions.forEach((t=>t.unsubscribe()))}}t("MqttConfigComponent",Te),Te.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Te,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Te.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Te,selector:"tb-action-node-mqtt-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.topic-pattern\n \n \n {{ \'tb.rulenode.topic-pattern-required\' | translate }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n \n tb.rulenode.connect-timeout\n \n \n {{ \'tb.rulenode.connect-timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n {{ \'tb.rulenode.connect-timeout-range\' | translate }}\n \n \n
\n \n tb.rulenode.client-id\n \n \n
tb.rulenode.client-id-hint
\n \n {{ \'tb.rulenode.append-client-id-suffix\' | translate }}\n \n
{{ "tb.rulenode.client-id-suffix-hint" | translate }}
\n \n {{ \'tb.rulenode.clean-session\' | translate }}\n \n \n {{ "tb.rulenode.retained-message" | translate }}\n \n \n {{ \'tb.rulenode.enable-ssl\' | translate }}\n \n \n
\n',styles:[":host .tb-mqtt-credentials-panel-group{margin:0 6px}:host .tb-hint.client-id{margin-top:-1.25em;max-width:-moz-fit-content;max-width:fit-content}:host mat-checkbox{padding-bottom:16px}\n"],components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Ne,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Te,decorators:[{type:o,args:[{selector:"tb-action-node-mqtt-config",templateUrl:"./mqtt-config.component.html",styleUrls:["./mqtt-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class ke extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.msgCountConfigForm}onConfigurationSet(t){this.msgCountConfigForm=this.fb.group({interval:[t?t.interval:null,[q.required,q.min(1)]],telemetryPrefix:[t?t.telemetryPrefix:null,[q.required]]})}}t("MsgCountConfigComponent",ke),ke.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ke,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ke.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ke,selector:"tb-action-node-msg-count-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.interval-seconds\n \n \n {{ \'tb.rulenode.interval-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-interval-seconds-message\' | translate }}\n \n \n \n tb.rulenode.output-timeseries-key-prefix\n \n \n {{ \'tb.rulenode.output-timeseries-key-prefix-required\' | translate }}\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ke,decorators:[{type:o,args:[{selector:"tb-action-node-msg-count-config",templateUrl:"./msg-count-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Me extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.msgDelayConfigForm}onConfigurationSet(t){this.msgDelayConfigForm=this.fb.group({useMetadataPeriodInSecondsPatterns:[!!t&&t.useMetadataPeriodInSecondsPatterns,[]],periodInSeconds:[t?t.periodInSeconds:null,[]],periodInSecondsPattern:[t?t.periodInSecondsPattern:null,[]],maxPendingMsgs:[t?t.maxPendingMsgs:null,[q.required,q.min(1),q.max(1e5)]]})}validatorTriggers(){return["useMetadataPeriodInSecondsPatterns"]}updateValidators(t){this.msgDelayConfigForm.get("useMetadataPeriodInSecondsPatterns").value?(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([q.required]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([])):(this.msgDelayConfigForm.get("periodInSecondsPattern").setValidators([]),this.msgDelayConfigForm.get("periodInSeconds").setValidators([q.required,q.min(0)])),this.msgDelayConfigForm.get("periodInSecondsPattern").updateValueAndValidity({emitEvent:t}),this.msgDelayConfigForm.get("periodInSeconds").updateValueAndValidity({emitEvent:t})}}t("MsgDelayConfigComponent",Me),Me.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Me,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Me.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Me,selector:"tb-action-node-msg-delay-config",usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'tb.rulenode.use-metadata-period-in-seconds-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-period-in-seconds-patterns-hint
\n \n tb.rulenode.period-seconds\n \n \n {{ \'tb.rulenode.period-seconds-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-period-0-seconds-message\' | translate }}\n \n \n \n \n tb.rulenode.period-in-seconds-pattern\n \n \n {{ \'tb.rulenode.period-in-seconds-pattern-required\' | translate }}\n \n \n \n \n \n tb.rulenode.max-pending-messages\n \n \n {{ \'tb.rulenode.max-pending-messages-required\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n {{ \'tb.rulenode.max-pending-messages-range\' | translate }}\n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatLabel,selector:"mat-label"},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Me,decorators:[{type:o,args:[{selector:"tb-action-node-msg-delay-config",templateUrl:"./msg-delay-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class qe extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.pubSubConfigForm}onConfigurationSet(t){this.pubSubConfigForm=this.fb.group({projectId:[t?t.projectId:null,[q.required]],topicName:[t?t.topicName:null,[q.required]],serviceAccountKey:[t?t.serviceAccountKey:null,[q.required]],serviceAccountKeyFileName:[t?t.serviceAccountKeyFileName:null,[q.required]],messageAttributes:[t?t.messageAttributes:null,[]]})}}t("PubSubConfigComponent",qe),qe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qe,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),qe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:qe,selector:"tb-action-node-pub-sub-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.gcp-project-id\n \n \n {{ \'tb.rulenode.gcp-project-id-required\' | translate }}\n \n \n \n tb.rulenode.pubsub-topic-name\n \n \n {{ \'tb.rulenode.pubsub-topic-name-required\' | translate }}\n \n \n \n \n \n
\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:z.FileInputComponent,selector:"tb-file-input",inputs:["label","accept","noFileText","inputId","allowedExtensions","dropLabel","contentConvertFunction","required","requiredAsError","disabled","existingFileName","readAsBinary","workFromFileObj","multipleFile"],outputs:["fileNameChanged"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qe,decorators:[{type:o,args:[{selector:"tb-action-node-pub-sub-config",templateUrl:"./pubsub-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Se extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToCloudConfigForm}onConfigurationSet(t){this.pushToCloudConfigForm=this.fb.group({scope:[t?t.scope:null,[q.required]]})}}t("PushToCloudConfigComponent",Se),Se.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Se,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Se.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Se,selector:"tb-action-node-push-to-cloud-config",usesInheritance:!0,ngImport:e,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Se,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-cloud-config",templateUrl:"./push-to-cloud-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ae extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u}configForm(){return this.pushToEdgeConfigForm}onConfigurationSet(t){this.pushToEdgeConfigForm=this.fb.group({scope:[t?t.scope:null,[q.required]]})}}t("PushToEdgeConfigComponent",Ae),Ae.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ae,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ae.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ae,selector:"tb-action-node-push-to-edge-config",usesInheritance:!0,ngImport:e,template:'
\n \n attribute.attributes-scope\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ae,decorators:[{type:o,args:[{selector:"tb-action-node-push-to-edge-config",templateUrl:"./push-to-edge-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ge extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.messageProperties=[null,"BASIC","TEXT_PLAIN","MINIMAL_BASIC","MINIMAL_PERSISTENT_BASIC","PERSISTENT_BASIC","PERSISTENT_TEXT_PLAIN"]}configForm(){return this.rabbitMqConfigForm}onConfigurationSet(t){this.rabbitMqConfigForm=this.fb.group({exchangeNamePattern:[t?t.exchangeNamePattern:null,[]],routingKeyPattern:[t?t.routingKeyPattern:null,[]],messageProperties:[t?t.messageProperties:null,[]],host:[t?t.host:null,[q.required]],port:[t?t.port:null,[q.required,q.min(1),q.max(65535)]],virtualHost:[t?t.virtualHost:null,[]],username:[t?t.username:null,[]],password:[t?t.password:null,[]],automaticRecoveryEnabled:[!!t&&t.automaticRecoveryEnabled,[]],connectionTimeout:[t?t.connectionTimeout:null,[q.min(0)]],handshakeTimeout:[t?t.handshakeTimeout:null,[q.min(0)]],clientProperties:[t?t.clientProperties:null,[]]})}}t("RabbitMqConfigComponent",Ge),Ge.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ge,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ge.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ge,selector:"tb-action-node-rabbit-mq-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.exchange-name-pattern\n \n \n \n tb.rulenode.routing-key-pattern\n \n \n \n tb.rulenode.message-properties\n \n \n {{ property }}\n \n \n \n
\n \n tb.rulenode.host\n \n \n {{ \'tb.rulenode.host-required\' | translate }}\n \n \n \n tb.rulenode.port\n \n \n {{ \'tb.rulenode.port-required\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n {{ \'tb.rulenode.port-range\' | translate }}\n \n \n
\n \n tb.rulenode.virtual-host\n \n \n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n \n {{ \'tb.rulenode.automatic-recovery\' | translate }}\n \n \n tb.rulenode.connection-timeout-ms\n \n \n {{ \'tb.rulenode.min-connection-timeout-ms-message\' | translate }}\n \n \n \n tb.rulenode.handshake-timeout-ms\n \n \n {{ \'tb.rulenode.min-handshake-timeout-ms-message\' | translate }}\n \n \n \n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:_.TogglePasswordComponent,selector:"tb-toggle-password"},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:V.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ge,decorators:[{type:o,args:[{selector:"tb-action-node-rabbit-mq-config",templateUrl:"./rabbit-mq-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class De extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.proxySchemes=["http","https"],this.httpRequestTypes=Object.keys(ee)}configForm(){return this.restApiCallConfigForm}onConfigurationSet(t){this.restApiCallConfigForm=this.fb.group({restEndpointUrlPattern:[t?t.restEndpointUrlPattern:null,[q.required]],requestMethod:[t?t.requestMethod:null,[q.required]],useSimpleClientHttpFactory:[!!t&&t.useSimpleClientHttpFactory,[]],ignoreRequestBody:[!!t&&t.ignoreRequestBody,[]],enableProxy:[!!t&&t.enableProxy,[]],useSystemProxyProperties:[!!t&&t.enableProxy,[]],proxyScheme:[t?t.proxyHost:null,[]],proxyHost:[t?t.proxyHost:null,[]],proxyPort:[t?t.proxyPort:null,[]],proxyUser:[t?t.proxyUser:null,[]],proxyPassword:[t?t.proxyPassword:null,[]],readTimeoutMs:[t?t.readTimeoutMs:null,[]],maxParallelRequestsCount:[t?t.maxParallelRequestsCount:null,[q.min(0)]],headers:[t?t.headers:null,[]],useRedisQueueForMsgPersistence:[!!t&&t.useRedisQueueForMsgPersistence,[]],trimQueue:[!!t&&t.trimQueue,[]],maxQueueSize:[t?t.maxQueueSize:null,[]],credentials:[t?t.credentials:null,[]]})}validatorTriggers(){return["useSimpleClientHttpFactory","useRedisQueueForMsgPersistence","enableProxy","useSystemProxyProperties"]}updateValidators(t){const e=this.restApiCallConfigForm.get("useSimpleClientHttpFactory").value,o=this.restApiCallConfigForm.get("useRedisQueueForMsgPersistence").value,r=this.restApiCallConfigForm.get("enableProxy").value,a=this.restApiCallConfigForm.get("useSystemProxyProperties").value;r&&!a?(this.restApiCallConfigForm.get("proxyHost").setValidators(r?[q.required]:[]),this.restApiCallConfigForm.get("proxyPort").setValidators(r?[q.required,q.min(1),q.max(65535)]:[])):(this.restApiCallConfigForm.get("proxyHost").setValidators([]),this.restApiCallConfigForm.get("proxyPort").setValidators([]),e?this.restApiCallConfigForm.get("readTimeoutMs").setValidators([]):this.restApiCallConfigForm.get("readTimeoutMs").setValidators([q.min(0)])),o?this.restApiCallConfigForm.get("maxQueueSize").setValidators([q.min(0)]):this.restApiCallConfigForm.get("maxQueueSize").setValidators([]),this.restApiCallConfigForm.get("readTimeoutMs").updateValueAndValidity({emitEvent:t}),this.restApiCallConfigForm.get("maxQueueSize").updateValueAndValidity({emitEvent:t}),this.restApiCallConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:t}),this.restApiCallConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:t}),this.restApiCallConfigForm.get("credentials").updateValueAndValidity({emitEvent:t})}}t("RestApiCallConfigComponent",De),De.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:De,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),De.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:De,selector:"tb-action-node-rest-api-call-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.endpoint-url-pattern\n \n \n {{ \'tb.rulenode.endpoint-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.request-method\n \n \n {{ requestType }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n \n {{ \'tb.rulenode.use-simple-client-http-factory\' | translate }}\n \n \n {{ \'tb.rulenode.ignore-request-body\' | translate }}\n \n
\n \n {{ \'tb.rulenode.use-system-proxy-properties\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-scheme\n \n \n {{ proxyScheme }}\n \n \n \n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n
\n \n tb.rulenode.read-timeout\n \n tb.rulenode.read-timeout-hint\n \n \n tb.rulenode.max-parallel-requests-count\n \n tb.rulenode.max-parallel-requests-count-hint\n \n \n
\n \n \n \n {{ \'tb.rulenode.use-redis-queue\' | translate }}\n \n
\n \n {{ \'tb.rulenode.trim-redis-queue\' | translate }}\n \n \n tb.rulenode.redis-queue-max-size\n \n \n
\n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:Ne,selector:"tb-credentials-config",inputs:["required","disableCertPemCredentials","passwordFieldRquired"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:De,decorators:[{type:o,args:[{selector:"tb-action-node-rest-api-call-config",templateUrl:"./rest-api-call-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ve extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.rpcReplyConfigForm}onConfigurationSet(t){this.rpcReplyConfigForm=this.fb.group({requestIdMetaDataAttribute:[t?t.requestIdMetaDataAttribute:null,[]]})}}t("RpcReplyConfigComponent",Ve),Ve.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ve,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ve.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ve,selector:"tb-action-node-rpc-reply-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ve,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-reply-config",templateUrl:"./rpc-reply-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ee extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.rpcRequestConfigForm}onConfigurationSet(t){this.rpcRequestConfigForm=this.fb.group({timeoutInSeconds:[t?t.timeoutInSeconds:null,[q.required,q.min(0)]]})}}t("RpcRequestConfigComponent",Ee),Ee.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ee,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ee.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ee,selector:"tb-action-node-rpc-request-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.timeout-sec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-message\' | translate }}\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ee,decorators:[{type:o,args:[{selector:"tb-action-node-rpc-request-config",templateUrl:"./rpc-request-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Pe extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.saveToCustomTableConfigForm}onConfigurationSet(t){this.saveToCustomTableConfigForm=this.fb.group({tableName:[t?t.tableName:null,[q.required,q.pattern(/.*\S.*/)]],fieldsMapping:[t?t.fieldsMapping:null,[q.required]]})}prepareOutputConfig(t){return t.tableName=t.tableName.trim(),t}}t("SaveToCustomTableConfigComponent",Pe),Pe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Pe,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Pe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Pe,selector:"tb-action-node-custom-table-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.custom-table-name\n \n \n {{ \'tb.rulenode.custom-table-name-required\' | translate }}\n \n tb.rulenode.custom-table-hint\n \n \n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Pe,decorators:[{type:o,args:[{selector:"tb-action-node-custom-table-config",templateUrl:"./save-to-custom-table-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Re extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.smtpProtocols=["smtp","smtps"],this.tlsVersions=["TLSv1","TLSv1.1","TLSv1.2","TLSv1.3"]}configForm(){return this.sendEmailConfigForm}onConfigurationSet(t){this.sendEmailConfigForm=this.fb.group({useSystemSmtpSettings:[!!t&&t.useSystemSmtpSettings,[]],smtpProtocol:[t?t.smtpProtocol:null,[]],smtpHost:[t?t.smtpHost:null,[]],smtpPort:[t?t.smtpPort:null,[]],timeout:[t?t.timeout:null,[]],enableTls:[!!t&&t.enableTls,[]],tlsVersion:[t?t.tlsVersion:null,[]],enableProxy:[!!t&&t.enableProxy,[]],proxyHost:[t?t.proxyHost:null,[]],proxyPort:[t?t.proxyPort:null,[]],proxyUser:[t?t.proxyUser:null,[]],proxyPassword:[t?t.proxyPassword:null,[]],username:[t?t.username:null,[]],password:[t?t.password:null,[]]})}validatorTriggers(){return["useSystemSmtpSettings","enableProxy"]}updateValidators(t){const e=this.sendEmailConfigForm.get("useSystemSmtpSettings").value,o=this.sendEmailConfigForm.get("enableProxy").value;e?(this.sendEmailConfigForm.get("smtpProtocol").setValidators([]),this.sendEmailConfigForm.get("smtpHost").setValidators([]),this.sendEmailConfigForm.get("smtpPort").setValidators([]),this.sendEmailConfigForm.get("timeout").setValidators([]),this.sendEmailConfigForm.get("proxyHost").setValidators([]),this.sendEmailConfigForm.get("proxyPort").setValidators([])):(this.sendEmailConfigForm.get("smtpProtocol").setValidators([q.required]),this.sendEmailConfigForm.get("smtpHost").setValidators([q.required]),this.sendEmailConfigForm.get("smtpPort").setValidators([q.required,q.min(1),q.max(65535)]),this.sendEmailConfigForm.get("timeout").setValidators([q.required,q.min(0)]),this.sendEmailConfigForm.get("proxyHost").setValidators(o?[q.required]:[]),this.sendEmailConfigForm.get("proxyPort").setValidators(o?[q.required,q.min(1),q.max(65535)]:[])),this.sendEmailConfigForm.get("smtpProtocol").updateValueAndValidity({emitEvent:t}),this.sendEmailConfigForm.get("smtpHost").updateValueAndValidity({emitEvent:t}),this.sendEmailConfigForm.get("smtpPort").updateValueAndValidity({emitEvent:t}),this.sendEmailConfigForm.get("timeout").updateValueAndValidity({emitEvent:t}),this.sendEmailConfigForm.get("proxyHost").updateValueAndValidity({emitEvent:t}),this.sendEmailConfigForm.get("proxyPort").updateValueAndValidity({emitEvent:t})}}t("SendEmailConfigComponent",Re),Re.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Re,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Re.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Re,selector:"tb-action-node-send-email-config",usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'tb.rulenode.use-system-smtp-settings\' | translate }}\n \n
\n \n tb.rulenode.smtp-protocol\n \n \n {{ smtpProtocol.toUpperCase() }}\n \n \n \n
\n \n tb.rulenode.smtp-host\n \n \n {{ \'tb.rulenode.smtp-host-required\' | translate }}\n \n \n \n tb.rulenode.smtp-port\n \n \n {{ \'tb.rulenode.smtp-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n {{ \'tb.rulenode.smtp-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.timeout-msec\n \n \n {{ \'tb.rulenode.timeout-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-timeout-msec-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.enable-tls\' | translate }}\n \n \n tb.rulenode.tls-version\n \n \n {{ tlsVersion }}\n \n \n \n \n {{ \'tb.rulenode.enable-proxy\' | translate }}\n \n
\n
\n \n tb.rulenode.proxy-host\n \n \n {{ \'tb.rulenode.proxy-host-required\' | translate }}\n \n \n \n tb.rulenode.proxy-port\n \n \n {{ \'tb.rulenode.proxy-port-required\' | translate }}\n \n \n {{ \'tb.rulenode.proxy-port-range\' | translate }}\n \n \n
\n \n tb.rulenode.proxy-user\n \n \n \n tb.rulenode.proxy-password\n \n \n
\n \n tb.rulenode.username\n \n \n \n tb.rulenode.password\n \n \n \n
\n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:bt.TbCheckboxComponent,selector:"tb-checkbox",inputs:["disabled","trueValue","falseValue"],outputs:["valueChange"]},{type:_.TogglePasswordComponent,selector:"tb-toggle-password"}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:V.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Re,decorators:[{type:o,args:[{selector:"tb-action-node-send-email-config",templateUrl:"./send-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Oe extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.sendSmsConfigForm}onConfigurationSet(t){this.sendSmsConfigForm=this.fb.group({numbersToTemplate:[t?t.numbersToTemplate:null,[q.required]],smsMessageTemplate:[t?t.smsMessageTemplate:null,[q.required]],useSystemSmsSettings:[!!t&&t.useSystemSmsSettings,[]],smsProviderConfiguration:[t?t.smsProviderConfiguration:null,[]]})}validatorTriggers(){return["useSystemSmsSettings"]}updateValidators(t){this.sendSmsConfigForm.get("useSystemSmsSettings").value?this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([]):this.sendSmsConfigForm.get("smsProviderConfiguration").setValidators([q.required]),this.sendSmsConfigForm.get("smsProviderConfiguration").updateValueAndValidity({emitEvent:t})}}t("SendSmsConfigComponent",Oe),Oe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Oe,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Oe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Oe,selector:"tb-action-node-send-sms-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.numbers-to-template\n \n \n {{ \'tb.rulenode.numbers-to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.sms-message-template\n \n \n {{ \'tb.rulenode.sms-message-template-required\' | translate }}\n \n \n \n \n {{ \'tb.rulenode.use-system-sms-settings\' | translate }}\n \n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:ht.SmsProviderConfigurationComponent,selector:"tb-sms-provider-configuration",inputs:["required","disabled"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Oe,decorators:[{type:o,args:[{selector:"tb-action-node-send-sms-config",templateUrl:"./send-sms-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class we extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.snsConfigForm}onConfigurationSet(t){this.snsConfigForm=this.fb.group({topicArnPattern:[t?t.topicArnPattern:null,[q.required]],accessKeyId:[t?t.accessKeyId:null,[q.required]],secretAccessKey:[t?t.secretAccessKey:null,[q.required]],region:[t?t.region:null,[q.required]]})}}t("SnsConfigComponent",we),we.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:we,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),we.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:we,selector:"tb-action-node-sns-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.topic-arn-pattern\n \n \n {{ \'tb.rulenode.topic-arn-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:we,decorators:[{type:o,args:[{selector:"tb-action-node-sns-config",templateUrl:"./sns-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class He extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.sqsQueueType=$t,this.sqsQueueTypes=Object.keys($t),this.sqsQueueTypeTranslationsMap=Wt}configForm(){return this.sqsConfigForm}onConfigurationSet(t){this.sqsConfigForm=this.fb.group({queueType:[t?t.queueType:null,[q.required]],queueUrlPattern:[t?t.queueUrlPattern:null,[q.required]],delaySeconds:[t?t.delaySeconds:null,[q.min(0),q.max(900)]],messageAttributes:[t?t.messageAttributes:null,[]],accessKeyId:[t?t.accessKeyId:null,[q.required]],secretAccessKey:[t?t.secretAccessKey:null,[q.required]],region:[t?t.region:null,[q.required]]})}}t("SqsConfigComponent",He),He.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:He,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),He.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:He,selector:"tb-action-node-sqs-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.queue-type\n \n \n {{ sqsQueueTypeTranslationsMap.get(type) | translate }}\n \n \n \n \n tb.rulenode.queue-url-pattern\n \n \n {{ \'tb.rulenode.queue-url-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.delay-seconds\n \n \n {{ \'tb.rulenode.min-delay-seconds-message\' | translate }}\n \n \n {{ \'tb.rulenode.max-delay-seconds-message\' | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.aws-access-key-id\n \n \n {{ \'tb.rulenode.aws-access-key-id-required\' | translate }}\n \n \n \n tb.rulenode.aws-secret-access-key\n \n \n {{ \'tb.rulenode.aws-secret-access-key-required\' | translate }}\n \n \n \n tb.rulenode.aws-region\n \n \n {{ \'tb.rulenode.aws-region-required\' | translate }}\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:He,decorators:[{type:o,args:[{selector:"tb-action-node-sqs-config",templateUrl:"./sqs-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ue extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.timeseriesConfigForm}onConfigurationSet(t){this.timeseriesConfigForm=this.fb.group({defaultTTL:[t?t.defaultTTL:null,[q.required,q.min(0)]],skipLatestPersistence:[!!t&&t.skipLatestPersistence,[]],useServerTs:[!!t&&t.useServerTs,[]]})}}t("TimeseriesConfigComponent",Ue),Ue.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ue,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ue.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ue,selector:"tb-action-node-timeseries-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.default-ttl\n \n \n {{ \'tb.rulenode.default-ttl-required\' | translate }}\n \n \n {{ \'tb.rulenode.min-default-ttl-message\' | translate }}\n \n \n \n {{ \'tb.rulenode.skip-latest-persistence\' | translate }}\n \n \n {{ \'tb.rulenode.use-server-ts\' | translate }}\n \n
tb.rulenode.use-server-ts-hint
\n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ue,decorators:[{type:o,args:[{selector:"tb-action-node-timeseries-config",templateUrl:"./timeseries-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Be extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.unassignCustomerConfigForm}onConfigurationSet(t){this.unassignCustomerConfigForm=this.fb.group({customerNamePattern:[t?t.customerNamePattern:null,[q.required,q.pattern(/.*\S.*/)]],customerCacheExpiration:[t?t.customerCacheExpiration:null,[q.required,q.min(0)]]})}prepareOutputConfig(t){return t.customerNamePattern=t.customerNamePattern.trim(),t}}t("UnassignCustomerConfigComponent",Be),Be.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Be,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Be.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Be,selector:"tb-action-node-un-assign-to-customer-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.customer-name-pattern\n \n \n {{ \'tb.rulenode.customer-name-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.customer-cache-expiration\n \n \n {{ \'tb.rulenode.customer-cache-expiration-required\' | translate }}\n \n \n {{ \'tb.rulenode.customer-cache-expiration-range\' | translate }}\n \n tb.rulenode.customer-cache-expiration-hint\n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Be,decorators:[{type:o,args:[{selector:"tb-action-node-un-assign-to-customer-config",templateUrl:"./unassign-customer-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ke extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.attributeScopes=Object.keys(m),this.telemetryTypeTranslationsMap=u,this.separatorKeysCodes=[et,ot,rt]}configForm(){return this.deleteAttributesConfigForm}onConfigurationSet(t){this.deleteAttributesConfigForm=this.fb.group({scope:[t?t.scope:null,[q.required]],keys:[t?t.keys:null,[q.required]]})}removeKey(t){const e=this.deleteAttributesConfigForm.get("keys").value,o=e.indexOf(t);o>=0&&(e.splice(o,1),this.deleteAttributesConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}addKey(t){const e=t.input;let o=t.value;if((o||"").trim()){o=o.trim();let t=this.deleteAttributesConfigForm.get("keys").value;t&&-1!==t.indexOf(o)||(t||(t=[]),t.push(o),this.deleteAttributesConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}e&&(e.value="")}}t("DeleteAttributesConfigComponent",Ke),Ke.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ke,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ke.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ke,selector:"tb-action-node-delete-attributes-config",viewQueries:[{propertyName:"attributeChipList",first:!0,predicate:["attributeChipList"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'attribute.attributes-scope\' | translate }}\n \n \n {{ telemetryTypeTranslationsMap.get(scope) | translate }}\n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.attributes-keys-required\' | translate }}\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ke,decorators:[{type:o,args:[{selector:"tb-action-node-delete-attributes-config",templateUrl:"./delete-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]},propDecorators:{attributeChipList:[{type:a,args:["attributeChipList"]}]}});class je extends b{constructor(t,e,o,r){super(t),this.store=t,this.translate=e,this.injector=o,this.fb=r,this.searchText="",this.dirty=!1,this.mathOperation=[...ne.values()],this.propagateChange=null}get required(){return this.requiredValue}set required(t){this.requiredValue=st(t)}ngOnInit(){this.mathFunctionForm=this.fb.group({operation:[""]}),this.filteredOptions=this.mathFunctionForm.get("operation").valueChanges.pipe(ft((t=>{let e;e="string"==typeof t&&ae[t]?ae[t]:null,this.updateView(e)})),ct((t=>(this.searchText=t||"",t?this._filter(t):this.mathOperation.slice()))))}_filter(t){const e=t.toLowerCase();return this.mathOperation.filter((t=>t.name.toLowerCase().includes(e)||t.value.toLowerCase().includes(e)))}registerOnChange(t){this.propagateChange=t}registerOnTouched(t){}setDisabledState(t){this.disabled=t,this.disabled?this.mathFunctionForm.disable({emitEvent:!1}):this.mathFunctionForm.enable({emitEvent:!1})}mathFunctionDisplayFn(t){if(t){const e=ne.get(t);return e.value+" | "+e.name}return""}writeValue(t){this.modelValue=t,this.mathFunctionForm.get("operation").setValue(t,{emitEvent:!1}),this.dirty=!0}updateView(t){this.modelValue!==t&&(this.modelValue=t,this.propagateChange(this.modelValue))}onFocus(){this.dirty&&(this.mathFunctionForm.get("operation").updateValueAndValidity({onlySelf:!0}),this.dirty=!1)}clear(){this.mathFunctionForm.get("operation").patchValue(""),setTimeout((()=>{this.operationInput.nativeElement.blur(),this.operationInput.nativeElement.focus()}),0)}}t("MathFunctionAutocompleteComponent",je),je.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:je,deps:[{token:k.Store},{token:R.TranslateService},{token:e.Injector},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),je.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:je,selector:"tb-math-function-autocomplete",inputs:{required:"required",disabled:"disabled"},providers:[{provide:A,useExisting:n((()=>je)),multi:!0}],viewQueries:[{propertyName:"operationInput",first:!0,predicate:["operationInput"],descendants:!0,static:!0}],usesInheritance:!0,ngImport:e,template:'\n tb.rulenode.functions-field-input\n \n \n \n \n \n \n {{ option.description }}\n \n \n \n tb.rulenode.no-option-found\n \n \n\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Ct.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Ct.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatSuffix,selector:"[matSuffix]"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{async:w.AsyncPipe,highlight:Ft.HighlightPipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:je,decorators:[{type:o,args:[{selector:"tb-math-function-autocomplete",templateUrl:"./math-function-autocomplete.component.html",styleUrls:[],providers:[{provide:A,useExisting:n((()=>je)),multi:!0}]}]}],ctorParameters:function(){return[{type:k.Store},{type:R.TranslateService},{type:e.Injector},{type:M.FormBuilder}]},propDecorators:{required:[{type:l}],disabled:[{type:l}],operationInput:[{type:a,args:["operationInput",{static:!0}]}]}});class _e extends b{constructor(t,e,o,r){super(t),this.store=t,this.translate=e,this.injector=o,this.fb=r,this.maxArgs=16,this.minArgs=1,this.displayArgumentName=!1,this.mathFunctionMap=ne,this.ArgumentType=le,this.attributeScopeMap=de,this.argumentTypeResultMap=se,this.arguments=Object.values(le),this.attributeScope=Object.values(ue),this.propagateChange=null,this.valueChangeSubscription=[]}get function(){return this.functionValue}set function(t){t&&this.functionValue!==t&&(this.functionValue=t,this.setupArgumentsFormGroup())}ngOnInit(){this.ngControl=this.injector.get(S),null!=this.ngControl&&(this.ngControl.valueAccessor=this),this.argumentsFormGroup=this.fb.group({}),this.argumentsFormGroup.addControl("arguments",this.fb.array([])),this.setupArgumentsFormGroup()}onDrop(t){const e=this.argumentsFormArray(),o=e.at(t.previousIndex);e.removeAt(t.previousIndex),e.insert(t.currentIndex,o),this.updateArgumentNames()}argumentsFormArray(){return this.argumentsFormGroup.get("arguments")}registerOnChange(t){this.propagateChange=t}registerOnTouched(t){}setDisabledState(t){this.disabled=t,this.disabled?this.argumentsFormGroup.disable({emitEvent:!1}):this.argumentsFormGroup.enable({emitEvent:!1})}ngOnDestroy(){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((t=>t.unsubscribe()))}writeValue(t){this.valueChangeSubscription.length&&this.valueChangeSubscription.forEach((t=>t.unsubscribe()));const e=[];t&&t.forEach(((t,o)=>{e.push(this.createArgumentControl(t,o))})),this.argumentsFormGroup.setControl("arguments",this.fb.array(e)),this.setupArgumentsFormGroup(),this.valueChangeSubscription.push(this.argumentsFormGroup.valueChanges.subscribe((()=>{this.updateModel()})))}removeArgument(t){this.argumentsFormGroup.get("arguments").removeAt(t),this.updateArgumentNames()}addArgument(){const t=this.argumentsFormGroup.get("arguments"),e=this.createArgumentControl(null,t.length);t.push(e)}validate(t){return this.argumentsFormGroup.valid?null:{argumentsRequired:!0}}setupArgumentsFormGroup(){if(this.function&&(this.maxArgs=this.mathFunctionMap.get(this.function).maxArgs,this.minArgs=this.mathFunctionMap.get(this.function).minArgs,this.displayArgumentName=this.function===ae.CUSTOM),this.argumentsFormGroup){for(this.argumentsFormGroup.get("arguments").setValidators([q.minLength(this.minArgs),q.maxLength(this.maxArgs)]),this.argumentsFormGroup.get("arguments").value.length>this.maxArgs&&(this.argumentsFormGroup.get("arguments").controls.length=this.maxArgs);this.argumentsFormGroup.get("arguments").value.length{this.updateArgumentControlValidators(o),o.get("attributeScope").updateValueAndValidity({emitEvent:!0}),o.get("defaultValue").updateValueAndValidity({emitEvent:!0})}))),o}updateArgumentControlValidators(t){const e=t.get("type").value;e===le.ATTRIBUTE?t.get("attributeScope").enable():t.get("attributeScope").disable(),e&&e!==le.CONSTANT?t.get("defaultValue").enable():t.get("defaultValue").disable()}updateArgumentNames(){this.argumentsFormGroup.get("arguments").controls.forEach(((t,e)=>{t.get("name").setValue(me[e])}))}updateModel(){const t=this.argumentsFormGroup.get("arguments").value;t.length&&this.argumentsFormGroup.valid?this.propagateChange(t):this.propagateChange(null)}}t("ArgumentsMapConfigComponent",_e),_e.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:_e,deps:[{token:k.Store},{token:R.TranslateService},{token:e.Injector},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),_e.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:_e,selector:"tb-arguments-map-config",inputs:{disabled:"disabled",function:"function"},providers:[{provide:A,useExisting:n((()=>_e)),multi:!0},{provide:G,useExisting:n((()=>_e)),multi:!0}],usesInheritance:!0,ngImport:e,template:'
\n\n
\n \n \n
\n \n
\n {{argumentControl.get(\'name\').value}}.\n
\n
\n \n tb.rulenode.argument-type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.argument-type-field-input-required\n \n \n \n tb.rulenode.argument-key-field-input\n \n \n tb.rulenode.argument-key-field-input-required\n \n \n \n tb.rulenode.constant-value-field-input\n \n \n tb.rulenode.constant-value-field-input-required\n \n \n
\n
\n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n tb.rulenode.attribute-scope-field-input-required\n \n \n \n tb.rulenode.default-value-field-input\n \n \n
\n
\n \n
\n
\n
\n
\n
\n
\n tb.rulenode.no-arguments-prompt\n
\n
\n \n
\n
\n',styles:[":host mat-list-item.tb-argument{border:solid rgba(0,0,0,.25) 1px;border-radius:4px;padding:10px 0;margin-bottom:10px}\n"],components:[{type:Lt.MatList,selector:"mat-list, mat-action-list",inputs:["disableRipple","disabled"],exportAs:["matList"]},{type:Lt.MatListItem,selector:"mat-list-item, a[mat-list-item], button[mat-list-item]",inputs:["disableRipple","disabled"],exportAs:["matListItem"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:w.NgClass,selector:"[ngClass]",inputs:["class","ngClass"]},{type:ut.DefaultClassDirective,selector:" [ngClass], [ngClass.xs], [ngClass.sm], [ngClass.md], [ngClass.lg], [ngClass.xl], [ngClass.lt-sm], [ngClass.lt-md], [ngClass.lt-lg], [ngClass.lt-xl], [ngClass.gt-xs], [ngClass.gt-sm], [ngClass.gt-md], [ngClass.gt-lg]",inputs:["ngClass","ngClass.xs","ngClass.sm","ngClass.md","ngClass.lg","ngClass.xl","ngClass.lt-sm","ngClass.lt-md","ngClass.lt-lg","ngClass.lt-xl","ngClass.gt-xs","ngClass.gt-sm","ngClass.gt-md","ngClass.gt-lg"]},{type:vt.CdkDropList,selector:"[cdkDropList], cdk-drop-list",inputs:["cdkDropListConnectedTo","id","cdkDropListEnterPredicate","cdkDropListSortPredicate","cdkDropListDisabled","cdkDropListSortingDisabled","cdkDropListAutoScrollDisabled","cdkDropListOrientation","cdkDropListLockAxis","cdkDropListData","cdkDropListAutoScrollStep"],outputs:["cdkDropListDropped","cdkDropListEntered","cdkDropListExited","cdkDropListSorted"],exportAs:["cdkDropList"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:M.FormArrayName,selector:"[formArrayName]",inputs:["formArrayName"]},{type:vt.CdkDrag,selector:"[cdkDrag]",inputs:["cdkDragDisabled","cdkDragStartDelay","cdkDragLockAxis","cdkDragConstrainPosition","cdkDragPreviewClass","cdkDragBoundary","cdkDragRootElement","cdkDragPreviewContainer","cdkDragData","cdkDragFreeDragPosition"],outputs:["cdkDragStarted","cdkDragReleased","cdkDragEnded","cdkDragEntered","cdkDragExited","cdkDragDropped","cdkDragMoved"],exportAs:["cdkDrag"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:P.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:vt.CdkDragHandle,selector:"[cdkDragHandle]",inputs:["cdkDragHandleDisabled"]},{type:pt.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:_e,decorators:[{type:o,args:[{selector:"tb-arguments-map-config",templateUrl:"./arguments-map-config.component.html",styleUrls:["./arguments-map-config.component.scss"],providers:[{provide:A,useExisting:n((()=>_e)),multi:!0},{provide:G,useExisting:n((()=>_e)),multi:!0}]}]}],ctorParameters:function(){return[{type:k.Store},{type:R.TranslateService},{type:e.Injector},{type:M.FormBuilder}]},propDecorators:{disabled:[{type:l}],function:[{type:l}]}});class ze extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.MathFunction=ae,this.ArgumentTypeResult=ie,this.argumentTypeResultMap=se,this.attributeScopeMap=de,this.argumentsResult=Object.values(ie),this.attributeScopeResult=Object.values(pe)}configForm(){return this.mathFunctionConfigForm}onConfigurationSet(t){this.mathFunctionConfigForm=this.fb.group({operation:[t?t.operation:null,[q.required]],arguments:[t?t.arguments:null,[q.required]],customFunction:[t?t.customFunction:"",[q.required]],result:this.fb.group({type:[t?t.result.type:null,[q.required]],attributeScope:[t?t.result.attributeScope:null],key:[t?t.result.key:"",[q.required]],resultValuePrecision:[t?t.result.resultValuePrecision:0],addToBody:[!!t&&t.result.addToBody],addToMetadata:[!!t&&t.result.addToMetadata]})})}updateValidators(t){const e=this.mathFunctionConfigForm.get("operation").value,o=this.mathFunctionConfigForm.get("result").get("type").value;e===ae.CUSTOM?this.mathFunctionConfigForm.get("customFunction").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("customFunction").disable({emitEvent:!1}),o===ie.ATTRIBUTE?this.mathFunctionConfigForm.get("result").get("attributeScope").enable({emitEvent:!1}):this.mathFunctionConfigForm.get("result").get("attributeScope").disable({emitEvent:!1}),this.mathFunctionConfigForm.get("customFunction").updateValueAndValidity({emitEvent:t}),this.mathFunctionConfigForm.get("result").get("attributeScope").updateValueAndValidity({emitEvent:t})}validatorTriggers(){return["operation","result.type"]}}t("MathFunctionConfigComponent",ze),ze.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ze,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ze.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ze,selector:"tb-action-node-math-function-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n
\n tb.rulenode.argument-tile\n \n \n
\n
\n {{\'tb.rulenode.custom-expression-field-input\' | translate }} *\n \n \n \n tb.rulenode.custom-expression-field-input-required\n \n \n \n
\n
\n tb.rulenode.result-title\n
\n
\n \n tb.rulenode.type-field-input\n \n \n {{ argumentTypeResultMap.get(argument) | translate }}\n \n \n \n tb.rulenode.type-field-input-required\n \n \n \n tb.rulenode.attribute-scope-field-input\n \n \n {{ attributeScopeMap.get(scope) | translate }}\n \n \n \n \n tb.rulenode.key-field-input\n \n \n tb.rulenode.key-field-input-required\n \n \n
\n
\n \n tb.rulenode.number-floating-point-field-input\n \n \n \n
\n
\n
\n \n {{\'tb.rulenode.add-to-body-field-input\' | translate }}\n \n \n {{\'tb.rulenode.add-to-metadata-field-input\' | translate}}\n \n
\n
\n
\n
\n',styles:[":host ::ng-deep .fields-group{padding:0 16px 8px;margin:10px 0;border:1px groove rgba(0,0,0,.25);border-radius:4px}:host ::ng-deep .fields-group legend{color:#000000b3;width:-moz-fit-content;width:fit-content}:host ::ng-deep .fields-group legend+*{display:block;margin-top:16px}:host ::ng-deep .fields-group legend+*.no-margin-top{margin-top:0}\n"],components:[{type:je,selector:"tb-math-function-autocomplete",inputs:["required","disabled"]},{type:_e,selector:"tb-arguments-map-config",inputs:["disabled","function"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{type:V.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:P.DefaultLayoutAlignDirective,selector:" [fxLayoutAlign], [fxLayoutAlign.xs], [fxLayoutAlign.sm], [fxLayoutAlign.md], [fxLayoutAlign.lg], [fxLayoutAlign.xl], [fxLayoutAlign.lt-sm], [fxLayoutAlign.lt-md], [fxLayoutAlign.lt-lg], [fxLayoutAlign.lt-xl], [fxLayoutAlign.gt-xs], [fxLayoutAlign.gt-sm], [fxLayoutAlign.gt-md], [fxLayoutAlign.gt-lg]",inputs:["fxLayoutAlign","fxLayoutAlign.xs","fxLayoutAlign.sm","fxLayoutAlign.md","fxLayoutAlign.lg","fxLayoutAlign.xl","fxLayoutAlign.lt-sm","fxLayoutAlign.lt-md","fxLayoutAlign.lt-lg","fxLayoutAlign.lt-xl","fxLayoutAlign.gt-xs","fxLayoutAlign.gt-sm","fxLayoutAlign.gt-md","fxLayoutAlign.gt-lg"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ze,decorators:[{type:o,args:[{selector:"tb-action-node-math-function-config",templateUrl:"./math-function-config.component.html",styleUrls:["./math-function-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Je extends b{constructor(t,e){super(t),this.store=t,this.fb=e,this.directionTypes=Object.keys(g),this.directionTypeTranslations=x,this.entityType=y,this.propagateChange=null}get required(){return this.requiredValue}set required(t){this.requiredValue=st(t)}ngOnInit(){this.deviceRelationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[q.required]],maxLevel:[null,[]],relationType:[null],deviceTypes:[null,[q.required]]}),this.deviceRelationsQueryFormGroup.valueChanges.subscribe((t=>{this.deviceRelationsQueryFormGroup.valid?this.propagateChange(t):this.propagateChange(null)}))}registerOnChange(t){this.propagateChange=t}registerOnTouched(t){}setDisabledState(t){this.disabled=t,this.disabled?this.deviceRelationsQueryFormGroup.disable({emitEvent:!1}):this.deviceRelationsQueryFormGroup.enable({emitEvent:!1})}writeValue(t){this.deviceRelationsQueryFormGroup.reset(t,{emitEvent:!1})}}t("DeviceRelationsQueryConfigComponent",Je),Je.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Je,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Je.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Je,selector:"tb-device-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:A,useExisting:n((()=>Je)),multi:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-type
\n \n \n
device.device-types
\n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:Nt.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]},{type:Tt.EntitySubTypeListComponent,selector:"tb-entity-subtype-list",inputs:["required","disabled","entityType"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Je,decorators:[{type:o,args:[{selector:"tb-device-relations-query-config",templateUrl:"./device-relations-query-config.component.html",styleUrls:[],providers:[{provide:A,useExisting:n((()=>Je)),multi:!0}]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class Qe extends b{constructor(t,e){super(t),this.store=t,this.fb=e,this.directionTypes=Object.keys(g),this.directionTypeTranslations=x,this.propagateChange=null}get required(){return this.requiredValue}set required(t){this.requiredValue=st(t)}ngOnInit(){this.relationsQueryFormGroup=this.fb.group({fetchLastLevelOnly:[!1,[]],direction:[null,[q.required]],maxLevel:[null,[]],filters:[null]}),this.relationsQueryFormGroup.valueChanges.subscribe((t=>{this.relationsQueryFormGroup.valid?this.propagateChange(t):this.propagateChange(null)}))}registerOnChange(t){this.propagateChange=t}registerOnTouched(t){}setDisabledState(t){this.disabled=t,this.disabled?this.relationsQueryFormGroup.disable({emitEvent:!1}):this.relationsQueryFormGroup.enable({emitEvent:!1})}writeValue(t){this.relationsQueryFormGroup.reset(t||{},{emitEvent:!1})}}t("RelationsQueryConfigComponent",Qe),Qe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Qe,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Qe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Qe,selector:"tb-relations-query-config",inputs:{disabled:"disabled",required:"required"},providers:[{provide:A,useExisting:n((()=>Qe)),multi:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'alias.last-level-relation\' | translate }}\n \n
\n \n relation.direction\n \n \n {{ directionTypeTranslations.get(type) | translate }}\n \n \n \n \n tb.rulenode.max-relation-level\n \n \n
\n
relation.relation-filters
\n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:kt.RelationFiltersComponent,selector:"tb-relation-filters",inputs:["disabled","allowedEntityTypes"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Qe,decorators:[{type:o,args:[{selector:"tb-relations-query-config",templateUrl:"./relations-query-config.component.html",styleUrls:[],providers:[{provide:A,useExisting:n((()=>Qe)),multi:!0}]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]},propDecorators:{disabled:[{type:l}],required:[{type:l}]}});class $e extends b{constructor(t,e,o,r){super(t),this.store=t,this.translate=e,this.truncate=o,this.fb=r,this.placeholder="tb.rulenode.message-type",this.separatorKeysCodes=[et,ot,rt],this.messageTypes=[],this.messageTypesList=[],this.searchText="",this.propagateChange=t=>{},this.messageTypeConfigForm=this.fb.group({messageType:[null]});for(const t of Object.keys(h))this.messageTypesList.push({name:C.get(h[t]),value:t})}get required(){return this.requiredValue}set required(t){this.requiredValue=st(t)}registerOnChange(t){this.propagateChange=t}registerOnTouched(t){}ngOnInit(){this.filteredMessageTypes=this.messageTypeConfigForm.get("messageType").valueChanges.pipe(gt(""),ct((t=>t||"")),xt((t=>this.fetchMessageTypes(t))),yt())}ngAfterViewInit(){}setDisabledState(t){this.disabled=t,this.disabled?this.messageTypeConfigForm.disable({emitEvent:!1}):this.messageTypeConfigForm.enable({emitEvent:!1})}writeValue(t){this.searchText="",this.messageTypes.length=0,t&&t.forEach((t=>{const e=this.messageTypesList.find((e=>e.value===t));e?this.messageTypes.push({name:e.name,value:e.value}):this.messageTypes.push({name:t,value:t})}))}displayMessageTypeFn(t){return t?t.name:void 0}textIsNotEmpty(t){return!!(t&&null!=t&&t.length>0)}createMessageType(t,e){t.preventDefault(),this.transformMessageType(e)}add(t){this.transformMessageType(t.value)}fetchMessageTypes(t){if(this.searchText=t,this.searchText&&this.searchText.length){const t=this.searchText.toUpperCase();return Mt(this.messageTypesList.filter((e=>e.name.toUpperCase().includes(t))))}return Mt(this.messageTypesList)}transformMessageType(t){if((t||"").trim()){let e=null;const o=t.trim(),r=this.messageTypesList.find((t=>t.name===o));e=r?{name:r.name,value:r.value}:{name:o,value:o},e&&this.addMessageType(e)}this.clear("")}remove(t){const e=this.messageTypes.indexOf(t);e>=0&&(this.messageTypes.splice(e,1),this.updateModel())}selected(t){this.addMessageType(t.option.value),this.clear("")}addMessageType(t){-1===this.messageTypes.findIndex((e=>e.value===t.value))&&(this.messageTypes.push(t),this.updateModel())}onFocus(){this.messageTypeConfigForm.get("messageType").updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(t=""){this.messageTypeInput.nativeElement.value=t,this.messageTypeConfigForm.get("messageType").patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.messageTypeInput.nativeElement.blur(),this.messageTypeInput.nativeElement.focus()}),0)}updateModel(){const t=this.messageTypes.map((t=>t.value));this.required?(this.chipList.errorState=!t.length,this.propagateChange(t.length>0?t:null)):(this.chipList.errorState=!1,this.propagateChange(t))}}t("MessageTypesConfigComponent",$e),$e.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:$e,deps:[{token:k.Store},{token:R.TranslateService},{token:F.TruncatePipe},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),$e.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:$e,selector:"tb-message-types-config",inputs:{required:"required",label:"label",placeholder:"placeholder",disabled:"disabled"},providers:[{provide:A,useExisting:n((()=>$e)),multi:!0}],viewQueries:[{propertyName:"chipList",first:!0,predicate:["chipList"],descendants:!0},{propertyName:"matAutocomplete",first:!0,predicate:["messageTypeAutocomplete"],descendants:!0},{propertyName:"messageTypeInput",first:!0,predicate:["messageTypeInput"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'\n {{ label }}\n \n \n {{messageType.name}}\n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-message-types-found\n
\n \n \n {{ translate.get(\'tb.rulenode.no-message-type-matching\',\n {messageType: truncate.transform(searchText, true, 6, '...')}) | async }}\n \n \n \n tb.rulenode.create-new-message-type\n \n
\n
\n
\n \n {{ \'tb.rulenode.message-types-required\' | translate }}\n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Ct.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Ct.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:Ct.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe,async:w.AsyncPipe,highlight:Ft.HighlightPipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:$e,decorators:[{type:o,args:[{selector:"tb-message-types-config",templateUrl:"./message-types-config.component.html",styleUrls:[],providers:[{provide:A,useExisting:n((()=>$e)),multi:!0}]}]}],ctorParameters:function(){return[{type:k.Store},{type:R.TranslateService},{type:F.TruncatePipe},{type:M.FormBuilder}]},propDecorators:{required:[{type:l}],label:[{type:l}],placeholder:[{type:l}],disabled:[{type:l}],chipList:[{type:a,args:["chipList",{static:!1}]}],matAutocomplete:[{type:a,args:["messageTypeAutocomplete",{static:!1}]}],messageTypeInput:[{type:a,args:["messageTypeInput",{static:!1}]}]}});class We{}t("RulenodeCoreConfigCommonModule",We),We.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:We,deps:[],target:e.ɵɵFactoryTarget.NgModule}),We.ɵmod=e.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:We,declarations:[Le,Je,Qe,$e,Ne,Vt,_e,je],imports:[H,L,It],exports:[Le,Je,Qe,$e,Ne,Vt,_e,je]}),We.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:We,imports:[[H,L,It]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:We,decorators:[{type:i,args:[{declarations:[Le,Je,Qe,$e,Ne,Vt,_e,je],imports:[H,L,It],exports:[Le,Je,Qe,$e,Ne,Vt,_e,je]}]}]});class Ye{}t("RuleNodeCoreConfigActionModule",Ye),Ye.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ye,deps:[],target:e.ɵɵFactoryTarget.NgModule}),Ye.ɵmod=e.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ye,declarations:[Ke,Pt,Ue,Ee,Ie,Et,ge,xe,ye,Me,be,Ce,Fe,ke,Ve,Pe,Be,we,He,qe,ve,Te,Ge,De,Re,ce,fe,he,Oe,Ae,Se,ze],imports:[H,L,It,We],exports:[Ke,Pt,Ue,Ee,Ie,Et,ge,xe,ye,Me,be,Ce,Fe,ke,Ve,Pe,Be,we,He,qe,ve,Te,Ge,De,Re,ce,fe,he,Oe,Ae,Se,ze]}),Ye.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ye,imports:[[H,L,It,We]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ye,decorators:[{type:i,args:[{declarations:[Ke,Pt,Ue,Ee,Ie,Et,ge,xe,ye,Me,be,Ce,Fe,ke,Ve,Pe,Be,we,He,qe,ve,Te,Ge,De,Re,ce,fe,he,Oe,Ae,Se,ze],imports:[H,L,It,We],exports:[Ke,Pt,Ue,Ee,Ie,Et,ge,xe,ye,Me,be,Ce,Fe,ke,Ve,Pe,Be,we,He,qe,ve,Te,Ge,De,Re,ce,fe,he,Oe,Ae,Se,ze]}]}]});class Xe extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.separatorKeysCodes=[et,ot,rt]}configForm(){return this.calculateDeltaConfigForm}onConfigurationSet(t){this.calculateDeltaConfigForm=this.fb.group({inputValueKey:[t?t.inputValueKey:null,[q.required]],outputValueKey:[t?t.outputValueKey:null,[q.required]],useCache:[t?t.useCache:null,[]],addPeriodBetweenMsgs:[!!t&&t.addPeriodBetweenMsgs,[]],periodValueKey:[t?t.periodValueKey:null,[]],round:[t?t.round:null,[q.min(0),q.max(15)]],tellFailureIfDeltaIsNegative:[t?t.tellFailureIfDeltaIsNegative:null,[]]})}updateValidators(t){this.calculateDeltaConfigForm.get("addPeriodBetweenMsgs").value?this.calculateDeltaConfigForm.get("periodValueKey").setValidators([q.required]):this.calculateDeltaConfigForm.get("periodValueKey").setValidators([]),this.calculateDeltaConfigForm.get("periodValueKey").updateValueAndValidity({emitEvent:t})}validatorTriggers(){return["addPeriodBetweenMsgs"]}}t("CalculateDeltaConfigComponent",Xe),Xe.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Xe,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Xe.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Xe,selector:"tb-enrichment-node-calculate-delta-config",usesInheritance:!0,ngImport:e,template:'
\n
\n \n tb.rulenode.input-value-key\n \n \n {{ \'tb.rulenode.input-value-key-required\' | translate }}\n \n \n \n tb.rulenode.output-value-key\n \n \n {{ \'tb.rulenode.output-value-key-required\' | translate }}\n \n \n \n tb.rulenode.round\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n {{ \'tb.rulenode.round-range\' | translate }}\n \n \n
\n \n {{ \'tb.rulenode.use-cache\' | translate }}\n \n \n {{ \'tb.rulenode.tell-failure-if-delta-is-negative\' | translate }}\n \n \n {{ \'tb.rulenode.add-period-between-msgs\' | translate }}\n \n \n tb.rulenode.period-value-key\n \n \n {{ \'tb.rulenode.period-value-key-required\' | translate }}\n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Xe,decorators:[{type:o,args:[{selector:"tb-enrichment-node-calculate-delta-config",templateUrl:"./calculate-delta-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Ze extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.customerAttributesConfigForm}onConfigurationSet(t){this.customerAttributesConfigForm=this.fb.group({telemetry:[!!t&&t.telemetry,[]],attrMapping:[t?t.attrMapping:null,[q.required]]})}}t("CustomerAttributesConfigComponent",Ze),Ze.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ze,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Ze.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Ze,selector:"tb-enrichment-node-customer-attributes-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ze,decorators:[{type:o,args:[{selector:"tb-enrichment-node-customer-attributes-config",templateUrl:"./customer-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class to extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.separatorKeysCodes=[et,ot,rt]}configForm(){return this.deviceAttributesConfigForm}onConfigurationSet(t){this.deviceAttributesConfigForm=this.fb.group({deviceRelationsQuery:[t?t.deviceRelationsQuery:null,[q.required]],tellFailureIfAbsent:[!!t&&t.tellFailureIfAbsent,[]],fetchToData:[!!t&&t.fetchToData,[]],clientAttributeNames:[t?t.clientAttributeNames:null,[]],sharedAttributeNames:[t?t.sharedAttributeNames:null,[]],serverAttributeNames:[t?t.serverAttributeNames:null,[]],latestTsKeyNames:[t?t.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!t&&t.getLatestValueWithTs,[]]})}removeKey(t,e){const o=this.deviceAttributesConfigForm.get(e).value,r=o.indexOf(t);r>=0&&(o.splice(r,1),this.deviceAttributesConfigForm.get(e).setValue(o,{emitEvent:!0}))}addKey(t,e){const o=t.input;let r=t.value;if((r||"").trim()){r=r.trim();let t=this.deviceAttributesConfigForm.get(e).value;t&&-1!==t.indexOf(r)||(t||(t=[]),t.push(r),this.deviceAttributesConfigForm.get(e).setValue(t,{emitEvent:!0}))}o&&(o.value="")}}t("DeviceAttributesConfigComponent",to),to.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:to,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),to.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:to,selector:"tb-enrichment-node-device-attributes-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n
{{ \'tb.rulenode.fetch-into\' | translate }}
\n \n \n {{ \'tb.rulenode.data\' | translate }}\n \n \n {{ \'tb.rulenode.metadata\' | translate }}\n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:Je,selector:"tb-device-relations-query-config",inputs:["disabled","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:qt.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:qt.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:to,decorators:[{type:o,args:[{selector:"tb-enrichment-node-device-attributes-config",templateUrl:"./device-attributes-config.component.html",styleUrls:["./device-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class eo extends s{constructor(t,e,o){super(t),this.store=t,this.translate=e,this.fb=o,this.entityDetailsTranslationsMap=zt,this.entityDetailsList=[],this.searchText="",this.displayDetailsFn=this.displayDetails.bind(this);for(const t of Object.keys(_t))this.entityDetailsList.push(_t[t]);this.detailsFormControl=new D(""),this.filteredEntityDetails=this.detailsFormControl.valueChanges.pipe(gt(""),ct((t=>t||"")),xt((t=>this.fetchEntityDetails(t))),yt())}ngOnInit(){super.ngOnInit()}configForm(){return this.entityDetailsConfigForm}prepareInputConfig(t){return this.searchText="",this.detailsFormControl.patchValue("",{emitEvent:!0}),t}onConfigurationSet(t){this.entityDetailsConfigForm=this.fb.group({detailsList:[t?t.detailsList:null,[q.required]],addToMetadata:[!!t&&t.addToMetadata,[]]})}displayDetails(t){return t?this.translate.instant(zt.get(t)):void 0}fetchEntityDetails(t){if(this.searchText=t,this.searchText&&this.searchText.length){const t=this.searchText.toUpperCase();return Mt(this.entityDetailsList.filter((e=>this.translate.instant(zt.get(_t[e])).toUpperCase().includes(t))))}return Mt(this.entityDetailsList)}detailsFieldSelected(t){this.addDetailsField(t.option.value),this.clear("")}removeDetailsField(t){const e=this.entityDetailsConfigForm.get("detailsList").value;if(e){const o=e.indexOf(t);o>=0&&(e.splice(o,1),this.entityDetailsConfigForm.get("detailsList").setValue(e))}}addDetailsField(t){let e=this.entityDetailsConfigForm.get("detailsList").value;e||(e=[]);-1===e.indexOf(t)&&(e.push(t),this.entityDetailsConfigForm.get("detailsList").setValue(e))}onEntityDetailsInputFocus(){this.detailsFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(t=""){this.detailsInput.nativeElement.value=t,this.detailsFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.detailsInput.nativeElement.blur(),this.detailsInput.nativeElement.focus()}),0)}}t("EntityDetailsConfigComponent",eo),eo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:eo,deps:[{token:k.Store},{token:R.TranslateService},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),eo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:eo,selector:"tb-enrichment-node-entity-details-config",viewQueries:[{propertyName:"detailsInput",first:!0,predicate:["detailsInput"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.entity-details\n \n \n \n {{entityDetailsTranslationsMap.get(details) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-entity-details-matching\n
\n
\n
\n
\n
\n \n \n {{ \'tb.rulenode.add-to-metadata\' | translate }}\n \n
tb.rulenode.add-to-metadata-hint
\n
\n',styles:[":host ::ng-deep mat-form-field.entity-fields-list .mat-form-field-wrapper{margin-bottom:-1.25em}\n"],components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Ct.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:mt.TbErrorComponent,selector:"tb-error",inputs:["error"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Ct.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:Ct.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe,async:w.AsyncPipe,highlight:Ft.HighlightPipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:eo,decorators:[{type:o,args:[{selector:"tb-enrichment-node-entity-details-config",templateUrl:"./entity-details-config.component.html",styleUrls:["./entity-details-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:R.TranslateService},{type:M.FormBuilder}]},propDecorators:{detailsInput:[{type:a,args:["detailsInput",{static:!1}]}]}});class oo extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.separatorKeysCodes=[et,ot,rt],this.aggregationTypes=v,this.aggregations=Object.keys(v),this.aggregationTypesTranslations=I,this.fetchMode=Jt,this.fetchModes=Object.keys(Jt),this.samplingOrders=Object.keys(Qt),this.timeUnits=Object.values(Ut),this.timeUnitsTranslationMap=Bt}configForm(){return this.getTelemetryFromDatabaseConfigForm}onConfigurationSet(t){this.getTelemetryFromDatabaseConfigForm=this.fb.group({latestTsKeyNames:[t?t.latestTsKeyNames:null,[]],aggregation:[t?t.aggregation:null,[q.required]],fetchMode:[t?t.fetchMode:null,[q.required]],orderBy:[t?t.orderBy:null,[]],limit:[t?t.limit:null,[]],useMetadataIntervalPatterns:[!!t&&t.useMetadataIntervalPatterns,[]],startInterval:[t?t.startInterval:null,[]],startIntervalTimeUnit:[t?t.startIntervalTimeUnit:null,[]],endInterval:[t?t.endInterval:null,[]],endIntervalTimeUnit:[t?t.endIntervalTimeUnit:null,[]],startIntervalPattern:[t?t.startIntervalPattern:null,[]],endIntervalPattern:[t?t.endIntervalPattern:null,[]]})}validatorTriggers(){return["fetchMode","useMetadataIntervalPatterns"]}updateValidators(t){const e=this.getTelemetryFromDatabaseConfigForm.get("fetchMode").value,o=this.getTelemetryFromDatabaseConfigForm.get("useMetadataIntervalPatterns").value;e&&e===Jt.ALL?(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([q.required]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([q.required]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([q.required,q.min(2),q.max(1e3)])):(this.getTelemetryFromDatabaseConfigForm.get("aggregation").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("orderBy").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("limit").setValidators([])),o?(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([q.required]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([q.required])):(this.getTelemetryFromDatabaseConfigForm.get("startInterval").setValidators([q.required,q.min(1),q.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").setValidators([q.required]),this.getTelemetryFromDatabaseConfigForm.get("endInterval").setValidators([q.required,q.min(1),q.max(2147483647)]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").setValidators([q.required]),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").setValidators([]),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").setValidators([])),this.getTelemetryFromDatabaseConfigForm.get("aggregation").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("orderBy").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("limit").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("startInterval").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalTimeUnit").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("endInterval").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalTimeUnit").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("startIntervalPattern").updateValueAndValidity({emitEvent:t}),this.getTelemetryFromDatabaseConfigForm.get("endIntervalPattern").updateValueAndValidity({emitEvent:t})}removeKey(t,e){const o=this.getTelemetryFromDatabaseConfigForm.get(e).value,r=o.indexOf(t);r>=0&&(o.splice(r,1),this.getTelemetryFromDatabaseConfigForm.get(e).setValue(o,{emitEvent:!0}))}addKey(t,e){const o=t.input;let r=t.value;if((r||"").trim()){r=r.trim();let t=this.getTelemetryFromDatabaseConfigForm.get(e).value;t&&-1!==t.indexOf(r)||(t||(t=[]),t.push(r),this.getTelemetryFromDatabaseConfigForm.get(e).setValue(t,{emitEvent:!0}))}o&&(o.value="")}}t("GetTelemetryFromDatabaseConfigComponent",oo),oo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:oo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),oo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:oo,selector:"tb-enrichment-node-get-telemetry-from-database",usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n tb.rulenode.fetch-mode\n \n \n {{ mode }}\n \n \n tb.rulenode.fetch-mode-hint\n \n
\n \n aggregation.function\n \n \n {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }}\n \n \n \n \n tb.rulenode.order-by\n \n \n {{ order }}\n \n \n tb.rulenode.order-by-hint\n \n \n tb.rulenode.limit\n \n tb.rulenode.limit-hint\n \n
\n \n {{ \'tb.rulenode.use-metadata-interval-patterns\' | translate }}\n \n
tb.rulenode.use-metadata-interval-patterns-hint
\n
\n
\n \n tb.rulenode.start-interval\n \n \n {{ \'tb.rulenode.start-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.start-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n tb.rulenode.end-interval\n \n \n {{ \'tb.rulenode.end-interval-value-required\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n {{ \'tb.rulenode.time-value-range\' | translate }}\n \n \n \n tb.rulenode.end-interval-time-unit\n \n \n {{ timeUnitsTranslationMap.get(timeUnit) | translate }}\n \n \n \n
\n
\n \n \n tb.rulenode.start-interval-pattern\n \n \n {{ \'tb.rulenode.start-interval-pattern-required\' | translate }}\n \n \n \n \n tb.rulenode.end-interval-pattern\n \n \n {{ \'tb.rulenode.end-interval-pattern-required\' | translate }}\n \n \n \n \n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:oo,decorators:[{type:o,args:[{selector:"tb-enrichment-node-get-telemetry-from-database",templateUrl:"./get-telemetry-from-database-config.component.html",styleUrls:["./get-telemetry-from-database-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class ro extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.separatorKeysCodes=[et,ot,rt]}configForm(){return this.originatorAttributesConfigForm}onConfigurationSet(t){this.originatorAttributesConfigForm=this.fb.group({tellFailureIfAbsent:[!!t&&t.tellFailureIfAbsent,[]],fetchToData:[!!t&&t.fetchToData,[]],clientAttributeNames:[t?t.clientAttributeNames:null,[]],sharedAttributeNames:[t?t.sharedAttributeNames:null,[]],serverAttributeNames:[t?t.serverAttributeNames:null,[]],latestTsKeyNames:[t?t.latestTsKeyNames:null,[]],getLatestValueWithTs:[!!t&&t.getLatestValueWithTs,[]]})}removeKey(t,e){const o=this.originatorAttributesConfigForm.get(e).value,r=o.indexOf(t);r>=0&&(o.splice(r,1),this.originatorAttributesConfigForm.get(e).setValue(o,{emitEvent:!0}))}addKey(t,e){const o=t.input;let r=t.value;if((r||"").trim()){r=r.trim();let t=this.originatorAttributesConfigForm.get(e).value;t&&-1!==t.indexOf(r)||(t||(t=[]),t.push(r),this.originatorAttributesConfigForm.get(e).setValue(t,{emitEvent:!0}))}o&&(o.value="")}}t("OriginatorAttributesConfigComponent",ro),ro.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ro,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ro.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ro,selector:"tb-enrichment-node-originator-attributes-config",usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'tb.rulenode.tell-failure-if-absent\' | translate }}\n \n
tb.rulenode.tell-failure-if-absent-hint
\n
{{ \'tb.rulenode.fetch-into\' | translate }}
\n \n \n {{ \'tb.rulenode.data\' | translate }}\n \n \n {{ \'tb.rulenode.metadata\' | translate }}\n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n \n \n \n \n {{key}}\n close\n \n \n \n \n \n \n {{ \'tb.rulenode.get-latest-value-with-ts\' | translate }}\n \n
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:qt.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:qt.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ro,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-attributes-config",templateUrl:"./originator-attributes-config.component.html",styleUrls:["./originator-attributes-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class ao extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.originatorFieldsConfigForm}onConfigurationSet(t){this.originatorFieldsConfigForm=this.fb.group({fieldsMapping:[t?t.fieldsMapping:null,[q.required]],ignoreNullStrings:[t?t.ignoreNullStrings:null]})}}t("OriginatorFieldsConfigComponent",ao),ao.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ao,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ao.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ao,selector:"tb-enrichment-node-originator-fields-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n \n {{ "tb.rulenode.ignore-null-strings" | translate }}\n
{{ "tb.rulenode.ignore-null-strings-hint" | translate }}
\n
\n',components:[{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ao,decorators:[{type:o,args:[{selector:"tb-enrichment-node-originator-fields-config",templateUrl:"./originator-fields-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class no extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.relatedAttributesConfigForm}onConfigurationSet(t){this.relatedAttributesConfigForm=this.fb.group({relationsQuery:[t?t.relationsQuery:null,[q.required]],telemetry:[!!t&&t.telemetry,[]],attrMapping:[t?t.attrMapping:null,[q.required]]})}}t("RelatedAttributesConfigComponent",no),no.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:no,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),no.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:no,selector:"tb-enrichment-node-related-attributes-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:Qe,selector:"tb-relations-query-config",inputs:["disabled","required"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:no,decorators:[{type:o,args:[{selector:"tb-enrichment-node-related-attributes-config",templateUrl:"./related-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class lo extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.tenantAttributesConfigForm}onConfigurationSet(t){this.tenantAttributesConfigForm=this.fb.group({telemetry:[!!t&&t.telemetry,[]],attrMapping:[t?t.attrMapping:null,[q.required]]})}}t("TenantAttributesConfigComponent",lo),lo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:lo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),lo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:lo,selector:"tb-enrichment-node-tenant-attributes-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n {{ \'tb.rulenode.latest-telemetry\' | translate }}\n \n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:lo,decorators:[{type:o,args:[{selector:"tb-enrichment-node-tenant-attributes-config",templateUrl:"./tenant-attributes-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class io extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.fetchDeviceCredentialsConfigForm}onConfigurationSet(t){this.fetchDeviceCredentialsConfigForm=this.fb.group({fetchToMetadata:[t?t.fetchToMetadata:null,[]]})}}t("FetchDeviceCredentialsConfigComponent",io),io.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:io,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),io.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:io,selector:"./tb-enrichment-node-fetch-device-credentials-config",usesInheritance:!0,ngImport:e,template:'
\n {{ \'tb.rulenode.fetch-credentials-to-metadata\' | translate }}\n
\n',components:[{type:St.MatSlideToggle,selector:"mat-slide-toggle",inputs:["disabled","disableRipple","color","tabIndex","name","id","labelPosition","aria-label","aria-labelledby","required","checked","aria-describedby"],outputs:["change","toggleChange"],exportAs:["matSlideToggle"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:io,decorators:[{type:o,args:[{selector:"./tb-enrichment-node-fetch-device-credentials-config",templateUrl:"./fetch-device-credentials-config.component.html"}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class so{}t("RulenodeCoreConfigEnrichmentModule",so),so.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:so,deps:[],target:e.ɵɵFactoryTarget.NgModule}),so.ɵmod=e.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:so,declarations:[Ze,eo,to,ro,ao,oo,no,lo,Xe,io],imports:[H,L,We],exports:[Ze,eo,to,ro,ao,oo,no,lo,Xe,io]}),so.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:so,imports:[[H,L,We]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:so,decorators:[{type:i,args:[{declarations:[Ze,eo,to,ro,ao,oo,no,lo,Xe,io],imports:[H,L,We],exports:[Ze,eo,to,ro,ao,oo,no,lo,Xe,io]}]}]});class mo extends s{constructor(t,e,o){super(t),this.store=t,this.translate=e,this.fb=o,this.alarmStatusTranslationsMap=N,this.alarmStatusList=[],this.searchText="",this.displayStatusFn=this.displayStatus.bind(this);for(const t of Object.keys(T))this.alarmStatusList.push(T[t]);this.statusFormControl=new D(""),this.filteredAlarmStatus=this.statusFormControl.valueChanges.pipe(gt(""),ct((t=>t||"")),xt((t=>this.fetchAlarmStatus(t))),yt())}ngOnInit(){super.ngOnInit()}configForm(){return this.alarmStatusConfigForm}prepareInputConfig(t){return this.searchText="",this.statusFormControl.patchValue("",{emitEvent:!0}),t}onConfigurationSet(t){this.alarmStatusConfigForm=this.fb.group({alarmStatusList:[t?t.alarmStatusList:null,[q.required]]})}displayStatus(t){return t?this.translate.instant(N.get(t)):void 0}fetchAlarmStatus(t){const e=this.getAlarmStatusList();if(this.searchText=t,this.searchText&&this.searchText.length){const t=this.searchText.toUpperCase();return Mt(e.filter((e=>this.translate.instant(N.get(T[e])).toUpperCase().includes(t))))}return Mt(e)}alarmStatusSelected(t){this.addAlarmStatus(t.option.value),this.clear("")}removeAlarmStatus(t){const e=this.alarmStatusConfigForm.get("alarmStatusList").value;if(e){const o=e.indexOf(t);o>=0&&(e.splice(o,1),this.alarmStatusConfigForm.get("alarmStatusList").setValue(e))}}addAlarmStatus(t){let e=this.alarmStatusConfigForm.get("alarmStatusList").value;e||(e=[]);-1===e.indexOf(t)&&(e.push(t),this.alarmStatusConfigForm.get("alarmStatusList").setValue(e))}getAlarmStatusList(){return this.alarmStatusList.filter((t=>-1===this.alarmStatusConfigForm.get("alarmStatusList").value.indexOf(t)))}onAlarmStatusInputFocus(){this.statusFormControl.updateValueAndValidity({onlySelf:!0,emitEvent:!0})}clear(t=""){this.alarmStatusInput.nativeElement.value=t,this.statusFormControl.patchValue(null,{emitEvent:!0}),setTimeout((()=>{this.alarmStatusInput.nativeElement.blur(),this.alarmStatusInput.nativeElement.focus()}),0)}}t("CheckAlarmStatusComponent",mo),mo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:mo,deps:[{token:k.Store},{token:R.TranslateService},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),mo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:mo,selector:"tb-filter-node-check-alarm-status-config",viewQueries:[{propertyName:"alarmStatusInput",first:!0,predicate:["alarmStatusInput"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.alarm-status-filter\n \n \n \n {{alarmStatusTranslationsMap.get(alarmStatus) | translate}}\n \n close\n \n \n \n \n \n \n \n \n
\n
\n tb.rulenode.no-alarm-status-matching\n
\n
\n
\n
\n
\n \n
\n\n\n\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:Ct.MatAutocomplete,selector:"mat-autocomplete",inputs:["disableRipple"],exportAs:["matAutocomplete"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:mt.TbErrorComponent,selector:"tb-error",inputs:["error"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:Ct.MatAutocompleteTrigger,selector:"input[matAutocomplete], textarea[matAutocomplete]",exportAs:["matAutocompleteTrigger"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:Ct.MatAutocompleteOrigin,selector:"[matAutocompleteOrigin]",exportAs:["matAutocompleteOrigin"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlDirective,selector:"[formControl]",inputs:["disabled","formControl","ngModel"],outputs:["ngModelChange"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:R.TranslatePipe,async:w.AsyncPipe,highlight:Ft.HighlightPipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:mo,decorators:[{type:o,args:[{selector:"tb-filter-node-check-alarm-status-config",templateUrl:"./check-alarm-status.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:R.TranslateService},{type:M.FormBuilder}]},propDecorators:{alarmStatusInput:[{type:a,args:["alarmStatusInput",{static:!1}]}]}});class uo extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.separatorKeysCodes=[et,ot,rt]}configForm(){return this.checkMessageConfigForm}onConfigurationSet(t){this.checkMessageConfigForm=this.fb.group({messageNames:[t?t.messageNames:null,[]],metadataNames:[t?t.metadataNames:null,[]],checkAllKeys:[!!t&&t.checkAllKeys,[]]})}validateConfig(){const t=this.checkMessageConfigForm.get("messageNames").value,e=this.checkMessageConfigForm.get("metadataNames").value;return t.length>0||e.length>0}removeMessageName(t){const e=this.checkMessageConfigForm.get("messageNames").value,o=e.indexOf(t);o>=0&&(e.splice(o,1),this.checkMessageConfigForm.get("messageNames").setValue(e,{emitEvent:!0}))}removeMetadataName(t){const e=this.checkMessageConfigForm.get("metadataNames").value,o=e.indexOf(t);o>=0&&(e.splice(o,1),this.checkMessageConfigForm.get("metadataNames").setValue(e,{emitEvent:!0}))}addMessageName(t){const e=t.input;let o=t.value;if((o||"").trim()){o=o.trim();let t=this.checkMessageConfigForm.get("messageNames").value;t&&-1!==t.indexOf(o)||(t||(t=[]),t.push(o),this.checkMessageConfigForm.get("messageNames").setValue(t,{emitEvent:!0}))}e&&(e.value="")}addMetadataName(t){const e=t.input;let o=t.value;if((o||"").trim()){o=o.trim();let t=this.checkMessageConfigForm.get("metadataNames").value;t&&-1!==t.indexOf(o)||(t||(t=[]),t.push(o),this.checkMessageConfigForm.get("metadataNames").setValue(t,{emitEvent:!0}))}e&&(e.value="")}}t("CheckMessageConfigComponent",uo),uo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:uo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),uo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:uo,selector:"tb-filter-node-check-message-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n {{messageName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n \n \n \n \n {{metadataName}}\n close\n \n \n \n \n
tb.rulenode.separator-hint
\n \n {{ \'tb.rulenode.check-all-keys\' | translate }}\n \n
tb.rulenode.check-all-keys-hint
\n
\n',styles:[":host label.tb-title{margin-bottom:-10px}\n"],components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:uo,decorators:[{type:o,args:[{selector:"tb-filter-node-check-message-config",templateUrl:"./check-message-config.component.html",styleUrls:["./check-message-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class po extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.entitySearchDirection=Object.keys(g),this.entitySearchDirectionTranslationsMap=x}configForm(){return this.checkRelationConfigForm}onConfigurationSet(t){this.checkRelationConfigForm=this.fb.group({checkForSingleEntity:[!!t&&t.checkForSingleEntity,[]],direction:[t?t.direction:null,[]],entityType:[t?t.entityType:null,t&&t.checkForSingleEntity?[q.required]:[]],entityId:[t?t.entityId:null,t&&t.checkForSingleEntity?[q.required]:[]],relationType:[t?t.relationType:null,[q.required]]})}validatorTriggers(){return["checkForSingleEntity"]}updateValidators(t){const e=this.checkRelationConfigForm.get("checkForSingleEntity").value;this.checkRelationConfigForm.get("entityType").setValidators(e?[q.required]:[]),this.checkRelationConfigForm.get("entityType").updateValueAndValidity({emitEvent:t}),this.checkRelationConfigForm.get("entityId").setValidators(e?[q.required]:[]),this.checkRelationConfigForm.get("entityId").updateValueAndValidity({emitEvent:t})}}t("CheckRelationConfigComponent",po),po.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:po,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),po.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:po,selector:"tb-filter-node-check-relation-config",usesInheritance:!0,ngImport:e,template:'
\n \n {{ \'tb.rulenode.check-relation-to-specific-entity\' | translate }}\n \n
tb.rulenode.check-relation-hint
\n \n relation.direction\n \n \n {{ entitySearchDirectionTranslationsMap.get(direction) | translate }}\n \n \n \n
\n \n \n \n \n
\n \n \n
\n',components:[{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:lt.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{type:At.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]},{type:Nt.RelationTypeAutocompleteComponent,selector:"tb-relation-type-autocomplete",inputs:["required","disabled"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:V.MatLabel,selector:"mat-label"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:po,decorators:[{type:o,args:[{selector:"tb-filter-node-check-relation-config",templateUrl:"./check-relation-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class fo extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.perimeterType=wt,this.perimeterTypes=Object.keys(wt),this.perimeterTypeTranslationMap=Ht,this.rangeUnits=Object.keys(Kt),this.rangeUnitTranslationMap=jt}configForm(){return this.geoFilterConfigForm}onConfigurationSet(t){this.geoFilterConfigForm=this.fb.group({latitudeKeyName:[t?t.latitudeKeyName:null,[q.required]],longitudeKeyName:[t?t.longitudeKeyName:null,[q.required]],perimeterType:[t?t.perimeterType:null,[q.required]],fetchPerimeterInfoFromMessageMetadata:[!!t&&t.fetchPerimeterInfoFromMessageMetadata,[]],perimeterKeyName:[t?t.perimeterKeyName:null,[]],centerLatitude:[t?t.centerLatitude:null,[]],centerLongitude:[t?t.centerLatitude:null,[]],range:[t?t.range:null,[]],rangeUnit:[t?t.rangeUnit:null,[]],polygonsDefinition:[t?t.polygonsDefinition:null,[]]})}validatorTriggers(){return["fetchPerimeterInfoFromMessageMetadata","perimeterType"]}updateValidators(t){const e=this.geoFilterConfigForm.get("fetchPerimeterInfoFromMessageMetadata").value,o=this.geoFilterConfigForm.get("perimeterType").value;e?this.geoFilterConfigForm.get("perimeterKeyName").setValidators([q.required]):this.geoFilterConfigForm.get("perimeterKeyName").setValidators([]),e||o!==wt.CIRCLE?(this.geoFilterConfigForm.get("centerLatitude").setValidators([]),this.geoFilterConfigForm.get("centerLongitude").setValidators([]),this.geoFilterConfigForm.get("range").setValidators([]),this.geoFilterConfigForm.get("rangeUnit").setValidators([])):(this.geoFilterConfigForm.get("centerLatitude").setValidators([q.required,q.min(-90),q.max(90)]),this.geoFilterConfigForm.get("centerLongitude").setValidators([q.required,q.min(-180),q.max(180)]),this.geoFilterConfigForm.get("range").setValidators([q.required,q.min(0)]),this.geoFilterConfigForm.get("rangeUnit").setValidators([q.required])),e||o!==wt.POLYGON?this.geoFilterConfigForm.get("polygonsDefinition").setValidators([]):this.geoFilterConfigForm.get("polygonsDefinition").setValidators([q.required]),this.geoFilterConfigForm.get("perimeterKeyName").updateValueAndValidity({emitEvent:t}),this.geoFilterConfigForm.get("centerLatitude").updateValueAndValidity({emitEvent:t}),this.geoFilterConfigForm.get("centerLongitude").updateValueAndValidity({emitEvent:t}),this.geoFilterConfigForm.get("range").updateValueAndValidity({emitEvent:t}),this.geoFilterConfigForm.get("rangeUnit").updateValueAndValidity({emitEvent:t}),this.geoFilterConfigForm.get("polygonsDefinition").updateValueAndValidity({emitEvent:t})}}t("GpsGeoFilterConfigComponent",fo),fo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:fo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),fo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:fo,selector:"tb-filter-node-gps-geofencing-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.latitude-key-name\n \n \n {{ \'tb.rulenode.latitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.longitude-key-name\n \n \n {{ \'tb.rulenode.longitude-key-name-required\' | translate }}\n \n \n \n tb.rulenode.perimeter-type\n \n \n {{ perimeterTypeTranslationMap.get(type) | translate }}\n \n \n \n \n {{ \'tb.rulenode.fetch-perimeter-info-from-message-metadata\' | translate }}\n \n \n tb.rulenode.perimeter-key-name\n \n \n {{ \'tb.rulenode.perimeter-key-name-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.circle-center-latitude\n \n \n {{ \'tb.rulenode.circle-center-latitude-required\' | translate }}\n \n \n \n tb.rulenode.circle-center-longitude\n \n \n {{ \'tb.rulenode.circle-center-longitude-required\' | translate }}\n \n \n
\n
\n \n tb.rulenode.range\n \n \n {{ \'tb.rulenode.range-required\' | translate }}\n \n \n \n tb.rulenode.range-units\n \n \n {{ rangeUnitTranslationMap.get(type) | translate }}\n \n \n \n
\n
\n
\n
\n \n tb.rulenode.polygon-definition\n \n \n {{ \'tb.rulenode.polygon-definition-required\' | translate }}\n \n \n
\n
\n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:E.MatCheckbox,selector:"mat-checkbox",inputs:["disableRipple","color","tabIndex","aria-label","aria-labelledby","id","labelPosition","name","required","checked","disabled","indeterminate","aria-describedby","value"],outputs:["change","indeterminateChange"],exportAs:["matCheckbox"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:M.MinValidator,selector:"input[type=number][min][formControlName],input[type=number][min][formControl],input[type=number][min][ngModel]",inputs:["min"]},{type:M.MaxValidator,selector:"input[type=number][max][formControlName],input[type=number][max][formControl],input[type=number][max][ngModel]",inputs:["max"]},{type:M.NumberValueAccessor,selector:"input[type=number][formControlName],input[type=number][formControl],input[type=number][ngModel]"}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:fo,decorators:[{type:o,args:[{selector:"tb-filter-node-gps-geofencing-config",templateUrl:"./gps-geo-filter-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class co extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.messageTypeConfigForm}onConfigurationSet(t){this.messageTypeConfigForm=this.fb.group({messageTypes:[t?t.messageTypes:null,[q.required]]})}}t("MessageTypeConfigComponent",co),co.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:co,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),co.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:co,selector:"tb-filter-node-message-type-config",usesInheritance:!0,ngImport:e,template:'
\n \n
\n',components:[{type:$e,selector:"tb-message-types-config",inputs:["required","label","placeholder","disabled"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:co,decorators:[{type:o,args:[{selector:"tb-filter-node-message-type-config",templateUrl:"./message-type-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class go extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.allowedEntityTypes=[y.DEVICE,y.ASSET,y.ENTITY_VIEW,y.TENANT,y.CUSTOMER,y.USER,y.DASHBOARD,y.RULE_CHAIN,y.RULE_NODE]}configForm(){return this.originatorTypeConfigForm}onConfigurationSet(t){this.originatorTypeConfigForm=this.fb.group({originatorTypes:[t?t.originatorTypes:null,[q.required]]})}}t("OriginatorTypeConfigComponent",go),go.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:go,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),go.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:go,selector:"tb-filter-node-originator-type-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n \n
\n',styles:[":host ::ng-deep tb-entity-type-list .mat-form-field-flex{padding-top:0}:host ::ng-deep tb-entity-type-list .mat-form-field-infix{border-top:0}\n"],components:[{type:Gt.EntityTypeListComponent,selector:"tb-entity-type-list",inputs:["required","disabled","allowedEntityTypes","ignoreAuthorityFilter"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:go,decorators:[{type:o,args:[{selector:"tb-filter-node-originator-type-config",templateUrl:"./originator-type-config.component.html",styleUrls:["./originator-type-config.component.scss"]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class xo extends s{constructor(t,e,o,r){super(t),this.store=t,this.fb=e,this.nodeScriptTestService=o,this.translate=r,this.mvelEnabled=Q(this.store).mvelEnabled,this.scriptLanguage=d}configForm(){return this.scriptConfigForm}onConfigurationSet(t){this.scriptConfigForm=this.fb.group({scriptLang:[t?t.scriptLang:d.JS,[q.required]],jsScript:[t?t.jsScript:null,[]],mvelScript:[t?t.mvelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.scriptConfigForm.get("scriptLang").value;e!==d.MVEL||this.mvelEnabled||(e=d.JS,this.scriptConfigForm.get("scriptLang").patchValue(e,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(e===d.JS?[q.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:t}),this.scriptConfigForm.get("mvelScript").setValidators(e===d.MVEL?[q.required]:[]),this.scriptConfigForm.get("mvelScript").updateValueAndValidity({emitEvent:t})}prepareInputConfig(t){return t&&(t.scriptLang||(t.scriptLang=d.JS)),t}testScript(){const t=this.scriptConfigForm.get("scriptLang").value,e=t===d.JS?"jsScript":"mvelScript",o=this.scriptConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(o,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/filter_node_script_fn",t).subscribe((t=>{t&&this.scriptConfigForm.get(e).setValue(t)}))}onValidate(){(this.scriptConfigForm.get("scriptLang").value===d.JS?this.jsFuncComponent:this.mvelFuncComponent).validateOnSubmit()}}t("ScriptConfigComponent",xo),xo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:xo,deps:[{token:k.Store},{token:M.FormBuilder},{token:$.NodeScriptTestService},{token:R.TranslateService}],target:e.ɵɵFactoryTarget.Component}),xo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:xo,selector:"tb-filter-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"mvelFuncComponent",first:!0,predicate:["mvelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n
\n \n
\n
\n',components:[{type:X.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{type:Z.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:xo,decorators:[{type:o,args:[{selector:"tb-filter-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder},{type:$.NodeScriptTestService},{type:R.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],mvelFuncComponent:[{type:a,args:["mvelFuncComponent",{static:!1}]}]}});class yo extends s{constructor(t,e,o,r){super(t),this.store=t,this.fb=e,this.nodeScriptTestService=o,this.translate=r,this.mvelEnabled=Q(this.store).mvelEnabled,this.scriptLanguage=d}configForm(){return this.switchConfigForm}onConfigurationSet(t){this.switchConfigForm=this.fb.group({scriptLang:[t?t.scriptLang:d.JS,[q.required]],jsScript:[t?t.jsScript:null,[]],mvelScript:[t?t.mvelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.switchConfigForm.get("scriptLang").value;e!==d.MVEL||this.mvelEnabled||(e=d.JS,this.switchConfigForm.get("scriptLang").patchValue(e,{emitEvent:!1}),setTimeout((()=>{this.switchConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.switchConfigForm.get("jsScript").setValidators(e===d.JS?[q.required]:[]),this.switchConfigForm.get("jsScript").updateValueAndValidity({emitEvent:t}),this.switchConfigForm.get("mvelScript").setValidators(e===d.MVEL?[q.required]:[]),this.switchConfigForm.get("mvelScript").updateValueAndValidity({emitEvent:t})}prepareInputConfig(t){return t&&(t.scriptLang||(t.scriptLang=d.JS)),t}testScript(){const t=this.switchConfigForm.get("scriptLang").value,e=t===d.JS?"jsScript":"mvelScript",o=this.switchConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(o,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/switch_node_script_fn",t).subscribe((t=>{t&&this.switchConfigForm.get(e).setValue(t)}))}onValidate(){(this.switchConfigForm.get("scriptLang").value===d.JS?this.jsFuncComponent:this.mvelFuncComponent).validateOnSubmit()}}t("SwitchConfigComponent",yo),yo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:yo,deps:[{token:k.Store},{token:M.FormBuilder},{token:$.NodeScriptTestService},{token:R.TranslateService}],target:e.ɵɵFactoryTarget.Component}),yo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:yo,selector:"tb-filter-node-switch-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"mvelFuncComponent",first:!0,predicate:["mvelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n
\n \n
\n
\n',components:[{type:X.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{type:Z.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:yo,decorators:[{type:o,args:[{selector:"tb-filter-node-switch-config",templateUrl:"./switch-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder},{type:$.NodeScriptTestService},{type:R.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],mvelFuncComponent:[{type:a,args:["mvelFuncComponent",{static:!1}]}]}});class bo{}t("RuleNodeCoreConfigFilterModule",bo),bo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:bo,deps:[],target:e.ɵɵFactoryTarget.NgModule}),bo.ɵmod=e.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:bo,declarations:[uo,po,fo,co,go,xo,yo,mo],imports:[H,L,We],exports:[uo,po,fo,co,go,xo,yo,mo]}),bo.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:bo,imports:[[H,L,We]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:bo,decorators:[{type:i,args:[{declarations:[uo,po,fo,co,go,xo,yo,mo],imports:[H,L,We],exports:[uo,po,fo,co,go,xo,yo,mo]}]}]});class ho extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.originatorSource=Rt,this.originatorSources=Object.keys(Rt),this.originatorSourceTranslationMap=Ot}configForm(){return this.changeOriginatorConfigForm}onConfigurationSet(t){this.changeOriginatorConfigForm=this.fb.group({originatorSource:[t?t.originatorSource:null,[q.required]],entityType:[t?t.entityType:null,[]],entityNamePattern:[t?t.entityNamePattern:null,[]],relationsQuery:[t?t.relationsQuery:null,[]]})}validatorTriggers(){return["originatorSource"]}updateValidators(t){const e=this.changeOriginatorConfigForm.get("originatorSource").value;e===Rt.RELATED?this.changeOriginatorConfigForm.get("relationsQuery").setValidators([q.required]):this.changeOriginatorConfigForm.get("relationsQuery").setValidators([]),e===Rt.ENTITY?(this.changeOriginatorConfigForm.get("entityType").setValidators([q.required]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([q.required,q.pattern(/.*\S.*/)])):(this.changeOriginatorConfigForm.get("entityType").patchValue(null,{emitEvent:t}),this.changeOriginatorConfigForm.get("entityNamePattern").patchValue(null,{emitEvent:t}),this.changeOriginatorConfigForm.get("entityType").setValidators([]),this.changeOriginatorConfigForm.get("entityNamePattern").setValidators([])),this.changeOriginatorConfigForm.get("relationsQuery").updateValueAndValidity({emitEvent:t}),this.changeOriginatorConfigForm.get("entityType").updateValueAndValidity({emitEvent:t}),this.changeOriginatorConfigForm.get("entityNamePattern").updateValueAndValidity({emitEvent:t})}}t("ChangeOriginatorConfigComponent",ho),ho.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ho,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ho.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ho,selector:"tb-transformation-node-change-originator-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.originator-source\n \n \n {{ originatorSourceTranslationMap.get(source) | translate }}\n \n \n \n
\n \n \n \n tb.rulenode.entity-name-pattern\n \n \n {{ \'tb.rulenode.entity-name-pattern-required\' | translate }}\n \n \n \n
\n
\n \n \n \n
\n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:lt.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{type:Qe,selector:"tb-relations-query-config",inputs:["disabled","required"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:P.DefaultFlexDirective,selector:" [fxFlex], [fxFlex.xs], [fxFlex.sm], [fxFlex.md], [fxFlex.lg], [fxFlex.xl], [fxFlex.lt-sm], [fxFlex.lt-md], [fxFlex.lt-lg], [fxFlex.lt-xl], [fxFlex.gt-xs], [fxFlex.gt-sm], [fxFlex.gt-md], [fxFlex.gt-lg]",inputs:["fxFlex","fxFlex.xs","fxFlex.sm","fxFlex.md","fxFlex.lg","fxFlex.xl","fxFlex.lt-sm","fxFlex.lt-md","fxFlex.lt-lg","fxFlex.lt-xl","fxFlex.gt-xs","fxFlex.gt-sm","fxFlex.gt-md","fxFlex.gt-lg"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ho,decorators:[{type:o,args:[{selector:"tb-transformation-node-change-originator-config",templateUrl:"./change-originator-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Co extends s{constructor(t,e,o,r){super(t),this.store=t,this.fb=e,this.nodeScriptTestService=o,this.translate=r,this.mvelEnabled=Q(this.store).mvelEnabled,this.scriptLanguage=d}configForm(){return this.scriptConfigForm}onConfigurationSet(t){this.scriptConfigForm=this.fb.group({scriptLang:[t?t.scriptLang:d.JS,[q.required]],jsScript:[t?t.jsScript:null,[q.required]],mvelScript:[t?t.mvelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.scriptConfigForm.get("scriptLang").value;e!==d.MVEL||this.mvelEnabled||(e=d.JS,this.scriptConfigForm.get("scriptLang").patchValue(e,{emitEvent:!1}),setTimeout((()=>{this.scriptConfigForm.updateValueAndValidity({emitEvent:!0})}))),this.scriptConfigForm.get("jsScript").setValidators(e===d.JS?[q.required]:[]),this.scriptConfigForm.get("jsScript").updateValueAndValidity({emitEvent:t}),this.scriptConfigForm.get("mvelScript").setValidators(e===d.MVEL?[q.required]:[]),this.scriptConfigForm.get("mvelScript").updateValueAndValidity({emitEvent:t})}prepareInputConfig(t){return t&&(t.scriptLang||(t.scriptLang=d.JS)),t}testScript(){const t=this.scriptConfigForm.get("scriptLang").value,e=t===d.JS?"jsScript":"mvelScript",o=this.scriptConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(o,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,"rulenode/transformation_node_script_fn",t).subscribe((t=>{t&&this.scriptConfigForm.get(e).setValue(t)}))}onValidate(){(this.scriptConfigForm.get("scriptLang").value===d.JS?this.jsFuncComponent:this.mvelFuncComponent).validateOnSubmit()}}t("TransformScriptConfigComponent",Co),Co.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Co,deps:[{token:k.Store},{token:M.FormBuilder},{token:$.NodeScriptTestService},{token:R.TranslateService}],target:e.ɵɵFactoryTarget.Component}),Co.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Co,selector:"tb-transformation-node-script-config",viewQueries:[{propertyName:"jsFuncComponent",first:!0,predicate:["jsFuncComponent"],descendants:!0},{propertyName:"mvelFuncComponent",first:!0,predicate:["mvelFuncComponent"],descendants:!0}],usesInheritance:!0,ngImport:e,template:'
\n \n \n \n \n \n
\n \n
\n
\n',components:[{type:X.TbScriptLangComponent,selector:"tb-script-lang",inputs:["disabled"]},{type:Z.JsFuncComponent,selector:"tb-js-func",inputs:["functionTitle","functionName","functionArgs","validationArgs","resultType","disabled","fillHeight","minHeight","editorCompleter","globalVariables","disableUndefinedCheck","helpId","noValidate","required"]},{type:tt.MatButton,selector:"button[mat-button], button[mat-raised-button], button[mat-icon-button], button[mat-fab], button[mat-mini-fab], button[mat-stroked-button], button[mat-flat-button]",inputs:["disabled","disableRipple","color"],exportAs:["matButton"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Co,decorators:[{type:o,args:[{selector:"tb-transformation-node-script-config",templateUrl:"./script-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder},{type:$.NodeScriptTestService},{type:R.TranslateService}]},propDecorators:{jsFuncComponent:[{type:a,args:["jsFuncComponent",{static:!1}]}],mvelFuncComponent:[{type:a,args:["mvelFuncComponent",{static:!1}]}]}});class Fo extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.mailBodyTypes=[{name:"tb.mail-body-type.plain-text",value:"false"},{name:"tb.mail-body-type.html",value:"true"},{name:"tb.mail-body-type.dynamic",value:"dynamic"}]}configForm(){return this.toEmailConfigForm}onConfigurationSet(t){this.toEmailConfigForm=this.fb.group({fromTemplate:[t?t.fromTemplate:null,[q.required]],toTemplate:[t?t.toTemplate:null,[q.required]],ccTemplate:[t?t.ccTemplate:null,[]],bccTemplate:[t?t.bccTemplate:null,[]],subjectTemplate:[t?t.subjectTemplate:null,[q.required]],mailBodyType:[t?t.mailBodyType:null],isHtmlTemplate:[t?t.isHtmlTemplate:null],bodyTemplate:[t?t.bodyTemplate:null,[q.required]]}),this.toEmailConfigForm.get("mailBodyType").valueChanges.pipe(gt([null==t?void 0:t.subjectTemplate])).subscribe((t=>{"dynamic"===t?(this.toEmailConfigForm.get("isHtmlTemplate").patchValue("",{emitEvent:!1}),this.toEmailConfigForm.get("isHtmlTemplate").setValidators(q.required)):this.toEmailConfigForm.get("isHtmlTemplate").clearValidators(),this.toEmailConfigForm.get("isHtmlTemplate").updateValueAndValidity()}))}}t("ToEmailConfigComponent",Fo),Fo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Fo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Fo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Fo,selector:"tb-transformation-node-to-email-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.from-template\n \n \n {{ \'tb.rulenode.from-template-required\' | translate }}\n \n \n \n \n tb.rulenode.to-template\n \n \n {{ \'tb.rulenode.to-template-required\' | translate }}\n \n \n \n \n tb.rulenode.cc-template\n \n \n \n \n tb.rulenode.bcc-template\n \n \n \n \n tb.rulenode.subject-template\n \n \n {{ \'tb.rulenode.subject-template-required\' | translate }}\n \n \n \n \n tb.rulenode.mail-body-type\n \n \n {{ type.name | translate }}\n \n \n \n \n tb.rulenode.dynamic-mail-body-type\n \n \n \n \n tb.rulenode.body-template\n \n \n {{ \'tb.rulenode.body-template-required\' | translate }}\n \n \n \n
\n',components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:B.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Fo,decorators:[{type:o,args:[{selector:"tb-transformation-node-to-email-config",templateUrl:"./to-email-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Lo extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.separatorKeysCodes=[et,ot,rt]}onConfigurationSet(t){this.copyKeysConfigForm=this.fb.group({fromMetadata:[t?t.fromMetadata:null,[q.required]],keys:[t?t.keys:null,[q.required]]})}configForm(){return this.copyKeysConfigForm}removeKey(t){const e=this.copyKeysConfigForm.get("keys").value,o=e.indexOf(t);o>=0&&(e.splice(o,1),this.copyKeysConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}addKey(t){const e=t.input;let o=t.value;if((o||"").trim()){o=o.trim();let t=this.copyKeysConfigForm.get("keys").value;t&&-1!==t.indexOf(o)||(t||(t=[]),t.push(o),this.copyKeysConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}e&&(e.value="")}}t("CopyKeysConfigComponent",Lo),Lo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Lo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Lo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Lo,selector:"tb-transformation-node-copy-keys-config",usesInheritance:!0,ngImport:e,template:'
\n
{{\'tb.rulenode.copy-from\' | translate}}
\n \n \n {{\'tb.rulenode.data-to-metadata\' | translate}}\n \n \n {{\'tb.rulenode.metadata-to-data\' | translate}}\n \n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n',components:[{type:qt.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:qt.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Lo,decorators:[{type:o,args:[{selector:"tb-transformation-node-copy-keys-config",templateUrl:"./copy-keys-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class vo extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.renameKeysConfigForm}onConfigurationSet(t){this.renameKeysConfigForm=this.fb.group({fromMetadata:[t?t.fromMetadata:null,[q.required]],renameKeysMapping:[t?t.renameKeysMapping:null,[q.required]]})}}t("RenameKeysConfigComponent",vo),vo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:vo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),vo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:vo,selector:"tb-transformation-node-rename-keys-config",usesInheritance:!0,ngImport:e,template:'
\n
{{ \'tb.rulenode.rename-keys-in\' | translate }}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n
\n',components:[{type:qt.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{type:Le,selector:"tb-kv-map-config",inputs:["disabled","uniqueKeyValuePairValidator","requiredText","keyText","keyRequiredText","valText","valRequiredText","hintText","required"]}],directives:[{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:qt.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:vo,decorators:[{type:o,args:[{selector:"tb-transformation-node-rename-keys-config",templateUrl:"./rename-keys-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Io extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.jsonPathConfigForm}onConfigurationSet(t){this.jsonPathConfigForm=this.fb.group({jsonPath:[t?t.jsonPath:null,[q.required]]})}}t("NodeJsonPathConfigComponent",Io),Io.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Io,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Io.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Io,selector:"tb-transformation-node-json-path-config",usesInheritance:!0,ngImport:e,template:"
\n \n {{ 'tb.rulenode.json-path-expression' | translate }}\n \n {{ 'tb.rulenode.json-path-expression-hint' | translate }}\n {{ 'tb.rulenode.json-path-expression-required' | translate }}\n \n
\n\n",components:[{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:V.MatLabel,selector:"mat-label"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:M.DefaultValueAccessor,selector:"input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]"},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Io,decorators:[{type:o,args:[{selector:"tb-transformation-node-json-path-config",templateUrl:"./node-json-path-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class No extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.separatorKeysCodes=[et,ot,rt]}onConfigurationSet(t){this.deleteKeysConfigForm=this.fb.group({fromMetadata:[t?t.fromMetadata:null,[q.required]],keys:[t?t.keys:null,[q.required]]})}configForm(){return this.deleteKeysConfigForm}removeKey(t){const e=this.deleteKeysConfigForm.get("keys").value,o=e.indexOf(t);o>=0&&(e.splice(o,1),this.deleteKeysConfigForm.get("keys").patchValue(e,{emitEvent:!0}))}addKey(t){const e=t.input;let o=t.value;if((o||"").trim()){o=o.trim();let t=this.deleteKeysConfigForm.get("keys").value;t&&-1!==t.indexOf(o)||(t||(t=[]),t.push(o),this.deleteKeysConfigForm.get("keys").patchValue(t,{emitEvent:!0}))}e&&(e.value="")}}t("DeleteKeysConfigComponent",No),No.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:No,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),No.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:No,selector:"tb-transformation-node-delete-keys-config",usesInheritance:!0,ngImport:e,template:'
\n
{{\'tb.rulenode.delete-from\' | translate}}
\n \n \n {{\'tb.rulenode.data\' | translate}}\n \n \n {{\'tb.rulenode.metadata\' | translate}}\n \n \n \n \n \n \n {{key}}\n close\n \n \n \n {{ \'tb.rulenode.keys-required\' | translate }}\n \n \n
\n',components:[{type:qt.MatRadioButton,selector:"mat-radio-button",inputs:["disableRipple","tabIndex"],exportAs:["matRadioButton"]},{type:V.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:at.MatChipList,selector:"mat-chip-list",inputs:["aria-orientation","multiple","compareWith","value","required","placeholder","disabled","selectable","tabIndex","errorStateMatcher"],outputs:["change","valueChange"],exportAs:["matChipList"]},{type:nt.MatIcon,selector:"mat-icon",inputs:["color","inline","svgIcon","fontSet","fontIcon"],exportAs:["matIcon"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:qt.MatRadioGroup,selector:"mat-radio-group",exportAs:["matRadioGroup"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]},{type:P.DefaultLayoutGapDirective,selector:" [fxLayoutGap], [fxLayoutGap.xs], [fxLayoutGap.sm], [fxLayoutGap.md], [fxLayoutGap.lg], [fxLayoutGap.xl], [fxLayoutGap.lt-sm], [fxLayoutGap.lt-md], [fxLayoutGap.lt-lg], [fxLayoutGap.lt-xl], [fxLayoutGap.gt-xs], [fxLayoutGap.gt-sm], [fxLayoutGap.gt-md], [fxLayoutGap.gt-lg]",inputs:["fxLayoutGap","fxLayoutGap.xs","fxLayoutGap.sm","fxLayoutGap.md","fxLayoutGap.lg","fxLayoutGap.xl","fxLayoutGap.lt-sm","fxLayoutGap.lt-md","fxLayoutGap.lt-lg","fxLayoutGap.lt-xl","fxLayoutGap.gt-xs","fxLayoutGap.gt-sm","fxLayoutGap.gt-md","fxLayoutGap.gt-lg"]},{type:V.MatLabel,selector:"mat-label"},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]},{type:at.MatChip,selector:"mat-basic-chip, [mat-basic-chip], mat-chip, [mat-chip]",inputs:["color","disableRipple","tabIndex","selected","value","selectable","disabled","removable"],outputs:["selectionChange","destroyed","removed"],exportAs:["matChip"]},{type:at.MatChipRemove,selector:"[matChipRemove]"},{type:O.MatInput,selector:"input[matInput], textarea[matInput], select[matNativeControl], input[matNativeControl], textarea[matNativeControl]",inputs:["id","disabled","required","type","value","readonly","placeholder","errorStateMatcher","aria-describedby"],exportAs:["matInput"]},{type:at.MatChipInput,selector:"input[matChipInputFor]",inputs:["matChipInputSeparatorKeyCodes","placeholder","id","matChipInputFor","matChipInputAddOnBlur","disabled"],outputs:["matChipInputTokenEnd"],exportAs:["matChipInput","matChipInputFor"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:V.MatError,selector:"mat-error",inputs:["id"]},{type:V.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Vt}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:No,decorators:[{type:o,args:[{selector:"tb-transformation-node-delete-keys-config",templateUrl:"./delete-keys-config.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class To{}t("RulenodeCoreConfigTransformModule",To),To.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:To,deps:[],target:e.ɵɵFactoryTarget.NgModule}),To.ɵmod=e.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:To,declarations:[ho,Co,Fo,Lo,vo,Io,No],imports:[H,L,We],exports:[ho,Co,Fo,Lo,vo,Io,No]}),To.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:To,imports:[[H,L,We]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:To,decorators:[{type:i,args:[{declarations:[ho,Co,Fo,Lo,vo,Io,No],imports:[H,L,We],exports:[ho,Co,Fo,Lo,vo,Io,No]}]}]});class ko extends s{constructor(t,e){super(t),this.store=t,this.fb=e,this.entityType=y}configForm(){return this.ruleChainInputConfigForm}onConfigurationSet(t){this.ruleChainInputConfigForm=this.fb.group({ruleChainId:[t?t.ruleChainId:null,[q.required]]})}}t("RuleChainInputComponent",ko),ko.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ko,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),ko.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:ko,selector:"tb-flow-node-rule-chain-input-config",usesInheritance:!0,ngImport:e,template:'
\n \n \n
\n',components:[{type:At.EntityAutocompleteComponent,selector:"tb-entity-autocomplete",inputs:["entityType","entitySubtype","excludeEntityIds","labelText","requiredText","required","disabled"],outputs:["entityChanged"]}],directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]},{type:M.RequiredValidator,selector:":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]",inputs:["required"]},{type:M.NgControlStatus,selector:"[formControlName],[ngModel],[formControl]"},{type:M.FormControlName,selector:"[formControlName]",inputs:["disabled","formControlName","ngModel"],outputs:["ngModelChange"]}]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:ko,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-input-config",templateUrl:"./rule-chain-input.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class Mo extends s{constructor(t,e){super(t),this.store=t,this.fb=e}configForm(){return this.ruleChainOutputConfigForm}onConfigurationSet(t){this.ruleChainOutputConfigForm=this.fb.group({})}}t("RuleChainOutputComponent",Mo),Mo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Mo,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Mo.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Mo,selector:"tb-flow-node-rule-chain-output-config",usesInheritance:!0,ngImport:e,template:'
\n
\n
\n',directives:[{type:P.DefaultLayoutDirective,selector:" [fxLayout], [fxLayout.xs], [fxLayout.sm], [fxLayout.md], [fxLayout.lg], [fxLayout.xl], [fxLayout.lt-sm], [fxLayout.lt-md], [fxLayout.lt-lg], [fxLayout.lt-xl], [fxLayout.gt-xs], [fxLayout.gt-sm], [fxLayout.gt-md], [fxLayout.gt-lg]",inputs:["fxLayout","fxLayout.xs","fxLayout.sm","fxLayout.md","fxLayout.lg","fxLayout.xl","fxLayout.lt-sm","fxLayout.lt-md","fxLayout.lt-lg","fxLayout.lt-xl","fxLayout.gt-xs","fxLayout.gt-sm","fxLayout.gt-md","fxLayout.gt-lg"]},{type:M.NgControlStatusGroup,selector:"[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]"},{type:M.FormGroupDirective,selector:"[formGroup]",inputs:["formGroup"],outputs:["ngSubmit"],exportAs:["ngForm"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Mo,decorators:[{type:o,args:[{selector:"tb-flow-node-rule-chain-output-config",templateUrl:"./rule-chain-output.component.html",styleUrls:[]}]}],ctorParameters:function(){return[{type:k.Store},{type:M.FormBuilder}]}});class qo{}t("RuleNodeCoreConfigFlowModule",qo),qo.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qo,deps:[],target:e.ɵɵFactoryTarget.NgModule}),qo.ɵmod=e.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qo,declarations:[ko,Mo],imports:[H,L,We],exports:[ko,Mo]}),qo.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qo,imports:[[H,L,We]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qo,decorators:[{type:i,args:[{declarations:[ko,Mo],imports:[H,L,We],exports:[ko,Mo]}]}]});class So{constructor(t){!function(t){t.setTranslation("en_US",{tb:{rulenode:{"create-entity-if-not-exists":"Create new entity if not exists","create-entity-if-not-exists-hint":"Create a new entity set above if it does not exist.","entity-name-pattern":"Name pattern","entity-name-pattern-required":"Name pattern is required","entity-type-pattern":"Type pattern","entity-type-pattern-required":"Type pattern is required","entity-cache-expiration":"Entities cache expiration time (sec)","entity-cache-expiration-hint":"Specifies maximum time interval allowed to store found entity records. 0 value means that records will never expire.","entity-cache-expiration-required":"Entities cache expiration time is required.","entity-cache-expiration-range":"Entities cache expiration time should be greater than or equal to 0.","customer-name-pattern":"Customer name pattern","customer-name-pattern-required":"Customer name pattern is required","create-customer-if-not-exists":"Create new customer if not exists","customer-cache-expiration":"Customers cache expiration time (sec)","customer-cache-expiration-hint":"Specifies maximum time interval allowed to store found customer records. 0 value means that records will never expire.","customer-cache-expiration-required":"Customers cache expiration time is required.","customer-cache-expiration-range":"Customers cache expiration time should be greater than or equal to 0.","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","fetch-mode-hint":"If selected fetch mode 'ALL' you able to choose telemetry sampling order.","order-by":"Order by","order-by-hint":"Select to choose telemetry sampling order.",limit:"Limit","limit-hint":"Min limit value is 2, max - 1000. In case you want to fetch a single entry, select fetch mode 'FIRST' or 'LAST'.","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","attributes-keys":"Attributes keys","attributes-keys-required":"Attributes keys are required","notify-device":"Notify Device","fetch-credentials-to-metadata":"Fetch credentials to metadata","notify-device-hint":"If the message arrives from the device, we will push it back to the device by default.","latest-timeseries":"Latest timeseries","timeseries-key":"Timeseries key","data-keys":"Message data","copy-from":"Copy from","data-to-metadata":"Data to metadata","metadata-to-data":"Metadata to data","use-regular-expression-hint":"Hint: use regular expression to copy keys by pattern",keys:"Keys","keys-required":"Keys are required","rename-keys-in":"Rename keys in",data:"Data",metadata:"Metadata","key-name":"Key name","key-name-required":"Key name is required","new-key-name":"New key name","new-key-name-required":"New key name is required","metadata-keys":"Message metadata","json-path-expression":"JSON path expression","json-path-expression-required":"JSON path expression is required","json-path-expression-hint":"JSONPath specifies a path to an element or a set of elements in a JSON structure. '$' represents the root object or array.","relations-query":"Relations query","device-relations-query":"Device relations query","max-relation-level":"Max relation level","relation-type-pattern":"Relation type pattern","relation-type-pattern-required":"Relation type pattern is required","relation-types-list":"Relation types to propagate","relation-types-list-hint":"If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.","unlimited-level":"Unlimited level","latest-telemetry":"Latest telemetry","delete-from":"Delete from","use-regular-expression-delete-hint":"Use regular expression to delete keys by pattern","fetch-into":"Fetch into","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","originator-alarm-originator":"Alarm Originator","originator-entity":"Entity","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.","use-metadata-period-in-seconds-patterns":"Use period in seconds pattern","use-metadata-period-in-seconds-patterns-hint":"If selected, rule node use period in seconds interval pattern from message metadata or data assuming that intervals are in the seconds.","period-in-seconds-pattern":"Period in seconds pattern","period-in-seconds-pattern-required":"Period in seconds pattern 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","alarm-severity-pattern":"Alarm severity pattern","alarm-status-filter":"Alarm status filter","alarm-status-list-empty":"Alarm status list is empty","no-alarm-status-matching":"No alarm status matching were found.",propagate:"Propagate alarm to related entities","propagate-to-owner":"Propagate alarm to entity owner (Customer or Tenant)","propagate-to-tenant":"Propagate alarm to Tenant",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","to-template":"To Template","to-template-required":"To Template is required","mail-address-list-template-hint":'Comma separated address list, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"cc-template":"Cc Template","bcc-template":"Bcc Template","subject-template":"Subject Template","subject-template-required":"Subject Template is required","body-template":"Body Template","body-template-required":"Body Template is required","dynamic-mail-body-type":"Dynamic mail body type","mail-body-type":"Mail body type","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","request-method":"Request method","use-simple-client-http-factory":"Use simple client HTTP factory","ignore-request-body":"Without request body","read-timeout":"Read timeout in millis","read-timeout-hint":"The value of 0 means an infinite timeout","max-parallel-requests-count":"Max number of parallel requests","max-parallel-requests-count-hint":"The value of 0 specifies no limit in parallel processing",headers:"Headers","headers-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in header/value fields',header:"Header","header-required":"Header is required",value:"Value","value-required":"Value is required","topic-pattern":"Topic pattern","key-pattern":"Key pattern","key-pattern-hint":"Hint: Optional. If a valid partition number is specified, it will be used when sending the record. If no partition is specified, the key will be used instead. If neither is specified, a partition will be assigned in a round-robin fashion.","topic-pattern-required":"Topic pattern is required",topic:"Topic","topic-required":"Topic is required","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","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","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","gcp-project-id":"GCP project ID","gcp-project-id-required":"GCP project ID is required","gcp-service-account-key":"GCP service account key file","gcp-service-account-key-required":"GCP service account key file is required","pubsub-topic-name":"Topic name","pubsub-topic-name-required":"Topic name is required","message-attributes":"Message attributes","message-attributes-hint":'Use ${metadataKey} for value from metadata, $[messageKey] for value from message body in name/value fields',"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","client-id-hint":'Hint: Optional. Leave empty for auto-generated Client ID. Be careful when specifying the Client ID. Majority of the MQTT brokers will not allow multiple connections with the same Client ID. To connect to such brokers, your mqtt Client ID must be unique. When platform is running in a micro-services mode, the copy of rule node is launched in each micro-service. This will automatically lead to multiple mqtt clients with the same ID and may cause failures of the rule node. To avoid such failures enable "Add Service ID as suffix to Client ID" option below.',"append-client-id-suffix":"Add Service ID as suffix to Client ID","client-id-suffix-hint":'Hint: Optional. Applied when "Client ID" specified explicitly. If selected then Service ID will be added to Client ID as a suffix. Helps to avoid failures when platform is running in a micro-services mode.',"device-id":"Device ID","device-id-required":"Device ID is required.","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","credentials-pem-hint":"At least Server CA certificate file or a pair of Client certificate and Client private key files are required","credentials-sas":"Shared Access Signature","sas-key":"SAS Key","sas-key-required":"SAS Key is required.",hostname:"Hostname","hostname-required":"Hostname is required.","azure-ca-cert":"CA certificate file","username-required":"Username is required.","password-required":"Password is required.","ca-cert":"Server CA certificate file *","private-key":"Client private key file *",cert:"Client 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","use-metadata-interval-patterns":"Use interval patterns","use-metadata-interval-patterns-hint":"If selected, rule node use start and end interval patterns from message metadata or data assuming that intervals are in the milliseconds.","use-message-alarm-data":"Use message alarm data","overwrite-alarm-details":"Overwrite alarm details","use-alarm-severity-pattern":"Use alarm severity pattern","check-all-keys":"Check that all selected keys are present","check-all-keys-hint":"If selected, checks that all specified keys are present in the message data and metadata.","check-relation-to-specific-entity":"Check relation to specific entity","check-relation-hint":"Checks existence of relation to specific entity or to any entity based on direction and relation type.","delete-relation-to-specific-entity":"Delete relation to specific entity","delete-relation-hint":"Deletes relation from the originator of the incoming message to the specified entity or list of entities based on direction and type.","remove-current-relations":"Remove current relations","remove-current-relations-hint":"Removes current relations from the originator of the incoming message based on direction and type.","change-originator-to-related-entity":"Change originator to related entity","change-originator-to-related-entity-hint":"Used to process submitted message as a message from another entity.","start-interval-pattern":"Start interval pattern","end-interval-pattern":"End interval pattern","start-interval-pattern-required":"Start interval pattern is required","end-interval-pattern-required":"End interval pattern is required","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","tls-version":"TLS version","enable-proxy":"Enable proxy","use-system-proxy-properties":"Use system proxy properties","proxy-host":"Proxy host","proxy-host-required":"Proxy host is required.","proxy-port":"Proxy port","proxy-port-required":"Proxy port is required.","proxy-port-range":"Proxy port should be in a range from 1 to 65535.","proxy-user":"Proxy user","proxy-password":"Proxy password","proxy-scheme":"Proxy scheme","numbers-to-template":"Phone Numbers To Template","numbers-to-template-required":"Phone Numbers To Template is required","numbers-to-template-hint":'Comma separated Phone Numbers, use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"sms-message-template":"SMS message Template","sms-message-template-required":"SMS message Template is required","use-system-sms-settings":"Use system SMS provider settings","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","interval-seconds":"Interval in seconds","interval-seconds-required":"Interval is required.","min-interval-seconds-message":"Only 1 second minimum interval is allowed.","output-timeseries-key-prefix":"Output timeseries key prefix","output-timeseries-key-prefix-required":"Output timeseries key prefix required.","separator-hint":'You should press "enter" to complete field input.',"entity-details":"Select entity details:","entity-details-title":"Title","entity-details-country":"Country","entity-details-state":"State","entity-details-city":"City","entity-details-zip":"Zip","entity-details-address":"Address","entity-details-address2":"Address2","entity-details-additional_info":"Additional Info","entity-details-phone":"Phone","entity-details-email":"Email","add-to-metadata":"Add selected details to message metadata","add-to-metadata-hint":"If selected, adds the selected details keys to the message metadata instead of message data.","entity-details-list-empty":"No entity details selected.","no-entity-details-matching":"No entity details matching were found.","custom-table-name":"Custom table name","custom-table-name-required":"Table Name is required","custom-table-hint":"You should enter the table name without prefix 'cs_tb_'.","message-field":"Message field","message-field-required":"Message field is required.","table-col":"Table column","table-col-required":"Table column is required.","latitude-key-name":"Latitude key name","longitude-key-name":"Longitude key name","latitude-key-name-required":"Latitude key name is required.","longitude-key-name-required":"Longitude key name is required.","fetch-perimeter-info-from-message-metadata":"Fetch perimeter information from message metadata","perimeter-key-name":"Perimeter key name","perimeter-key-name-required":"Perimeter key name is required.","perimeter-circle":"Circle","perimeter-polygon":"Polygon","perimeter-type":"Perimeter type","circle-center-latitude":"Center latitude","circle-center-latitude-required":"Center latitude is required.","circle-center-longitude":"Center longitude","circle-center-longitude-required":"Center longitude is required.","range-unit-meter":"Meter","range-unit-kilometer":"Kilometer","range-unit-foot":"Foot","range-unit-mile":"Mile","range-unit-nautical-mile":"Nautical mile","range-units":"Range units",range:"Range","range-required":"Range is required.","polygon-definition":"Polygon definition","polygon-definition-required":"Polygon definition is required.","polygon-definition-hint":"Please, use the following format for manual definition of polygon: [[lat1,lon1],[lat2,lon2], ... ,[latN,lonN]].","min-inside-duration":"Minimal inside duration","min-inside-duration-value-required":"Minimal inside duration is required","min-inside-duration-time-unit":"Minimal inside duration time unit","min-outside-duration":"Minimal outside duration","min-outside-duration-value-required":"Minimal outside duration is required","min-outside-duration-time-unit":"Minimal outside duration time unit","tell-failure-if-absent":"Tell Failure","tell-failure-if-absent-hint":'If at least one selected key doesn\'t exist the outbound message will report "Failure".',"get-latest-value-with-ts":"Fetch Latest telemetry with Timestamp","get-latest-value-with-ts-hint":'If selected, latest telemetry values will be added to the outbound message metadata with timestamp, e.g: "temp": "{"ts":1574329385897, "value":42}"',"use-redis-queue":"Use redis queue for message persistence","ignore-null-strings":"Ignore null strings","ignore-null-strings-hint":"If selected rule node will ignore entity fields with empty value.","trim-redis-queue":"Trim redis queue","redis-queue-max-size":"Redis queue max size","add-metadata-key-values-as-kafka-headers":"Add Message metadata key-value pairs to Kafka record headers","add-metadata-key-values-as-kafka-headers-hint":"If selected, key-value pairs from message metadata will be added to the outgoing records headers as byte arrays with predefined charset encoding.","charset-encoding":"Charset encoding","charset-encoding-required":"Charset encoding is required.","charset-us-ascii":"US-ASCII","charset-iso-8859-1":"ISO-8859-1","charset-utf-8":"UTF-8","charset-utf-16be":"UTF-16BE","charset-utf-16le":"UTF-16LE","charset-utf-16":"UTF-16","select-queue-hint":"The queue name can be selected from a drop-down list or add a custom name.","persist-alarm-rules":"Persist state of alarm rules","fetch-alarm-rules":"Fetch state of alarm rules","input-value-key":"Input value key","input-value-key-required":"Input value key is required.","output-value-key":"Output value key","output-value-key-required":"Output value key is required.",round:"Decimals","round-range":"Decimals should be in a range from 0 to 15.","use-cache":"Use cache for latest value","tell-failure-if-delta-is-negative":"Tell Failure if delta is negative","add-period-between-msgs":"Add period between messages","period-value-key":"Period value key","period-value-key-required":"Period value key is required.","general-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body',"alarm-severity-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body. Alarm severity should be system (CRITICAL, MAJOR etc.)',"output-node-name-hint":"The rule node name corresponds to the relation type of the output message, and it is used to forward messages to other rule nodes in the caller rule chain.","skip-latest-persistence":"Skip latest persistence","use-server-ts":"Use server ts","use-server-ts-hint":"Enable this setting to use the timestamp of the message processing instead of the timestamp from the message. Useful for all sorts of sequential processing if you merge messages from multiple sources (devices, assets, etc).","kv-map-pattern-hint":'Hint: use ${metadataKey} for value from metadata, $[messageKey] for value from message body to substitute "Source" and "Target" key names',"shared-scope":"Shared scope","server-scope":"Server scope","client-scope":"Client scope","attribute-type":"Attribute","constant-type":"Constant","time-series-type":"Time series","message-body-type":"Message body","message-metadata-type":"Message metadata","argument-tile":"Arguments","no-arguments-prompt":"No arguments configured","result-title":"Result","functions-field-input":"Functions","no-option-found":"No option found","argument-type-field-input":"Type","argument-type-field-input-required":"Argument type is required.","argument-key-field-input":"Key","argument-key-field-input-required":"Argument key is required.","constant-value-field-input":"Constant value","constant-value-field-input-required":"Constant value is required.","attribute-scope-field-input":"Attribute scope","attribute-scope-field-input-required":"Attribute scope os required.","default-value-field-input":"Default value","type-field-input":"Type","type-field-input-required":"Type is required.","key-field-input":"Key","key-field-input-required":"Key is required.","number-floating-point-field-input":"Number of digits after floating point","number-floating-point-field-input-hint":"Hint: use 0 to convert result to integer","add-to-body-field-input":"Add to message body","add-to-metadata-field-input":"Add to message metadata","custom-expression-field-input":"Mathematical Expression","custom-expression-field-input-required":"Mathematical expression is required","custom-expression-field-input-hint":"Hint: specify a mathematical expression to evaluate. For example, transform Fahrenheit to Celsius using (x - 32) / 1.8)","retained-message":"Retained"},"key-val":{key:"Key",value:"Value","remove-entry":"Remove entry","add-entry":"Add entry","unique-key-value-pair-error":"'{{valText}}' must be different from the current '{{keyText}}'"},"mail-body-type":{"plain-text":"Plain Text",html:"HTML",dynamic:"Dynamic"}}},!0)}(t)}}t("RuleNodeCoreConfigModule",So),So.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:So,deps:[{token:R.TranslateService}],target:e.ɵɵFactoryTarget.NgModule}),So.ɵmod=e.ɵɵngDeclareNgModule({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:So,declarations:[Dt],imports:[H,L],exports:[Ye,bo,so,To,qo,Dt]}),So.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:So,imports:[[H,L],Ye,bo,so,To,qo]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:So,decorators:[{type:i,args:[{declarations:[Dt],imports:[H,L],exports:[Ye,bo,so,To,qo,Dt]}]}],ctorParameters:function(){return[{type:R.TranslateService}]}})}}}));//# sourceMappingURL=rulenode-core-config.js.map diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java index d9706f6b96..b4b4563b4d 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/action/TbAlarmNodeTest.java @@ -42,6 +42,7 @@ import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -180,7 +181,7 @@ public class TbAlarmNodeTest { verifyError(msg, "message", NotImplementedException.class); - verify(ctx).createJsScriptEngine("DETAILS"); + verify(ctx).createScriptEngine(ScriptLanguage.JS, "DETAILS"); verify(ctx).getAlarmService(); verify(ctx, times(3)).getDbCallbackExecutor(); verify(ctx).logJsEvalRequest(); @@ -395,12 +396,13 @@ public class TbAlarmNodeTest { config.setPropagate(true); config.setSeverity("$[alarmSeverity]"); config.setAlarmType("SomeType"); + config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); config.setDynamicSeverity(true); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getAlarmService()).thenReturn(alarmService); @@ -456,6 +458,7 @@ public class TbAlarmNodeTest { public void testCreateAlarmWithDynamicSeverityFromMetadata() throws Exception { TbCreateAlarmNodeConfiguration config = new TbCreateAlarmNodeConfiguration(); config.setPropagate(true); + config.setScriptLang(ScriptLanguage.JS); config.setSeverity("${alarmSeverity}"); config.setAlarmType("SomeType"); config.setAlarmDetailsBuildJs("DETAILS"); @@ -463,7 +466,7 @@ public class TbAlarmNodeTest { ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getAlarmService()).thenReturn(alarmService); @@ -521,12 +524,13 @@ public class TbAlarmNodeTest { config.setPropagateToTenant(true); config.setSeverity(CRITICAL.name()); config.setAlarmType("SomeType" + i); + config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); config.setDynamicSeverity(true); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getAlarmService()).thenReturn(alarmService); @@ -584,11 +588,12 @@ public class TbAlarmNodeTest { config.setPropagate(true); config.setSeverity(CRITICAL.name()); config.setAlarmType("SomeType"); + config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getAlarmService()).thenReturn(alarmService); @@ -605,11 +610,12 @@ public class TbAlarmNodeTest { try { TbClearAlarmNodeConfiguration config = new TbClearAlarmNodeConfiguration(); config.setAlarmType("SomeType"); + config.setScriptLang(ScriptLanguage.JS); config.setAlarmDetailsBuildJs("DETAILS"); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("DETAILS")).thenReturn(detailsJs); + when(ctx.createScriptEngine(ScriptLanguage.JS, "DETAILS")).thenReturn(detailsJs); when(ctx.getTenantId()).thenReturn(tenantId); when(ctx.getAlarmService()).thenReturn(alarmService); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java index 63c94ea9a5..f40c6f0072 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsFilterNodeTest.java @@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -54,8 +55,6 @@ public class TbJsFilterNodeTest { @Mock private TbContext ctx; @Mock - private ListeningExecutor executor; - @Mock private ScriptEngine scriptEngine; private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); @@ -73,7 +72,7 @@ public class TbJsFilterNodeTest { } @Test - public void exceptionInJsThrowsException() throws TbNodeException, ScriptException { + public void exceptionInJsThrowsException() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); @@ -85,7 +84,7 @@ public class TbJsFilterNodeTest { } @Test - public void metadataConditionCanBeTrue() throws TbNodeException, ScriptException { + public void metadataConditionCanBeTrue() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); TbMsg msg = TbMsg.newMsg("USER", null, metaData, TbMsgDataType.JSON, "{}", ruleChainId, ruleNodeId); @@ -98,11 +97,12 @@ public class TbJsFilterNodeTest { private void initWithScript() throws TbNodeException { TbJsFilterNodeConfiguration config = new TbJsFilterNodeConfiguration(); + config.setScriptLang(ScriptLanguage.JS); config.setJsScript("scr"); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine); + when(ctx.createScriptEngine(ScriptLanguage.JS, "scr")).thenReturn(scriptEngine); node = new TbJsFilterNode(); node.init(ctx, nodeConfiguration); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java index 62770d466c..5cd3ef632a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/filter/TbJsSwitchNodeTest.java @@ -34,6 +34,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -56,8 +57,6 @@ public class TbJsSwitchNodeTest { @Mock private TbContext ctx; @Mock - private ListeningExecutor executor; - @Mock private ScriptEngine scriptEngine; private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); @@ -80,22 +79,14 @@ public class TbJsSwitchNodeTest { private void initWithScript() throws TbNodeException { TbJsSwitchNodeConfiguration config = new TbJsSwitchNodeConfiguration(); + config.setScriptLang(ScriptLanguage.JS); config.setJsScript("scr"); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine); + when(ctx.createScriptEngine(ScriptLanguage.JS, "scr")).thenReturn(scriptEngine); node = new TbJsSwitchNode(); node.init(ctx, nodeConfiguration); } - - private void verifyError(TbMsg msg, String message, Class expectedClass) { - ArgumentCaptor captor = ArgumentCaptor.forClass(Throwable.class); - verify(ctx).tellFailure(same(msg), captor.capture()); - - Throwable value = captor.getValue(); - assertEquals(expectedClass, value.getClass()); - assertEquals(message, value.getMessage()); - } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathArgumentValueTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathArgumentValueTest.java new file mode 100644 index 0000000000..d8ef3312b4 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathArgumentValueTest.java @@ -0,0 +1,117 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.math; + +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.junit.Assert; +import org.junit.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.math.TbMathArgument; +import org.thingsboard.rule.engine.math.TbMathArgumentType; +import org.thingsboard.rule.engine.math.TbMathArgumentValue; +import org.thingsboard.server.common.msg.TbMsgMetaData; + +import java.util.Optional; + +import static org.junit.jupiter.api.Assertions.assertThrows; + +public class TbMathArgumentValueTest { + + @Test + public void test_fromMessageBody_then_defaultValue() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + tbMathArgument.setDefaultValue(5.0); + TbMathArgumentValue result = TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.ofNullable(JacksonUtil.newObjectNode())); + Assert.assertEquals(5.0, result.getValue(), 0d); + } + + @Test + public void test_fromMessageBody_then_emptyBody() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + Throwable thrown = assertThrows(RuntimeException.class, () -> { + TbMathArgumentValue result = TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.empty()); + }); + Assert.assertNotNull(thrown.getMessage()); + } + + @Test + public void test_fromMessageBody_then_noKey() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.ofNullable(JacksonUtil.newObjectNode()))); + Assert.assertNotNull(thrown.getMessage()); + } + + @Test + public void test_fromMessageBody_then_valueEmpty() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + ObjectNode msgData = JacksonUtil.newObjectNode(); + msgData.putNull("TestKey"); + + //null value + Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData))); + Assert.assertNotNull(thrown.getMessage()); + + //empty value + msgData.put("TestKey", ""); + thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData))); + Assert.assertNotNull(thrown.getMessage()); + } + + @Test + public void test_fromMessageBody_then_valueCantConvert_to_double() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + ObjectNode msgData = JacksonUtil.newObjectNode(); + msgData.put("TestKey", "Test"); + + //string value + Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData))); + Assert.assertNotNull(thrown.getMessage()); + + //object value + msgData.set("TestKey", JacksonUtil.newObjectNode()); + thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageBody(tbMathArgument, Optional.of(msgData))); + Assert.assertNotNull(thrown.getMessage()); + } + + @Test + public void test_fromMessageMetadata_then_noKey() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageMetadata(tbMathArgument, new TbMsgMetaData())); + Assert.assertNotNull(thrown.getMessage()); + } + + @Test + public void test_fromMessageMetadata_then_valueEmpty() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromMessageMetadata(tbMathArgument, null)); + Assert.assertNotNull(thrown.getMessage()); + } + + @Test + public void test_fromString_thenOK() { + var value = "5.0"; + TbMathArgumentValue result = TbMathArgumentValue.fromString(value); + Assert.assertNotNull(result); + Assert.assertEquals(5.0, result.getValue(), 0d); + } + + @Test + public void test_fromString_then_failure() { + var value = "Test"; + Throwable thrown = assertThrows(RuntimeException.class, () -> TbMathArgumentValue.fromString(value)); + Assert.assertNotNull(thrown.getMessage()); + } +} diff --git a/rule-engine/rule-engine-components/src/test/java/math/TbMathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java similarity index 63% rename from rule-engine/rule-engine-components/src/test/java/math/TbMathNodeTest.java rename to rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java index 5a0cf6eb91..d682e398e8 100644 --- a/rule-engine/rule-engine-components/src/test/java/math/TbMathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/math/TbMathNodeTest.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package math; +package org.thingsboard.rule.engine.math; import com.datastax.oss.driver.api.core.uuid.Uuids; import com.google.common.util.concurrent.Futures; @@ -28,6 +28,7 @@ import org.mockito.Mockito; import org.mockito.junit.MockitoJUnitRunner; import org.thingsboard.common.util.AbstractListeningExecutor; import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.RuleEngineTelemetryService; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; @@ -45,6 +46,7 @@ import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; import org.thingsboard.server.common.data.kv.BasicTsKvEntry; import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.dao.attributes.AttributesService; @@ -53,7 +55,13 @@ import org.thingsboard.server.dao.timeseries.TimeseriesService; import java.util.Arrays; import java.util.Optional; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyDouble; +import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; @RunWith(MockitoJUnitRunner.class) public class TbMathNodeTest { @@ -67,7 +75,8 @@ public class TbMathNodeTest { private AttributesService attributesService; @Mock private TimeseriesService tsService; - + @Mock + private RuleEngineTelemetryService telemetryService; private AbstractListeningExecutor dbExecutor; @Before @@ -91,7 +100,9 @@ public class TbMathNodeTest { Mockito.reset(ctx); Mockito.reset(attributesService); Mockito.reset(tsService); + Mockito.reset(telemetryService); lenient().when(ctx.getAttributesService()).thenReturn(attributesService); + lenient().when(ctx.getTelemetryService()).thenReturn(telemetryService); lenient().when(ctx.getTimeseriesService()).thenReturn(tsService); lenient().when(ctx.getTenantId()).thenReturn(tenantId); lenient().when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); @@ -163,10 +174,36 @@ public class TbMathNodeTest { testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TAN, Math.toRadians(45), 1); testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TAN, Math.toRadians(0), 0); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.TANH, 90, 1); + + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ACOS, 0.5, 1.05); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ASIN, 0.5, 0.52); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ATAN, 0.5, 0.46); + testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.ATAN2, 0.5, 0.3, 1.03); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.EXP, 1, 2.72); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.EXPM1, 1, 1.72); testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.ABS, -1, 1); testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SQRT, 4, 2); testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.CBRT, 8, 2); + + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.GET_EXP, 4, 2); + testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.HYPOT, 4, 5, 6.4); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.LOG, 4, 1.39); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.LOG10, 4, 0.6); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.LOG1P, 4, 1.61); + + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.CEIL, 1.55, 2); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.FLOOR, 23.97, 23); + testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.FLOOR_DIV, 5, 3, 1); + testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.FLOOR_MOD, 6, 3, 0); + + testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.MIN, 5, 3, 3); + testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.MAX, 5, 3, 5); + testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType.POW, 5, 3, 125); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.SIGNUM, 0.55, 1); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.RAD, 5, 0.09); + testSimpleOneArgumentFunction(TbRuleNodeMathFunctionType.DEG, 5, 286.48); } private void testSimpleTwoArgumentFunction(TbRuleNodeMathFunctionType function, double arg1, double arg2, double result) { @@ -336,4 +373,133 @@ public class TbMathNodeTest { Assert.assertEquals("2.236", result); } + @Test + public void test_sqrt_5_to_attribute_and_metadata() { + var node = initNode(TbRuleNodeMathFunctionType.SQRT, + new TbMathResult(TbMathArgumentType.ATTRIBUTE, "result", 3, false, true, DataConstants.SERVER_SCOPE), + new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") + ); + + TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + + Mockito.when(telemetryService.saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble())) + .thenReturn(Futures.immediateFuture(null)); + + node.onMsg(ctx, msg); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); + Mockito.verify(telemetryService, times(1)).saveAttrAndNotify(any(), any(), anyString(), anyString(), anyDouble()); + + TbMsg resultMsg = msgCaptor.getValue(); + Assert.assertNotNull(resultMsg); + Assert.assertNotNull(resultMsg.getData()); + var result = resultMsg.getMetaData().getValue("result"); + Assert.assertNotNull(result); + Assert.assertEquals("2.236", result); + } + + @Test + public void test_sqrt_5_to_timeseries_and_data() { + var node = initNode(TbRuleNodeMathFunctionType.SQRT, + new TbMathResult(TbMathArgumentType.TIME_SERIES, "result", 3, true, false, DataConstants.SERVER_SCOPE), + new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") + ); + + TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) + .thenReturn(Futures.immediateFuture(null)); + + node.onMsg(ctx, msg); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); + Mockito.verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class)); + + TbMsg resultMsg = msgCaptor.getValue(); + Assert.assertNotNull(resultMsg); + Assert.assertNotNull(resultMsg.getData()); + var resultJson = JacksonUtil.toJsonNode(resultMsg.getData()); + Assert.assertTrue(resultJson.has("result")); + Assert.assertEquals(2.236, resultJson.get("result").asDouble(), 0.0); + } + + @Test + public void test_sqrt_5_to_timeseries_and_metadata_and_data() { + var node = initNode(TbRuleNodeMathFunctionType.SQRT, + new TbMathResult(TbMathArgumentType.TIME_SERIES, "result", 3, true, true, DataConstants.SERVER_SCOPE), + new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") + ); + + TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 5).toString()); + Mockito.when(telemetryService.saveAndNotify(any(), any(), any(TsKvEntry.class))) + .thenReturn(Futures.immediateFuture(null)); + + node.onMsg(ctx, msg); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); + Mockito.verify(telemetryService, times(1)).saveAndNotify(any(), any(), any(TsKvEntry.class)); + + TbMsg resultMsg = msgCaptor.getValue(); + Assert.assertNotNull(resultMsg); + Assert.assertNotNull(resultMsg.getData()); + var resultMetadata = resultMsg.getMetaData().getValue("result"); + var resultData = JacksonUtil.toJsonNode(resultMsg.getData()); + + Assert.assertTrue(resultData.has("result")); + Assert.assertEquals(2.236, resultData.get("result").asDouble(), 0.0); + + Assert.assertNotNull(resultMetadata); + Assert.assertEquals("2.236", resultMetadata); + } + + @Test + public void test_sqrt_5_default_value() { + TbMathArgument tbMathArgument = new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey"); + tbMathArgument.setDefaultValue(5.0); + var node = initNode(TbRuleNodeMathFunctionType.SQRT, + new TbMathResult(TbMathArgumentType.MESSAGE_METADATA, "result", 3, false, false, null), + tbMathArgument + ); + TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + + node.onMsg(ctx, msg); + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); + + TbMsg resultMsg = msgCaptor.getValue(); + Assert.assertNotNull(resultMsg); + Assert.assertNotNull(resultMsg.getData()); + var result = resultMsg.getMetaData().getValue("result"); + Assert.assertNotNull(result); + Assert.assertEquals("2.236", result); + } + + @Test + public void test_sqrt_5_default_value_failure() { + var node = initNode(TbRuleNodeMathFunctionType.SQRT, + new TbMathResult(TbMathArgumentType.TIME_SERIES, "result", 3, true, false, DataConstants.SERVER_SCOPE), + new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "TestKey") + ); + TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), JacksonUtil.newObjectNode().put("a", 10).toString()); + Throwable thrown = assertThrows(RuntimeException.class, () -> { + node.onMsg(ctx, msg); + }); + Assert.assertNotNull(thrown.getMessage()); + } + + @Test + public void testConvertMsgBodyIfRequiredFailure() { + var node = initNode(TbRuleNodeMathFunctionType.SQRT, + new TbMathResult(TbMathArgumentType.MESSAGE_BODY, "result", 3, true, false, DataConstants.SERVER_SCOPE), + new TbMathArgument(TbMathArgumentType.MESSAGE_BODY, "a") + ); + + TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), "[]"); + Throwable thrown = assertThrows(RuntimeException.class, () -> { + node.onMsg(ctx, msg); + }); + Assert.assertNotNull(thrown.getMessage()); + } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNodeTest.java new file mode 100644 index 0000000000..80554445ed --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbAbstractGetAttributesNodeTest.java @@ -0,0 +1,338 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.metadata; + +import com.datastax.oss.driver.api.core.uuid.Uuids; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.common.util.concurrent.Futures; +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.AbstractListeningExecutor; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.dao.attributes.AttributesService; +import org.thingsboard.server.dao.timeseries.TimeseriesService; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.lenient; +import static org.mockito.Mockito.never; + +@RunWith(MockitoJUnitRunner.class) +public class TbAbstractGetAttributesNodeTest { + + final ObjectMapper mapper = new ObjectMapper(); + + private EntityId originator = new DeviceId(Uuids.timeBased()); + private TenantId tenantId = TenantId.fromUUID(Uuids.timeBased()); + + @Mock + private TbContext ctx; + @Mock + private AttributesService attributesService; + @Mock + private TimeseriesService tsService; + private AbstractListeningExecutor dbExecutor; + + private List clientAttributes; + private List serverAttributes; + private List sharedAttributes; + private List tsKeys; + private long ts; + + @Before + public void before() throws TbNodeException { + dbExecutor = new AbstractListeningExecutor() { + @Override + protected int getThreadPollSize() { + return 3; + } + }; + dbExecutor.init(); + + Mockito.reset(ctx); + Mockito.reset(attributesService); + Mockito.reset(tsService); + + Mockito.reset(ctx); + Mockito.reset(attributesService); + Mockito.reset(tsService); + + lenient().when(ctx.getAttributesService()).thenReturn(attributesService); + lenient().when(ctx.getTimeseriesService()).thenReturn(tsService); + lenient().when(ctx.getTenantId()).thenReturn(tenantId); + lenient().when(ctx.getDbCallbackExecutor()).thenReturn(dbExecutor); + + clientAttributes = getAttributeNames("client"); + serverAttributes = getAttributeNames("server"); + sharedAttributes = getAttributeNames("shared"); + tsKeys = List.of("temperature", "humidity", "unknown"); + ts = System.currentTimeMillis(); + + Mockito.when(attributesService.find(tenantId, originator, DataConstants.CLIENT_SCOPE, clientAttributes)) + .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(clientAttributes, ts))); + + + Mockito.when(attributesService.find(tenantId, originator, DataConstants.SERVER_SCOPE, serverAttributes)) + .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(serverAttributes, ts))); + + + Mockito.when(attributesService.find(tenantId, originator, DataConstants.SHARED_SCOPE, sharedAttributes)) + .thenReturn(Futures.immediateFuture(getListAttributeKvEntry(sharedAttributes, ts))); + + Mockito.when(tsService.findLatest(tenantId, originator, tsKeys)) + .thenReturn(Futures.immediateFuture(getListTsKvEntry(tsKeys, ts))); + } + + @After + public void after() { + dbExecutor.destroy(); + } + + @Test + public void fetchToMetadata_whenOnMsg_then_success() throws Exception { + TbGetAttributesNode node = initNode(false, false, false); + TbMsg msg = getTbMsg(originator); + node.onMsg(ctx, msg); + + TbMsg resultMsg = checkMsg(); + TbMsgMetaData msgMetaData = resultMsg.getMetaData(); + + //check attributes + checkAttributes(clientAttributes, "cs_", false, msgMetaData, null); + checkAttributes(serverAttributes, "ss_", false, msgMetaData, null); + checkAttributes(sharedAttributes, "shared_", false, msgMetaData, null); + + //check timeseries + checkTs(tsKeys, false, false, msgMetaData, null); + } + + @Test + public void fetchToMetadata_latestWithTs_whenOnMsg_then_success() throws Exception { + TbGetAttributesNode node = initNode(false, true, false); + TbMsg msg = getTbMsg(originator); + node.onMsg(ctx, msg); + + TbMsg resultMsg = checkMsg(); + TbMsgMetaData msgMetaData = resultMsg.getMetaData(); + + //check attributes + checkAttributes(clientAttributes, "cs_", false, msgMetaData, null); + checkAttributes(serverAttributes, "ss_", false, msgMetaData, null); + checkAttributes(sharedAttributes, "shared_", false, msgMetaData, null); + + //check timeseries with ts + checkTs(tsKeys, false, true, msgMetaData, null); + } + + @Test + public void fetchToData_whenOnMsg_then_success() throws Exception { + TbGetAttributesNode node = initNode(true, false, false); + TbMsg msg = getTbMsg(originator); + node.onMsg(ctx, msg); + + TbMsg resultMsg = checkMsg(); + JsonNode msgData = JacksonUtil.toJsonNode(resultMsg.getData()); + + //check attributes + checkAttributes(clientAttributes, "cs_", true, null, msgData); + checkAttributes(serverAttributes, "ss_", true, null, msgData); + checkAttributes(sharedAttributes, "shared_", true, null, msgData); + + //check timeseries + checkTs(tsKeys, true, false, null, msgData); + } + + @Test + public void fetchToData_latestWithTs_whenOnMsg_then_success() throws Exception { + TbGetAttributesNode node = initNode(true, true, false); + TbMsg msg = getTbMsg(originator); + node.onMsg(ctx, msg); + + TbMsg resultMsg = checkMsg(); + JsonNode msgData = JacksonUtil.toJsonNode(resultMsg.getData()); + + //check attributes + checkAttributes(clientAttributes, "cs_", true, null, msgData); + checkAttributes(serverAttributes, "ss_", true, null, msgData); + checkAttributes(sharedAttributes, "shared_", true, null, msgData); + + //check timeseries with ts + checkTs(tsKeys, true, true, null, msgData); + } + + @Test + public void fetchToData_whenOnMsg_then_failure() throws Exception { + TbGetAttributesNode node = initNode(true, true, true); + TbMsg msg = getTbMsg(originator); + node.onMsg(ctx, msg); + + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(Exception.class); + Mockito.verify(ctx, never()).tellSuccess(any()); + Mockito.verify(ctx, Mockito.timeout(5000)).tellFailure(newMsgCaptor.capture(), exceptionCaptor.capture()); + + Assert.assertSame(newMsgCaptor.getValue(), msg); + Assert.assertNotNull(exceptionCaptor.getValue()); + } + + @Test + public void fetchToData_whenOnMsg_then_data_not_object_failure() throws Exception { + TbGetAttributesNode node = initNode(true, true, true); + TbMsg msg = TbMsg.newMsg("TEST", originator, new TbMsgMetaData(), "[]"); + node.onMsg(ctx, msg); + + ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor exceptionCaptor = ArgumentCaptor.forClass(Exception.class); + Mockito.verify(ctx, never()).tellSuccess(any()); + Mockito.verify(ctx, Mockito.timeout(5000)).tellFailure(newMsgCaptor.capture(), exceptionCaptor.capture()); + + Assert.assertSame(newMsgCaptor.getValue(), msg); + Assert.assertNotNull(exceptionCaptor.getValue()); + } + + private TbMsg checkMsg() { + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + Mockito.verify(ctx, Mockito.timeout(5000)).tellSuccess(msgCaptor.capture()); + + TbMsg resultMsg = msgCaptor.getValue(); + Assert.assertNotNull(resultMsg); + Assert.assertNotNull(resultMsg.getMetaData()); + Assert.assertNotNull(resultMsg.getData()); + return resultMsg; + } + + private void checkAttributes(List attributes, String prefix, boolean fetchToData, TbMsgMetaData msgMetaData, JsonNode msgData) { + attributes.stream() + .filter(attribute -> !attribute.equals("unknown")) + .forEach(attribute -> { + String result; + if (fetchToData) { + result = msgData.get(prefix + attribute).asText(); + } else { + result = msgMetaData.getValue(prefix + attribute); + } + Assert.assertNotNull(result); + Assert.assertEquals(attribute + "_value", result); + }); + } + + private void checkTs(List tsKeys, boolean fetchToData, boolean getLatestValueWithTs, TbMsgMetaData msgMetaData, JsonNode msgData) { + long value = 1L; + for (String key : tsKeys) { + if (key.equals("unknown")) { + continue; + } + String actualValue; + String expectedValue; + if (getLatestValueWithTs) { + expectedValue = "{\"ts\":" + ts + ",\"value\":{\"data\":" + value + "}}"; + } else { + expectedValue = "{\"data\":" + value + "}"; + } + if (fetchToData) { + actualValue = JacksonUtil.toString(msgData.get(key)); + } else { + actualValue = msgMetaData.getValue(key); + } + Assert.assertNotNull(actualValue); + Assert.assertEquals(expectedValue, actualValue); + value++; + } + } + + private TbGetAttributesNode initNode(boolean fetchToData, boolean getLatestValueWithTs, boolean isTellFailureIfAbsent) throws TbNodeException { + TbGetAttributesNodeConfiguration config = new TbGetAttributesNodeConfiguration(); + config.setClientAttributeNames(List.of("client_attr_1", "client_attr_2", "${client_attr_metadata}", "unknown")); + config.setServerAttributeNames(List.of("server_attr_1", "server_attr_2", "${server_attr_metadata}", "unknown")); + config.setSharedAttributeNames(List.of("shared_attr_1", "shared_attr_2", "$[shared_attr_data]", "unknown")); + config.setLatestTsKeyNames(List.of("temperature", "humidity", "unknown")); + config.setFetchToData(fetchToData); + config.setGetLatestValueWithTs(getLatestValueWithTs); + config.setTellFailureIfAbsent(isTellFailureIfAbsent); + TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + TbGetAttributesNode node = new TbGetAttributesNode(); + node.init(ctx, nodeConfiguration); + return node; + } + + private TbMsg getTbMsg(EntityId entityId) { + ObjectNode msgData = JacksonUtil.newObjectNode(); + msgData.put("shared_attr_data", "shared_attr_3"); + + TbMsgMetaData msgMetaData = new TbMsgMetaData(); + msgMetaData.putValue("client_attr_metadata", "client_attr_3"); + msgMetaData.putValue("server_attr_metadata", "server_attr_3"); + + return TbMsg.newMsg("TEST", entityId, msgMetaData, msgData.toString()); + } + + private List getAttributeNames(String prefix) { + return List.of(prefix + "_attr_1", prefix + "_attr_2", prefix + "_attr_3", "unknown"); + } + + private List getListAttributeKvEntry(List attributes, long ts) { + return attributes.stream() + .filter(attribute -> !attribute.equals("unknown")) + .map(attribute -> toAttributeKvEntry(ts, attribute)) + .collect(Collectors.toList()); + } + + private BaseAttributeKvEntry toAttributeKvEntry(long ts, String attribute) { + return new BaseAttributeKvEntry(ts, new StringDataEntry(attribute, attribute + "_value")); + } + + private List getListTsKvEntry(List keys, long ts) { + long value = 1L; + List kvEntries = new ArrayList<>(); + for (String key : keys) { + if (key.equals("unknown")) { + continue; + } + String dataValue = "{\"data\":" + value + "}"; + kvEntries.add(new BasicTsKvEntry(ts, new JsonDataEntry(key, dataValue))); + value++; + } + return kvEntries; + } + +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java new file mode 100644 index 0000000000..095505aff3 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNodeTest.java @@ -0,0 +1,119 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.rpc; + +import com.google.common.util.concurrent.SettableFuture; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.Mockito; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.common.util.ListeningExecutor; +import org.thingsboard.rule.engine.api.RuleEngineRpcService; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.rule.engine.api.TbNodeConfiguration; +import org.thingsboard.rule.engine.api.TbNodeException; +import org.thingsboard.server.common.data.DataConstants; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.msg.TbMsg; +import org.thingsboard.server.common.msg.TbMsgDataType; +import org.thingsboard.server.common.msg.TbMsgMetaData; +import org.thingsboard.server.common.msg.session.SessionMsgType; +import org.thingsboard.server.dao.edge.EdgeEventService; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; + +@RunWith(MockitoJUnitRunner.class) +public class TbSendRPCReplyNodeTest { + + private static final String DUMMY_SERVICE_ID = "testServiceId"; + private static final int DUMMY_REQUEST_ID = 0; + private static final UUID DUMMY_SESSION_ID = UUID.randomUUID(); + private static final String DUMMY_DATA = "{\"key\":\"value\"}"; + + TbSendRPCReplyNode node; + + private final TenantId tenantId = TenantId.fromUUID(UUID.randomUUID()); + private final DeviceId deviceId = new DeviceId(UUID.randomUUID()); + + @Mock + private TbContext ctx; + + @Mock + private RuleEngineRpcService rpcService; + + @Mock + private EdgeEventService edgeEventService; + + @Mock + private ListeningExecutor listeningExecutor; + + @Before + public void setUp() throws TbNodeException { + node = new TbSendRPCReplyNode(); + TbSendRpcReplyNodeConfiguration config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration(); + node.init(ctx, new TbNodeConfiguration(JacksonUtil.valueToTree(config))); + } + + @Test + public void sendReplyToTransport() { + Mockito.when(ctx.getRpcService()).thenReturn(rpcService); + + + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, getDefaultMetadata(), + TbMsgDataType.JSON, DUMMY_DATA, null, null); + + node.onMsg(ctx, msg); + + verify(rpcService).sendRpcReplyToDevice(DUMMY_SERVICE_ID, DUMMY_SESSION_ID, DUMMY_REQUEST_ID, DUMMY_DATA); + verify(edgeEventService, never()).saveAsync(any()); + } + + @Test + public void sendReplyToEdgeQueue() { + Mockito.when(ctx.getTenantId()).thenReturn(tenantId); + Mockito.when(ctx.getEdgeEventService()).thenReturn(edgeEventService); + Mockito.when(edgeEventService.saveAsync(any())).thenReturn(SettableFuture.create()); + Mockito.when(ctx.getDbCallbackExecutor()).thenReturn(listeningExecutor); + + TbMsgMetaData defaultMetadata = getDefaultMetadata(); + defaultMetadata.putValue(DataConstants.EDGE_ID, UUID.randomUUID().toString()); + defaultMetadata.putValue(DataConstants.DEVICE_ID, UUID.randomUUID().toString()); + TbMsg msg = TbMsg.newMsg(SessionMsgType.POST_TELEMETRY_REQUEST.name(), deviceId, defaultMetadata, + TbMsgDataType.JSON, DUMMY_DATA, null, null); + + node.onMsg(ctx, msg); + + verify(edgeEventService).saveAsync(any()); + verify(rpcService, never()).sendRpcReplyToDevice(DUMMY_SERVICE_ID, DUMMY_SESSION_ID, DUMMY_REQUEST_ID, DUMMY_DATA); + } + + private TbMsgMetaData getDefaultMetadata() { + TbSendRpcReplyNodeConfiguration config = new TbSendRpcReplyNodeConfiguration().defaultConfiguration(); + TbMsgMetaData metadata = new TbMsgMetaData(); + metadata.putValue(config.getServiceIdMetaDataAttribute(), DUMMY_SERVICE_ID); + metadata.putValue(config.getSessionIdMetaDataAttribute(), DUMMY_SESSION_ID.toString()); + metadata.putValue(config.getRequestIdMetaDataAttribute(), Integer.toString(DUMMY_REQUEST_ID)); + return metadata; + } +} diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java index 411db21c7f..89480b404e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbTransformMsgNodeTest.java @@ -33,6 +33,7 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.script.ScriptLanguage; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgDataType; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -55,12 +56,10 @@ public class TbTransformMsgNodeTest { @Mock private TbContext ctx; @Mock - private ListeningExecutor executor; - @Mock private ScriptEngine scriptEngine; @Test - public void metadataCanBeUpdated() throws TbNodeException, ScriptException { + public void metadataCanBeUpdated() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("temp", "7"); @@ -80,7 +79,7 @@ public class TbTransformMsgNodeTest { } @Test - public void exceptionHandledCorrectly() throws TbNodeException, ScriptException { + public void exceptionHandledCorrectly() throws TbNodeException { initWithScript(); TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("temp", "7"); @@ -97,11 +96,12 @@ public class TbTransformMsgNodeTest { private void initWithScript() throws TbNodeException { TbTransformMsgNodeConfiguration config = new TbTransformMsgNodeConfiguration(); + config.setScriptLang(ScriptLanguage.JS); config.setJsScript("scr"); ObjectMapper mapper = new ObjectMapper(); TbNodeConfiguration nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - when(ctx.createJsScriptEngine("scr")).thenReturn(scriptEngine); + when(ctx.createScriptEngine(ScriptLanguage.JS, "scr")).thenReturn(scriptEngine); node = new TbTransformMsgNode(); node.init(ctx, nodeConfiguration); diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java new file mode 100644 index 0000000000..8a53148204 --- /dev/null +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/util/TenantIdLoaderTest.java @@ -0,0 +1,351 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.thingsboard.rule.engine.util; + +import org.junit.After; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; +import org.thingsboard.common.util.AbstractListeningExecutor; +import org.thingsboard.rule.engine.api.RuleEngineAlarmService; +import org.thingsboard.rule.engine.api.RuleEngineApiUsageStateService; +import org.thingsboard.rule.engine.api.RuleEngineAssetProfileCache; +import org.thingsboard.rule.engine.api.RuleEngineDeviceProfileCache; +import org.thingsboard.rule.engine.api.RuleEngineRpcService; +import org.thingsboard.rule.engine.api.TbContext; +import org.thingsboard.server.common.data.ApiUsageState; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.OtaPackage; +import org.thingsboard.server.common.data.TbResource; +import org.thingsboard.server.common.data.TenantProfile; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.asset.Asset; +import org.thingsboard.server.common.data.asset.AssetProfile; +import org.thingsboard.server.common.data.edge.Edge; +import org.thingsboard.server.common.data.id.AssetProfileId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityIdFactory; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.TenantProfileId; +import org.thingsboard.server.common.data.queue.Queue; +import org.thingsboard.server.common.data.rpc.Rpc; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleNode; +import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.dao.asset.AssetService; +import org.thingsboard.server.dao.customer.CustomerService; +import org.thingsboard.server.dao.dashboard.DashboardService; +import org.thingsboard.server.dao.device.DeviceService; +import org.thingsboard.server.dao.edge.EdgeService; +import org.thingsboard.server.dao.entityview.EntityViewService; +import org.thingsboard.server.dao.ota.OtaPackageService; +import org.thingsboard.server.dao.queue.QueueService; +import org.thingsboard.server.dao.resource.ResourceService; +import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.user.UserService; +import org.thingsboard.server.dao.widget.WidgetTypeService; +import org.thingsboard.server.dao.widget.WidgetsBundleService; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.when; + +@RunWith(MockitoJUnitRunner.class) +public class TenantIdLoaderTest { + + @Mock + private TbContext ctx; + @Mock + private CustomerService customerService; + @Mock + private UserService userService; + @Mock + private AssetService assetService; + @Mock + private DeviceService deviceService; + @Mock + private RuleEngineAlarmService alarmService; + @Mock + private RuleChainService ruleChainService; + @Mock + private EntityViewService entityViewService; + @Mock + private DashboardService dashboardService; + @Mock + private EdgeService edgeService; + @Mock + private OtaPackageService otaPackageService; + @Mock + private RuleEngineAssetProfileCache assetProfileCache; + @Mock + private RuleEngineDeviceProfileCache deviceProfileCache; + @Mock + private WidgetTypeService widgetTypeService; + @Mock + private WidgetsBundleService widgetsBundleService; + @Mock + private QueueService queueService; + @Mock + private ResourceService resourceService; + @Mock + private RuleEngineRpcService rpcService; + @Mock + private RuleEngineApiUsageStateService ruleEngineApiUsageStateService; + + private TenantId tenantId; + private TenantProfileId tenantProfileId; + private AbstractListeningExecutor dbExecutor; + + @Before + public void before() { + dbExecutor = new AbstractListeningExecutor() { + @Override + protected int getThreadPollSize() { + return 3; + } + }; + dbExecutor.init(); + this.tenantId = new TenantId(UUID.randomUUID()); + this.tenantProfileId = new TenantProfileId(UUID.randomUUID()); + + when(ctx.getTenantId()).thenReturn(tenantId); + + for (EntityType entityType : EntityType.values()) { + initMocks(entityType, tenantId); + } + } + + @After + public void after() { + dbExecutor.destroy(); + } + + private void initMocks(EntityType entityType, TenantId tenantId) { + switch (entityType) { + case TENANT: + break; + case CUSTOMER: + Customer customer = new Customer(); + customer.setTenantId(tenantId); + + when(ctx.getCustomerService()).thenReturn(customerService); + doReturn(customer).when(customerService).findCustomerById(eq(tenantId), any()); + + break; + case USER: + User user = new User(); + user.setTenantId(tenantId); + + when(ctx.getUserService()).thenReturn(userService); + doReturn(user).when(userService).findUserById(eq(tenantId), any()); + + break; + case ASSET: + Asset asset = new Asset(); + asset.setTenantId(tenantId); + + when(ctx.getAssetService()).thenReturn(assetService); + doReturn(asset).when(assetService).findAssetById(eq(tenantId), any()); + + break; + case DEVICE: + Device device = new Device(); + device.setTenantId(tenantId); + + when(ctx.getDeviceService()).thenReturn(deviceService); + doReturn(device).when(deviceService).findDeviceById(eq(tenantId), any()); + + break; + case ALARM: + Alarm alarm = new Alarm(); + alarm.setTenantId(tenantId); + + when(ctx.getAlarmService()).thenReturn(alarmService); + doReturn(alarm).when(alarmService).findAlarmById(eq(tenantId), any()); + + break; + case RULE_CHAIN: + RuleChain ruleChain = new RuleChain(); + ruleChain.setTenantId(tenantId); + + when(ctx.getRuleChainService()).thenReturn(ruleChainService); + doReturn(ruleChain).when(ruleChainService).findRuleChainById(eq(tenantId), any()); + + break; + case ENTITY_VIEW: + EntityView entityView = new EntityView(); + entityView.setTenantId(tenantId); + + when(ctx.getEntityViewService()).thenReturn(entityViewService); + doReturn(entityView).when(entityViewService).findEntityViewById(eq(tenantId), any()); + + break; + case DASHBOARD: + Dashboard dashboard = new Dashboard(); + dashboard.setTenantId(tenantId); + + when(ctx.getDashboardService()).thenReturn(dashboardService); + doReturn(dashboard).when(dashboardService).findDashboardById(eq(tenantId), any()); + + break; + case EDGE: + Edge edge = new Edge(); + edge.setTenantId(tenantId); + + when(ctx.getEdgeService()).thenReturn(edgeService); + doReturn(edge).when(edgeService).findEdgeById(eq(tenantId), any()); + + break; + case OTA_PACKAGE: + OtaPackage otaPackage = new OtaPackage(); + otaPackage.setTenantId(tenantId); + + when(ctx.getOtaPackageService()).thenReturn(otaPackageService); + doReturn(otaPackage).when(otaPackageService).findOtaPackageInfoById(eq(tenantId), any()); + + break; + case ASSET_PROFILE: + AssetProfile assetProfile = new AssetProfile(); + assetProfile.setTenantId(tenantId); + + when(ctx.getAssetProfileCache()).thenReturn(assetProfileCache); + doReturn(assetProfile).when(assetProfileCache).get(eq(tenantId), any(AssetProfileId.class)); + + break; + case DEVICE_PROFILE: + DeviceProfile deviceProfile = new DeviceProfile(); + deviceProfile.setTenantId(tenantId); + + when(ctx.getDeviceProfileCache()).thenReturn(deviceProfileCache); + doReturn(deviceProfile).when(deviceProfileCache).get(eq(tenantId), any(DeviceProfileId.class)); + + break; + case WIDGET_TYPE: + WidgetType widgetType = new WidgetType(); + widgetType.setTenantId(tenantId); + + when(ctx.getWidgetTypeService()).thenReturn(widgetTypeService); + doReturn(widgetType).when(widgetTypeService).findWidgetTypeById(eq(tenantId), any()); + + break; + case WIDGETS_BUNDLE: + WidgetsBundle widgetsBundle = new WidgetsBundle(); + widgetsBundle.setTenantId(tenantId); + + when(ctx.getWidgetBundleService()).thenReturn(widgetsBundleService); + doReturn(widgetsBundle).when(widgetsBundleService).findWidgetsBundleById(eq(tenantId), any()); + + break; + case RPC: + Rpc rpc = new Rpc(); + rpc.setTenantId(tenantId); + + when(ctx.getRpcService()).thenReturn(rpcService); + doReturn(rpc).when(rpcService).findRpcById(eq(tenantId), any()); + + break; + case QUEUE: + Queue queue = new Queue(); + queue.setTenantId(tenantId); + + when(ctx.getQueueService()).thenReturn(queueService); + doReturn(queue).when(queueService).findQueueById(eq(tenantId), any()); + + break; + case API_USAGE_STATE: + ApiUsageState apiUsageState = new ApiUsageState(); + apiUsageState.setTenantId(tenantId); + + when(ctx.getRuleEngineApiUsageStateService()).thenReturn(ruleEngineApiUsageStateService); + doReturn(apiUsageState).when(ruleEngineApiUsageStateService).findApiUsageStateById(eq(tenantId), any()); + + break; + case TB_RESOURCE: + TbResource tbResource = new TbResource(); + tbResource.setTenantId(tenantId); + + when(ctx.getResourceService()).thenReturn(resourceService); + doReturn(tbResource).when(resourceService).findResourceInfoById(eq(tenantId), any()); + + break; + case RULE_NODE: + RuleNode ruleNode = new RuleNode(); + + when(ctx.getRuleChainService()).thenReturn(ruleChainService); + doReturn(ruleNode).when(ruleChainService).findRuleNodeById(eq(tenantId), any()); + + break; + case TENANT_PROFILE: + TenantProfile tenantProfile = new TenantProfile(tenantProfileId); + + when(ctx.getTenantProfile()).thenReturn(tenantProfile); + + break; + default: + throw new RuntimeException("Unexpected original EntityType " + entityType); + } + + } + + private EntityId getEntityId(EntityType entityType) { + return EntityIdFactory.getByTypeAndUuid(entityType, UUID.randomUUID()); + } + + private void checkTenant(TenantId checkTenantId, boolean equals) { + for (EntityType entityType : EntityType.values()) { + EntityId entityId; + if (EntityType.TENANT.equals(entityType)) { + entityId = tenantId; + } else if (EntityType.TENANT_PROFILE.equals(entityType)) { + entityId = tenantProfileId; + } else { + entityId = getEntityId(entityType); + } + TenantId targetTenantId = TenantIdLoader.findTenantId(ctx, entityId); + String msg = "Check entity type <" + entityType.name() + ">:"; + if (equals) { + Assert.assertEquals(msg, targetTenantId, checkTenantId); + } else { + Assert.assertNotEquals(msg, targetTenantId, checkTenantId); + } + } + } + + @Test + public void test_findEntityIdAsync_current_tenant() { + checkTenant(tenantId, true); + } + + @Test + public void test_findEntityIdAsync_other_tenant() { + checkTenant(new TenantId(UUID.randomUUID()), false); + } + +} diff --git a/tools/pom.xml b/tools/pom.xml index 850485ed42..91cf576ae1 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -56,8 +56,8 @@ cassandra-all
- com.datastax.cassandra - cassandra-driver-core + org.apache.cassandra + cassandra-thrift commons-io diff --git a/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md index a468cbd3af..6995632514 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md +++ b/tools/src/main/java/org/thingsboard/client/tools/migrator/README.md @@ -63,7 +63,7 @@ Tool execution time depends on DB size, CPU resources and Disk throughput * Note that this this part works only for single node Cassandra Cluster. If you have more nodes - it is better to use `sstableloader` tool. 1. [Optional] install Cassandra on the instance -2. [Optional] Using `cqlsh` create `thingsboard` keyspace and requred tables from this files `schema-ts.cql` and `schema-ts-latest.cql` using `source` command +2. [Optional] Using `cqlsh` create `thingsboard` keyspace and requred tables from this files `schema-keyspace.cql`, `schema-ts.cql` and `schema-ts-latest.cql` using `source` command 3. Stop Cassandra 4. Look at `/var/lib/cassandra/data/thingsboard` and check for names of data folders 5. Copy generated SSTable files into cassandra data dir using next command: diff --git a/ui-ngx/src/app/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index 6a413f4a95..4348bb482c 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -252,6 +252,7 @@ export interface WidgetSubscriptionOptions { displayTimewindow?: boolean; timeWindowConfig?: Timewindow; dashboardTimewindow?: Timewindow; + onTimewindowChangeFunction?: (timewindow: Timewindow) => Timewindow; legendConfig?: LegendConfig; comparisonEnabled?: boolean; timeForComparison?: moment_.unitOfTime.DurationConstructor; @@ -290,6 +291,7 @@ export interface IWidgetSubscription { hiddenData?: Array<{data: DataSet}>; timeWindowConfig?: Timewindow; timeWindow?: WidgetTimewindow; + onTimewindowChangeFunction?: (timewindow: Timewindow) => Timewindow; widgetTimewindowChanged$: Observable; comparisonEnabled?: boolean; comparisonTimeWindow?: WidgetTimewindow; diff --git a/ui-ngx/src/app/core/api/widget-subscription.ts b/ui-ngx/src/app/core/api/widget-subscription.ts index a01e731b8d..d40dc95db7 100644 --- a/ui-ngx/src/app/core/api/widget-subscription.ts +++ b/ui-ngx/src/app/core/api/widget-subscription.ts @@ -97,6 +97,7 @@ export class WidgetSubscription implements IWidgetSubscription { subscriptionTimewindow: SubscriptionTimewindow; useDashboardTimewindow: boolean; useTimewindow: boolean; + onTimewindowChangeFunction: (timewindow: Timewindow) => Timewindow; tsOffset = 0; hasDataPageLink: boolean; @@ -247,6 +248,7 @@ export class WidgetSubscription implements IWidgetSubscription { this.originalTimewindow = null; this.timeWindow = {}; this.useDashboardTimewindow = options.useDashboardTimewindow; + this.onTimewindowChangeFunction = options.onTimewindowChangeFunction || ((timewindow) => timewindow); this.stateData = options.stateData; this.useTimewindow = this.type === widgetType.timeseries || datasourcesHasAggregation(this.configuredDatasources); if (this.useDashboardTimewindow) { diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 33b84945aa..7e97cb7a13 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -21,6 +21,7 @@ export interface SysParamsState { allowedDashboardIds: string[]; edgesSupportEnabled: boolean; hasRepository: boolean; + mvelEnabled: boolean; } export interface AuthPayload extends SysParamsState { diff --git a/ui-ngx/src/app/core/auth/auth.reducer.ts b/ui-ngx/src/app/core/auth/auth.reducer.ts index cace62660e..7408f29145 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -24,7 +24,8 @@ const emptyUserAuthState: AuthPayload = { forceFullscreen: false, allowedDashboardIds: [], edgesSupportEnabled: false, - hasRepository: false + hasRepository: false, + mvelEnabled: false }; export const initialState: AuthState = { diff --git a/ui-ngx/src/app/core/auth/auth.selectors.ts b/ui-ngx/src/app/core/auth/auth.selectors.ts index 4a63dbcbed..f703d72f57 100644 --- a/ui-ngx/src/app/core/auth/auth.selectors.ts +++ b/ui-ngx/src/app/core/auth/auth.selectors.ts @@ -60,6 +60,11 @@ export const selectHasRepository = createSelector( (state: AuthState) => state.hasRepository ); +export const selectMvelEnabled = createSelector( + selectAuthState, + (state: AuthState) => state.mvelEnabled +); + export function getCurrentAuthState(store: Store): AuthState { let state: AuthState; store.pipe(select(selectAuth), take(1)).subscribe( diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 172e4d5ac9..2b54306e2c 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -478,11 +478,20 @@ export class AuthService { } } + private loadMvelEnabled(authUser: AuthUser): Observable { + if (authUser.authority === Authority.TENANT_ADMIN) { + return this.http.get('/api/ruleChain/mvelEnabled', defaultHttpOptions()); + } else { + return of(false); + } + } + private loadSystemParams(authPayload: AuthPayload): Observable { const sources = [this.loadIsUserTokenAccessEnabled(authPayload.authUser), this.fetchAllowedDashboardIds(authPayload), this.loadIsEdgesSupportEnabled(), this.loadHasRepository(authPayload.authUser), + this.loadMvelEnabled(authPayload.authUser), this.timeService.loadMaxDatapointsLimit()]; return forkJoin(sources) .pipe(map((data) => { @@ -490,7 +499,8 @@ export class AuthService { const allowedDashboardIds: string[] = data[1] as string[]; const edgesSupportEnabled: boolean = data[2] as boolean; const hasRepository: boolean = data[3] as boolean; - return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasRepository}; + const mvelEnabled: boolean = data[4] as boolean; + return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasRepository, mvelEnabled}; }, catchError((err) => { return of({}); }))); diff --git a/ui-ngx/src/app/core/auth/public-api.ts b/ui-ngx/src/app/core/auth/public-api.ts new file mode 100644 index 0000000000..07dc03ea83 --- /dev/null +++ b/ui-ngx/src/app/core/auth/public-api.ts @@ -0,0 +1,21 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +export * from './auth.actions'; +export * from './auth.models'; +export * from './auth.reducer'; +export * from './auth.selectors'; +export * from './auth.service'; diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 9b8c3b76b9..485f355b87 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -15,8 +15,8 @@ /// import { Injectable } from '@angular/core'; -import { defaultHttpOptions, defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; -import { Observable, of } from 'rxjs'; +import { defaultHttpOptionsFromConfig, RequestConfig } from './http-utils'; +import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { AdminSettings, @@ -24,12 +24,12 @@ import { MailServerSettings, SecuritySettings, TestSmsRequest, - UpdateMessage, AutoCommitSettings + UpdateMessage, + AutoCommitSettings, + RepositorySettingsInfo } from '@shared/models/settings.models'; import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; import { tap } from 'rxjs/operators'; -import { AuthUser } from '@shared/models/user.model'; -import { Authority } from '@shared/models/authority.enum'; @Injectable({ providedIn: 'root' @@ -97,6 +97,10 @@ export class AdminService { return this.http.post('/api/admin/repositorySettings/checkAccess', repositorySettings, defaultHttpOptionsFromConfig(config)); } + public getRepositorySettingsInfo(config?: RequestConfig): Observable { + return this.http.get('/api/admin/repositorySettings/info', defaultHttpOptionsFromConfig(config)); + } + public getAutoCommitSettings(config?: RequestConfig): Observable { return this.http.get(`/api/admin/autoCommitSettings`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/http/entity.service.ts b/ui-ngx/src/app/core/http/entity.service.ts index 96e6e057fc..b66399de6c 100644 --- a/ui-ngx/src/app/core/http/entity.service.ts +++ b/ui-ngx/src/app/core/http/entity.service.ts @@ -244,6 +244,11 @@ export class EntityService { (id) => this.assetProfileService.getAssetProfileInfo(id, config), entityIds); break; + case EntityType.WIDGETS_BUNDLE: + observable = this.getEntitiesByIdsObservable( + (id) => this.widgetService.getWidgetsBundle(id, config), + entityIds); + break; } return observable; } @@ -393,6 +398,10 @@ export class EntityService { pageLink.sortOrder.property = 'name'; entitiesObservable = this.assetProfileService.getAssetProfileInfos(pageLink, config); break; + case EntityType.WIDGETS_BUNDLE: + pageLink.sortOrder.property = 'title'; + entitiesObservable = this.widgetService.getWidgetBundles(pageLink, config); + break; } return entitiesObservable; } diff --git a/ui-ngx/src/app/core/http/rule-chain.service.ts b/ui-ngx/src/app/core/http/rule-chain.service.ts index 6b0f3c3f22..8fcb5ecc5c 100644 --- a/ui-ngx/src/app/core/http/rule-chain.service.ts +++ b/ui-ngx/src/app/core/http/rule-chain.service.ts @@ -31,7 +31,7 @@ import { ComponentDescriptorService } from './component-descriptor.service'; import { IRuleNodeConfigurationComponent, LinkLabel, - RuleNodeComponentDescriptor, RuleNodeConfiguration, + RuleNodeComponentDescriptor, RuleNodeConfiguration, ScriptLanguage, TestScriptInputParams, TestScriptResult } from '@app/shared/models/rule-node.models'; @@ -170,8 +170,12 @@ export class RuleChainService { return this.http.get(`/api/ruleNode/${ruleNodeId}/debugIn`, defaultHttpOptionsFromConfig(config)); } - public testScript(inputParams: TestScriptInputParams, config?: RequestConfig): Observable { - return this.http.post('/api/ruleChain/testScript', inputParams, defaultHttpOptionsFromConfig(config)); + public testScript(inputParams: TestScriptInputParams, scriptLang?: ScriptLanguage, config?: RequestConfig): Observable { + let url = '/api/ruleChain/testScript'; + if (scriptLang) { + url += `?scriptLang=${scriptLang}`; + } + return this.http.post(url, inputParams, defaultHttpOptionsFromConfig(config)); } private loadRuleNodeComponents(ruleChainType: RuleChainType, config?: RequestConfig): Observable> { diff --git a/ui-ngx/src/app/core/public-api.ts b/ui-ngx/src/app/core/public-api.ts index e2c9352229..6232736584 100644 --- a/ui-ngx/src/app/core/public-api.ts +++ b/ui-ngx/src/app/core/public-api.ts @@ -22,3 +22,4 @@ export * from './ws/telemetry-websocket.service'; export * from './core.state'; export * from './core.module'; export * from './utils'; +export * from './auth/public-api'; diff --git a/ui-ngx/src/app/core/services/script/node-script-test.service.ts b/ui-ngx/src/app/core/services/script/node-script-test.service.ts index 5dcdb192af..f7724aacc6 100644 --- a/ui-ngx/src/app/core/services/script/node-script-test.service.ts +++ b/ui-ngx/src/app/core/services/script/node-script-test.service.ts @@ -24,6 +24,7 @@ import { NodeScriptTestDialogData } from '@shared/components/dialog/node-script-test-dialog.component'; import { sortObjectKeys } from '@core/utils'; +import { ScriptLanguage } from '@shared/models/rule-node.models'; @Injectable({ providedIn: 'root' @@ -35,7 +36,8 @@ export class NodeScriptTestService { } testNodeScript(script: string, scriptType: string, functionTitle: string, - functionName: string, argNames: string[], ruleNodeId: string, helpId?: string): Observable { + functionName: string, argNames: string[], ruleNodeId: string, helpId?: string, + scriptLang?: ScriptLanguage): Observable { if (ruleNodeId) { return this.ruleChainService.getLatestRuleNodeDebugInput(ruleNodeId).pipe( switchMap((debugIn) => { @@ -52,18 +54,19 @@ export class NodeScriptTestService { msgType = debugIn.msgType; } return this.openTestScriptDialog(script, scriptType, functionTitle, - functionName, argNames, msg, metadata, msgType, helpId); + functionName, argNames, msg, metadata, msgType, helpId, scriptLang); }) ); } else { return this.openTestScriptDialog(script, scriptType, functionTitle, - functionName, argNames, null, null, null, helpId); + functionName, argNames, null, null, null, helpId, scriptLang); } } private openTestScriptDialog(script: string, scriptType: string, functionTitle: string, functionName: string, argNames: string[], - msg?: any, metadata?: {[key: string]: string}, msgType?: string, helpId?: string): Observable { + msg?: any, metadata?: {[key: string]: string}, msgType?: string, helpId?: string, + scriptLang?: ScriptLanguage): Observable { if (!msg) { msg = { temperature: 22.4, @@ -95,7 +98,8 @@ export class NodeScriptTestService { script, scriptType, argNames, - helpId + helpId, + scriptLang } }).afterClosed(); } diff --git a/ui-ngx/src/app/core/services/utils.service.ts b/ui-ngx/src/app/core/services/utils.service.ts index b901b87328..66eefa9e13 100644 --- a/ui-ngx/src/app/core/services/utils.service.ts +++ b/ui-ngx/src/app/core/services/utils.service.ts @@ -43,6 +43,15 @@ import { WidgetInfo } from '@home/models/widget-component.models'; import jsonSchemaDefaults from 'json-schema-defaults'; import materialIconsCodepoints from '!raw-loader!./material-icons-codepoints.raw'; import { Observable, of, ReplaySubject } from 'rxjs'; +import { publishReplay, refCount } from 'rxjs/operators'; +import { WidgetContext } from '@app/modules/home/models/widget-component.models'; +import { + AttributeData, + LatestTelemetry, + TelemetrySubscriber, + TelemetryType +} from '@shared/models/telemetry/telemetry.models'; +import { EntityId } from '@shared/models/id/entity-id'; const i18nRegExp = new RegExp(`{${i18nPrefix}:[^{}]+}`, 'g'); @@ -479,4 +488,27 @@ export class UtilsService { return defaultValue; } } + + private getEntityIdFromDatasource(dataSource: Datasource): EntityId { + return {id: dataSource.entityId, entityType: dataSource.entityType}; + } + + public subscribeToEntityTelemetry(ctx: WidgetContext, + entityId?: EntityId, + type: TelemetryType = LatestTelemetry.LATEST_TELEMETRY, + keys: string[] = null): Observable> { + if (!entityId && ctx.datasources.length > 0) { + entityId = this.getEntityIdFromDatasource(ctx.datasources[0]); + } + const subscription = TelemetrySubscriber.createEntityAttributesSubscription(ctx.telemetryWsService, entityId, type, ctx.ngZone, keys); + if (!ctx.telemetrySubscribers) { + ctx.telemetrySubscribers = []; + } + ctx.telemetrySubscribers.push(subscription); + subscription.subscribe(); + return subscription.attributeData$().pipe( + publishReplay(1), + refCount() + ); + } } diff --git a/ui-ngx/src/app/core/utils.ts b/ui-ngx/src/app/core/utils.ts index bedc963186..9a56fff033 100644 --- a/ui-ngx/src/app/core/utils.ts +++ b/ui-ngx/src/app/core/utils.ts @@ -22,7 +22,6 @@ import { EntityId } from '@shared/models/id/entity-id'; import { NULL_UUID } from '@shared/models/id/has-uuid'; import { EntityType, baseDetailsPageByEntityType } from '@shared/models/entity-type.models'; import { HttpErrorResponse } from '@angular/common/http'; -import { letterSpacing } from 'html2canvas/dist/types/css/property-descriptors/letter-spacing'; import { TranslateService } from '@ngx-translate/core'; import { serverErrorCodesTranslations } from '@shared/models/constants'; @@ -479,6 +478,18 @@ export function flatFormattedData(input: FormattedData[]): FormattedData { return result; } +export function flatDataWithoutOverride(input: FormattedData[]): FormattedData { + const result: FormattedData = {} as FormattedData; + input.forEach((data) => { + Object.keys(data).forEach((key) => { + if (!isDefinedAndNotNull(result[key]) || isEmptyStr(result[key])) { + result[key] = data[key]; + } + }); + }); + return result; +} + export function mergeFormattedData(first: FormattedData[], second: FormattedData[]): FormattedData[] { const merged = first.concat(second); return _(merged).groupBy(el => el.$datasource) diff --git a/ui-ngx/src/app/modules/common/modules-map.ts b/ui-ngx/src/app/modules/common/modules-map.ts index ce86939a01..47864e5cd0 100644 --- a/ui-ngx/src/app/modules/common/modules-map.ts +++ b/ui-ngx/src/app/modules/common/modules-map.ts @@ -142,6 +142,7 @@ import * as JsonObjectEditComponent from '@shared/components/json-object-edit.co import * as JsonObjectViewComponent from '@shared/components/json-object-view.component'; import * as JsonContentComponent from '@shared/components/json-content.component'; import * as JsFuncComponent from '@shared/components/js-func.component'; +import * as TbScriptLangComponent from '@shared/components/script-lang.component'; import * as FabToolbarComponent from '@shared/components/fab-toolbar.component'; import * as WidgetsBundleSelectComponent from '@shared/components/widgets-bundle-select.component'; import * as ConfirmDialogComponent from '@shared/components/dialog/confirm-dialog.component'; @@ -430,6 +431,7 @@ class ModulesMap implements IModulesMap { '@shared/components/json-object-view.component': JsonObjectViewComponent, '@shared/components/json-content.component': JsonContentComponent, '@shared/components/js-func.component': JsFuncComponent, + '@shared/components/script-lang.component': TbScriptLangComponent, '@shared/components/fab-toolbar.component': FabToolbarComponent, '@shared/components/widgets-bundle-select.component': WidgetsBundleSelectComponent, '@shared/components/dialog/confirm-dialog.component': ConfirmDialogComponent, diff --git a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts index 0a7b8211e7..38bedf4782 100644 --- a/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/audit-log/audit-log-details-dialog.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, ElementRef, Inject, OnInit, Renderer2, ViewChild } from '@angular/core'; +import { Component, ElementRef, Inject, OnDestroy, OnInit, Renderer2, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -33,7 +33,7 @@ export interface AuditLogDetailsDialogData { templateUrl: './audit-log-details-dialog.component.html', styleUrls: ['./audit-log-details-dialog.component.scss'] }) -export class AuditLogDetailsDialogComponent extends DialogComponent implements OnInit { +export class AuditLogDetailsDialogComponent extends DialogComponent implements OnInit, OnDestroy { @ViewChild('actionDataEditor', {static: true}) actionDataEditorElmRef: ElementRef; @@ -45,6 +45,7 @@ export class AuditLogDetailsDialogComponent extends DialogComponent, protected router: Router, @@ -66,6 +67,11 @@ export class AuditLogDetailsDialogComponent extends DialogComponent editor.destroy()); + super.ngOnDestroy(); + } + createEditor(editorElementRef: ElementRef, content: string): void { const editorElement = editorElementRef.nativeElement; let editorOptions: Partial = { @@ -86,6 +92,7 @@ export class AuditLogDetailsDialogComponent extends DialogComponent { const editor = ace.edit(editorElement, editorOptions); + this.aceEditors.push(editor); editor.session.setUseWrapMode(false); editor.setValue(content, -1); this.updateEditorSize(editorElement, content, editor); diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts index 14e566411f..4652228637 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/dashboard-page.component.ts @@ -49,6 +49,7 @@ import { DashboardState, DashboardStateLayouts, GridSettings, + LayoutDimension, WidgetLayout } from '@app/shared/models/dashboard.models'; import { WINDOW } from '@core/services/window.service'; @@ -145,6 +146,7 @@ import { MatButton } from '@angular/material/button'; import { VersionControlComponent } from '@home/components/vc/version-control.component'; import { TbPopoverService } from '@shared/components/popover.service'; import { tap } from 'rxjs/operators'; +import { LayoutFixedSize, LayoutWidthType } from '@home/components/dashboard-page/layout/layout.models'; import { TbPopoverComponent } from '@shared/components/popover.component'; // @dynamic @@ -672,7 +674,7 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC if (this.isEditingWidget && this.editingLayoutCtx.id === 'main') { return '100%'; } else { - return this.layouts.right.show && !this.isMobile ? '50%' : '100%'; + return this.layouts.right.show && !this.isMobile ? this.calculateWidth('main') : '100%'; } } @@ -688,7 +690,47 @@ export class DashboardPageComponent extends PageComponent implements IDashboardC if (this.isEditingWidget && this.editingLayoutCtx.id === 'right') { return '100%'; } else { - return this.isMobile ? '100%' : '50%'; + return this.isMobile ? '100%' : this.calculateWidth('right'); + } + } + + private calculateWidth(layout: DashboardLayoutId): string { + let layoutDimension: LayoutDimension; + const mainLayout = this.dashboard.configuration.states[this.dashboardCtx.state].layouts.main; + const rightLayout = this.dashboard.configuration.states[this.dashboardCtx.state].layouts.right; + if (rightLayout) { + if (mainLayout.gridSettings.layoutDimension) { + layoutDimension = mainLayout.gridSettings.layoutDimension; + } else { + layoutDimension = rightLayout.gridSettings.layoutDimension; + } + } + if (layoutDimension) { + if (layoutDimension.type === LayoutWidthType.PERCENTAGE) { + if (layout === 'right') { + return (100 - layoutDimension.leftWidthPercentage) + '%'; + } else { + return layoutDimension.leftWidthPercentage + '%'; + } + } else { + const dashboardWidth = this.dashboardContainer.nativeElement.getBoundingClientRect().width; + const minAvailableWidth = dashboardWidth - LayoutFixedSize.MIN; + if (layoutDimension.fixedLayout === layout) { + if (minAvailableWidth <= layoutDimension.fixedWidth) { + return minAvailableWidth + 'px'; + } else { + return layoutDimension.fixedWidth + 'px'; + } + } else { + if (minAvailableWidth <= layoutDimension.fixedWidth) { + return LayoutFixedSize.MIN + 'px'; + } else { + return (dashboardWidth - layoutDimension.fixedWidth) + 'px'; + } + } + } + } else { + return '50%'; } } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html index 0255ec8ade..2e57c6179a 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/dashboard-layout.component.html @@ -28,13 +28,13 @@
- + {{'dashboard.no-widgets' | translate}}
close - - -
-
-
- - {{ 'layout.main' | translate }} - - - {{ 'layout.right' | translate }} - +
+
+ + {{ 'layout.divider' | translate }} + +
+
+ + + {{ 'layout.percentage-width' | translate }} + + + {{ 'layout.fixed-width' | translate }} + + +
+
+
+ + +
+ + +
+
+
+ + +
+ + +
+
+
-
- - +
+ + + + +
+ + +
-
+
diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.scss b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.scss new file mode 100644 index 0000000000..4bb8dca0cf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.scss @@ -0,0 +1,181 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +@use '~@angular/material' as mat; +@import '../theme.scss'; + +$tb-warn: mat.get-color-from-palette(map-get($tb-theme, warn), text); + +:host { + .tb-layout-fixed-container { + width: 100%; + min-width: 368px; + padding: 8px 8px 8px 0; + min-height: 48px; + } + + .tb-hint-group { + padding: 0; + margin-top: -14px; + display: block; + } + + .tb-layout-preview { + width: 120%; + background-color: rgba(mat.get-color-from-palette($tb-primary, 50), 0.6); + padding: 35px; + + &-container { + width: 75%; + + button.tb-fixed-layout-button { + background-color: transparent; + color: #000000; + cursor: pointer; + + .mat-icon { + color: rgba(0, 0, 0, 0.38); + } + + &:hover { + background-color: rgba(211, 211, 211, 0.6); + } + } + + div { + transition-duration: 0.5s; + transition-property: max-width; + position: relative; + + .mat-icon-button { + align-self: end; + } + } + + .tb-layout-preview-element { + position: absolute; + z-index: 99; + + .mat-icon { + font-size: 20px; + width: 20px; + height: 20px; + line-height: 20px; + color: rgba(255, 255, 255, 0.76); + + &:hover { + transform: rotate(180deg); + transition: transform 0.5s; + } + } + } + + &-main { + min-width: 25%; + } + + /* remove arrows from input for Chrome, Safari, Edge, Opera */ + input::-webkit-outer-spin-button, + input::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; + } + + /* remove arrows from input for Firefox */ + input[type=number] { + -moz-appearance: textfield; + } + + .tb-layout-preview-input { + margin: 80px 0 0; + + input { + border: 1px solid #778899; + background-color: transparent; + color: #ffffff; + border-radius: 4px; + text-align: center; + outline: none; + width: 37px; + height: 28px; + font-size: 14px; + + &:invalid { + outline: 2px solid $tb-warn; + border: 1px solid transparent; + background-color: rgba($tb-warn, 0.2); + } + } + } + } + } +} + +:host ::ng-deep { + .mat-slider-wrapper { + .mat-slider-thumb-container { + .mat-slider-thumb-label { + width: 35px; + height: 35px; + } + } + } + + .mat-button-toggle-group { + width: 100%; + min-width: 354px; + border: 2px solid rgba(0, 0, 0, 0.06); + + .mat-button-toggle-checked { + background: rgba(0, 0, 0, 0.06); + } + + .mat-button-toggle { + border: none !important; + } + } + + /* Make mat-slider tooltip always visible */ + .mat-slider-thumb-label { + transform: rotate(45deg) !important; + border-radius: 50% 50% 0 !important; + } + + .mat-slider-thumb { + transform: scale(0) !important; + } + + .mat-slider-thumb-label-text { + opacity: 1 !important; + } +} + +::ng-deep { + /* Alarm tooltip with side-to-side movement */ + .tb-layout-error-tooltip-right { + background-color: $tb-warn !important; + margin: 5px 0 0 105px; + width: 160px; + text-align: center; + } + + .tb-layout-error-tooltip-main { + background-color: $tb-warn !important; + margin: 5px 105px 0 0; + width: 160px; + text-align: center; + } +} diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts index bc61fd46e4..4d593a7c57 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/layout/manage-dashboard-layouts-dialog.component.ts @@ -14,23 +14,38 @@ /// limitations under the License. /// -import { Component, Inject, OnInit, SkipSelf } from '@angular/core'; +import { Component, Inject, SkipSelf, ViewChild } from '@angular/core'; import { ErrorStateMatcher } from '@angular/material/core'; import { MAT_DIALOG_DATA, MatDialog, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; -import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm } from '@angular/forms'; +import { + AbstractControl, + FormBuilder, + FormControl, + FormGroup, + FormGroupDirective, + NgForm, + Validators +} from '@angular/forms'; import { Router } from '@angular/router'; import { DialogComponent } from '@app/shared/components/dialog.component'; import { UtilsService } from '@core/services/utils.service'; import { TranslateService } from '@ngx-translate/core'; -import { DashboardLayoutId, DashboardStateLayouts } from '@app/shared/models/dashboard.models'; +import { DashboardLayoutId, DashboardStateLayouts, LayoutDimension } from '@app/shared/models/dashboard.models'; import { deepClone, isDefined } from '@core/utils'; import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { DashboardSettingsDialogComponent, DashboardSettingsDialogData } from '@home/components/dashboard-page/dashboard-settings-dialog.component'; +import { + LayoutFixedSize, + LayoutPercentageSize, + LayoutWidthType +} from '@home/components/dashboard-page/layout/layout.models'; +import { Subscription } from 'rxjs'; +import { MatTooltip } from '@angular/material/tooltip'; export interface ManageDashboardLayoutsDialogData { layouts: DashboardStateLayouts; @@ -40,22 +55,32 @@ export interface ManageDashboardLayoutsDialogData { selector: 'tb-manage-dashboard-layouts-dialog', templateUrl: './manage-dashboard-layouts-dialog.component.html', providers: [{provide: ErrorStateMatcher, useExisting: ManageDashboardLayoutsDialogComponent}], - styleUrls: ['../../../components/dashboard/layout-button.scss'] + styleUrls: ['./manage-dashboard-layouts-dialog.component.scss', '../../../components/dashboard/layout-button.scss'] }) export class ManageDashboardLayoutsDialogComponent extends DialogComponent - implements OnInit, ErrorStateMatcher { + implements ErrorStateMatcher { + + @ViewChild('tooltip', {static: true}) tooltip: MatTooltip; layoutsFormGroup: FormGroup; - layouts: DashboardStateLayouts; + layoutWidthType = LayoutWidthType; + + layoutPercentageSize = LayoutPercentageSize; + + layoutFixedSize = LayoutFixedSize; + + private readonly layouts: DashboardStateLayouts; - submitted = false; + private subscriptions: Array = []; + + private submitted = false; constructor(protected store: Store, protected router: Router, - @Inject(MAT_DIALOG_DATA) public data: ManageDashboardLayoutsDialogData, + @Inject(MAT_DIALOG_DATA) private data: ManageDashboardLayoutsDialogData, @SkipSelf() private errorStateMatcher: ErrorStateMatcher, - public dialogRef: MatDialogRef, + protected dialogRef: MatDialogRef, private fb: FormBuilder, private utils: UtilsService, private dashboardUtils: DashboardUtilsService, @@ -64,20 +89,118 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent { + if (value === LayoutWidthType.FIXED) { + this.layoutsFormGroup.get('leftWidthPercentage').disable(); + this.layoutsFormGroup.get('rightWidthPercentage').disable(); + this.layoutsFormGroup.get('fixedWidth').enable(); + this.layoutsFormGroup.get('fixedLayout').enable(); + } else { + this.layoutsFormGroup.get('leftWidthPercentage').enable(); + this.layoutsFormGroup.get('rightWidthPercentage').enable(); + this.layoutsFormGroup.get('fixedWidth').disable(); + this.layoutsFormGroup.get('fixedLayout').disable(); + } + } + ) + ); + + if (this.layouts.right) { + if (this.layouts.right.gridSettings.layoutDimension) { + this.layoutsFormGroup.patchValue({ + fixedLayout: this.layouts.right.gridSettings.layoutDimension.fixedLayout, + type: LayoutWidthType.FIXED, + fixedWidth: this.layouts.right.gridSettings.layoutDimension.fixedWidth, + sliderFixed: this.layouts.right.gridSettings.layoutDimension.fixedWidth + }, {emitEvent: false}); + } else if (this.layouts.main.gridSettings.layoutDimension) { + if (this.layouts.main.gridSettings.layoutDimension.type === LayoutWidthType.FIXED) { + this.layoutsFormGroup.patchValue({ + fixedLayout: this.layouts.main.gridSettings.layoutDimension.fixedLayout, + type: LayoutWidthType.FIXED, + fixedWidth: this.layouts.main.gridSettings.layoutDimension.fixedWidth, + sliderFixed: this.layouts.main.gridSettings.layoutDimension.fixedWidth + }, {emitEvent: false}); + } else { + const leftWidthPercentage: number = Number(this.layouts.main.gridSettings.layoutDimension.leftWidthPercentage); + this.layoutsFormGroup.patchValue({ + leftWidthPercentage, + sliderPercentage: leftWidthPercentage, + rightWidthPercentage: 100 - Number(leftWidthPercentage) + }, {emitEvent: false}); + } } } + + if (!this.layouts.main) { + this.layouts.main = this.dashboardUtils.createDefaultLayoutData(); + } + if (!this.layouts.right) { + this.layouts.right = this.dashboardUtils.createDefaultLayoutData(); + } + + this.subscriptions.push( + this.layoutsFormGroup.get('sliderPercentage').valueChanges + .subscribe( + (value) => this.layoutsFormGroup.get('leftWidthPercentage').patchValue(value) + )); + this.subscriptions.push( + this.layoutsFormGroup.get('sliderFixed').valueChanges + .subscribe( + (value) => { + this.layoutsFormGroup.get('fixedWidth').patchValue(value); + } + )); + this.subscriptions.push( + this.layoutsFormGroup.get('leftWidthPercentage').valueChanges + .subscribe( + (value) => { + this.showTooltip(this.layoutsFormGroup.get('leftWidthPercentage'), LayoutWidthType.PERCENTAGE, 'main'); + this.layoutControlChange('rightWidthPercentage', value); + } + )); + this.subscriptions.push( + this.layoutsFormGroup.get('rightWidthPercentage').valueChanges + .subscribe( + (value) => { + this.showTooltip(this.layoutsFormGroup.get('rightWidthPercentage'), LayoutWidthType.PERCENTAGE, 'right'); + this.layoutControlChange('leftWidthPercentage', value); + } + )); + this.subscriptions.push( + this.layoutsFormGroup.get('fixedWidth').valueChanges + .subscribe( + (value) => { + this.showTooltip(this.layoutsFormGroup.get('fixedWidth'), LayoutWidthType.FIXED, + this.layoutsFormGroup.get('fixedLayout').value); + this.layoutsFormGroup.get('sliderFixed').setValue(value, {emitEvent: false}); + } + )); } - ngOnInit(): void { + ngOnDestroy(): void { + for (const subscription of this.subscriptions) { + subscription.unsubscribe(); + } } isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean { @@ -110,12 +233,132 @@ export class ManageDashboardLayoutsDialogComponent extends DialogComponent implements OnInit { +export class EventContentDialogComponent extends DialogComponent implements OnInit, OnDestroy { @ViewChild('eventContentEditor', {static: true}) eventContentEditorElmRef: ElementRef; @@ -48,6 +48,7 @@ export class EventContentDialogComponent extends DialogComponent, protected router: Router, @@ -65,6 +66,13 @@ export class EventContentDialogComponent extends DialogComponent { - const editor = ace.edit(editorElement, editorOptions); - editor.session.setUseWrapMode(false); - editor.setValue(processedContent, -1); - this.updateEditorSize(editorElement, processedContent, editor); + this.aceEditor = ace.edit(editorElement, editorOptions); + this.aceEditor.session.setUseWrapMode(false); + this.aceEditor.setValue(processedContent, -1); + this.updateEditorSize(editorElement, processedContent, this.aceEditor); } ); } diff --git a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts index 68b803e006..ee4a7f7378 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.ts @@ -108,7 +108,7 @@ export class EventFilterPanelComponent { changeIsError(value: boolean | string) { if (this.conditionError && value === '') { - this.eventFilterFormGroup.get('error').reset('', {emitEvent: false}); + this.eventFilterFormGroup.get('errorStr').reset('', {emitEvent: false}); } } } diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts index 19c49638ff..0e5cb4e91e 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-config.ts +++ b/ui-ngx/src/app/modules/home/components/event/event-table-config.ts @@ -440,6 +440,8 @@ export class EventTableConfig extends EntityTableConfig { }; config.positionStrategy = this.overlay.position().flexibleConnectedTo(target as HTMLElement) .withPositions([connectedPosition]); + config.maxHeight = '70vh'; + config.height = 'min-content'; const overlayRef = this.overlay.create(config); overlayRef.backdropClick().subscribe(() => { diff --git a/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts index 2db07e913f..0d23d9a018 100644 --- a/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts +++ b/ui-ngx/src/app/modules/home/components/import-export/import-dialog-csv.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { AfterViewInit, Component, ElementRef, Inject, Renderer2, ViewChild } from '@angular/core'; +import { AfterViewInit, Component, ElementRef, Inject, OnDestroy, Renderer2, ViewChild } from '@angular/core'; import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -54,7 +54,7 @@ export interface ImportDialogCsvData { styleUrls: ['./import-dialog-csv.component.scss'] }) export class ImportDialogCsvComponent extends DialogComponent - implements AfterViewInit { + implements AfterViewInit, OnDestroy { @ViewChild('importStepper', {static: true}) importStepper: MatVerticalStepper; @@ -91,6 +91,8 @@ export class ImportDialogCsvComponent extends DialogComponent column.value); } + ngOnDestroy(): void { + if (this.aceEditor) { + this.aceEditor.destroy(); + } + super.ngOnDestroy(); + } + cancel(): void { this.dialogRef.close(false); } @@ -271,10 +280,10 @@ export class ImportDialogCsvComponent extends DialogComponent error.replace('\n', '')).join('\n'); getAce().subscribe( (ace) => { - const editor = ace.edit(editorElement, editorOptions); - editor.session.setUseWrapMode(false); - editor.setValue(content, -1); - this.updateEditorSize(editorElement, content, editor); + this.aceEditor = ace.edit(editorElement, editorOptions); + this.aceEditor.session.setUseWrapMode(false); + this.aceEditor.setValue(content, -1); + this.updateEditorSize(editorElement, content, this.aceEditor); } ); } diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html index 6529b4b97b..8336eb728d 100644 --- a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.html @@ -29,7 +29,7 @@
-
+
admin.auto-commit-entities
version-control.auto-commit-settings-read-only-hint
-
diff --git a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts index fd100f6247..980d89db4f 100644 --- a/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/auto-commit-settings.component.ts @@ -23,8 +23,8 @@ import { AdminService } from '@core/http/admin.service'; import { AutoCommitSettings, AutoVersionCreateConfig } from '@shared/models/settings.models'; import { TranslateService } from '@ngx-translate/core'; import { DialogService } from '@core/services/dialog.service'; -import { catchError, mergeMap } from 'rxjs/operators'; -import { of } from 'rxjs'; +import { catchError, map, mergeMap } from 'rxjs/operators'; +import { Observable, of } from 'rxjs'; import { EntityTypeVersionCreateConfig, exportableEntityTypes } from '@shared/models/vc.models'; import { EntityType, entityTypeTranslations } from '@shared/models/entity-type.models'; import { DomSanitizer, SafeHtml } from '@angular/platform-browser'; @@ -41,6 +41,8 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit entityTypes = EntityType; + isReadOnly: Observable; + constructor(protected store: Store, private adminService: AdminService, private dialogService: DialogService, @@ -71,6 +73,7 @@ export class AutoCommitSettingsComponent extends PageComponent implements OnInit this.autoCommitSettingsForm.setControl('entityTypes', this.prepareEntityTypesFormArray(settings), {emitEvent: false}); }); + this.isReadOnly = this.adminService.getRepositorySettingsInfo().pipe(map(settings => settings.readOnly)); } entityTypesFormGroupArray(): FormGroup[] { diff --git a/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts b/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts index 8b53bdaeac..3d6cf3c735 100644 --- a/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/complex-version-create.component.ts @@ -121,12 +121,18 @@ export class ComplexVersionCreateComponent extends PageComponent implements OnIn } this.versionCreateResultSubscription = this.versionCreateResult$.subscribe((result) => { - if (result.done && !result.added && !result.modified && !result.removed) { - this.resultMessage = this.sanitizer.bypassSecurityTrustHtml(this.translate.instant('version-control.nothing-to-commit')); + let message: string; + if (!result.error) { + if (result.done && !result.added && !result.modified && !result.removed) { + message = this.translate.instant('version-control.nothing-to-commit'); + } else { + message = this.translate.instant('version-control.version-create-result', + {added: result.added, modified: result.modified, removed: result.removed}); + } } else { - this.resultMessage = this.sanitizer.bypassSecurityTrustHtml(result.error ? result.error : this.translate.instant('version-control.version-create-result', - {added: result.added, modified: result.modified, removed: result.removed})); + message = result.error; } + this.resultMessage = this.sanitizer.bypassSecurityTrustHtml(message); this.versionCreateResult = result; this.versionCreateBranch = request.branch; this.cd.detectChanges(); diff --git a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html index 772cb5ecd9..46b0be09c0 100644 --- a/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/entity-versions-table.component.html @@ -34,14 +34,14 @@
- +
+ + + +
+ drag_handle +
+
+
{{ 'widget-config.action-source' | translate }} - + {{ action.actionSourceName }} - {{ 'widget-config.action-name' | translate }} - + {{ 'widget-config.action-name' | translate }} + {{ action.name }} - {{ 'widget-config.action-icon' | translate }} - + {{ 'widget-config.action-icon' | translate }} + {{ action.icon }} - {{ 'widget-config.action-type' | translate }} - + {{ 'widget-config.action-type' | translate }} + {{ action.typeName }} @@ -107,9 +127,10 @@ - - + +
) { + this.dragDisabled = true; + const droppedAction: WidgetActionDescriptorInfo = event.item.data; + this.dataSource.pageData$.pipe( + first() + ).subscribe((actions) => { + const action = actions.data; + let startActionSourceIndex = action.findIndex(element => element.actionSourceId === droppedAction.actionSourceId); + const targetActions = this.getOrCreateTargetActions(droppedAction.actionSourceId); + if (startActionSourceIndex === 0) { + startActionSourceIndex -= targetActions.findIndex(element => element.id === action[0].id); + } + moveItemInArray(targetActions, event.previousIndex - startActionSourceIndex, event.currentIndex - startActionSourceIndex); + this.onActionsUpdated(); + }); + } + addAction($event: Event) { this.openWidgetActionDialog($event); } diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html index a36cab05ff..1ca6b131b0 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.html @@ -63,20 +63,20 @@
- + datakey.aggregation {{ aggregationTypesTranslations.get(aggregationTypes[aggregation]) | translate }} - - {{ dataKeyFormGroup.get('aggregationType').value ? (dataKeyAggregationTypeHintTranslations.get(aggregationTypes[dataKeyFormGroup.get('aggregationType').value]) | translate) : '' }} -
- {{ 'datakey.aggregation-type-hint-common' | translate }} -
-
+
+ {{ dataKeyFormGroup.get('aggregationType').value ? (dataKeyAggregationTypeHintTranslations.get(aggregationTypes[dataKeyFormGroup.get('aggregationType').value]) | translate) : '' }} +
+ {{ 'datakey.aggregation-type-hint-common' | translate }} +
+
datakey.delta-calculation diff --git a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss index 77f1e3a2b3..c4ea8019d6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/widget/data-key-config.component.scss @@ -58,6 +58,12 @@ } } } + + .tb-hint.after-fields { + margin-top: -1.25em; + max-width: fit-content; + line-height: 15px; + } } } @@ -95,7 +101,7 @@ &.comparison { .mat-expansion-panel-header { - height: 140px; + height: 100%; } } diff --git a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html index 2ae29c5f5b..4936cb5ba6 100644 --- a/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/dialog/embed-dashboard-dialog.component.html @@ -28,7 +28,7 @@
+ [currentState]="state" [dashboard]="dashboard" [parentDashboard]="parentDashboard" style="width: 100%; height: 100%;">
+
+ widgets.maps.clustering-markers + + + + + {{ 'widgets.maps.use-icon-create-function' | translate }} + + + + widget-config.advanced-settings + + + + + + + +
diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/marker-clustering-settings.component.ts b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/marker-clustering-settings.component.ts index f1ca781bed..087b8aaa9d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/marker-clustering-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/settings/map/marker-clustering-settings.component.ts @@ -56,6 +56,8 @@ export class MarkerClusteringSettingsComponent extends PageComponent implements private modelValue: MarkerClusteringSettings; + functionScopeVariables = this.widgetService.getWidgetScopeVariables(); + private propagateChange = null; public markerClusteringSettingsFormGroup: FormGroup; @@ -77,7 +79,9 @@ export class MarkerClusteringSettingsComponent extends PageComponent implements spiderfyOnMaxZoom: [null, []], showCoverageOnHover: [null, []], chunkedLoading: [null, []], - removeOutsideVisibleBounds: [null, []] + removeOutsideVisibleBounds: [null, []], + useIconCreateFunction: [null, []], + clusterMarkerFunction: [null, []] }); this.markerClusteringSettingsFormGroup.valueChanges.subscribe(() => { this.updateModel(); diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts index 0747ddffc1..b18806bce3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts +++ b/ui-ngx/src/app/modules/home/components/widget/lib/table-widget.models.ts @@ -27,7 +27,7 @@ import { TranslateService } from '@ngx-translate/core'; const tinycolor = tinycolor_; -type ColumnVisibilityOptions = 'visible' | 'hidden'; +type ColumnVisibilityOptions = 'visible' | 'hidden' | 'hidden-mobile'; type ColumnSelectionOptions = 'enabled' | 'disabled'; @@ -43,6 +43,7 @@ export interface TableWidgetSettings { } export interface TableWidgetDataKeySettings { + customTitle?: string; columnWidth?: string; useCellStyleFunction: boolean; cellStyleFunction?: string; @@ -302,8 +303,9 @@ export function widthStyle(width: string): any { return widthStyleObj; } -export function getColumnDefaultVisibility(keySettings: TableWidgetDataKeySettings): boolean { - return !(isDefined(keySettings.defaultColumnVisibility) && keySettings.defaultColumnVisibility === 'hidden'); +export function getColumnDefaultVisibility(keySettings: TableWidgetDataKeySettings, ctx?: WidgetContext): boolean { + return !(isDefined(keySettings.defaultColumnVisibility) && (keySettings.defaultColumnVisibility === 'hidden' || + (ctx && ctx.isMobile && keySettings.defaultColumnVisibility === 'hidden-mobile'))); } export function getColumnSelectionAvailability(keySettings: TableWidgetDataKeySettings): boolean { @@ -474,3 +476,10 @@ export function constructTableCssString(widgetConfig: WidgetConfig): string { '}'; return cssString; } + +export function getHeaderTitle(dataKey: DataKey, keySettings: TableWidgetDataKeySettings, utils: UtilsService) { + if (isDefined(keySettings.customTitle) && isNotEmptyStr(keySettings.customTitle)) { + return utils.customTranslation(keySettings.customTitle, keySettings.customTitle); + } + return dataKey.label; +} diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html index 3adb2d4fb6..70c8586ccd 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.html @@ -86,16 +86,24 @@ - - -
widget-config.datasources
-
{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}
- + +
+
widget-config.datasources
+
{{ 'widget-config.maximum-datasources' | translate:{count: modelValue?.typeParameters.maxDatasources} }}
+
+ + report_gmailerrorred +
+ + {{ 'widget-config.timeseries-key-error' | translate }} +
datasource?.dataKeys?.length).length > 0; if (!valid) { - this.dataError = 'At least one timeseries data key should be specified'; + this.timeseriesKeyError = true; return { timeseriesDataKeys: { valid: false @@ -1022,4 +1022,7 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont return null; } + public extensionPanelIsOpen(value) { + this.openExtensionPanel = value; + } } diff --git a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts index 790daf1a42..baa0664a31 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget.component.ts @@ -453,7 +453,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI for (const id of Object.keys(this.widgetContext.subscriptions)) { const subscription = this.widgetContext.subscriptions[id]; if (!subscription.useDashboardTimewindow) { - subscription.updateTimewindowConfig(timewindow); + subscription.updateTimewindowConfig(subscription.onTimewindowChangeFunction(timewindow)); } } } @@ -1436,7 +1436,7 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI title = insertVariable(title, prop + ':entityLabel', params[prop].entityLabel); } } - this.dialog.open(this.embedDashboardDialogComponent, { + dashboard.dialogRef = this.dialog.open(this.embedDashboardDialogComponent, { disableClose: true, panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], viewContainerRef: this.widgetContentContainer, @@ -1446,7 +1446,9 @@ export class WidgetComponent extends PageComponent implements OnInit, AfterViewI title, hideToolbar: hideDashboardToolbar, width: dialogWidth, - height: dialogHeight + height: dialogHeight, + parentDashboard: this.widgetContext.parentDashboard ? + this.widgetContext.parentDashboard : this.widgetContext.dashboard } }); this.cd.markForCheck(); diff --git a/ui-ngx/src/app/modules/home/models/services.map.ts b/ui-ngx/src/app/modules/home/models/services.map.ts index 93ec69da61..7514e972b5 100644 --- a/ui-ngx/src/app/modules/home/models/services.map.ts +++ b/ui-ngx/src/app/modules/home/models/services.map.ts @@ -39,6 +39,7 @@ import { OtaPackageService } from '@core/http/ota-package.service'; import { AuthService } from '@core/auth/auth.service'; import { ResourceService } from '@core/http/resource.service'; import { TwoFactorAuthenticationService } from '@core/http/two-factor-authentication.service'; +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; export const ServicesMap = new Map>( [ @@ -65,6 +66,7 @@ export const ServicesMap = new Map>( ['otaPackageService', OtaPackageService], ['authService', AuthService], ['resourceService', ResourceService], - ['twoFactorAuthenticationService', TwoFactorAuthenticationService] + ['twoFactorAuthenticationService', TwoFactorAuthenticationService], + ['telemetryWsService', TelemetryWebsocketService] ] ); diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 0bcfa8fb01..813b3b6927 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -75,9 +75,10 @@ import { DialogService } from '@core/services/dialog.service'; import { CustomDialogService } from '@home/components/widget/dialog/custom-dialog.service'; import { AuthService } from '@core/auth/auth.service'; import { ResourceService } from '@core/http/resource.service'; +import { TelemetryWebsocketService } from '@core/ws/telemetry-websocket.service'; import { DatePipe } from '@angular/common'; import { TranslateService } from '@ngx-translate/core'; -import { PageLink } from '@shared/models/page/page-link'; +import { PageLink, TimePageLink } from '@shared/models/page/page-link'; import { SortOrder } from '@shared/models/page/sort-order'; import { DomSanitizer } from '@angular/platform-browser'; import { Router } from '@angular/router'; @@ -86,6 +87,8 @@ import * as RxJS from 'rxjs'; import * as RxJSOperators from 'rxjs/operators'; import { TbPopoverComponent } from '@shared/components/popover.component'; import { EntityId } from '@shared/models/id/entity-id'; +import { AlarmQuery, AlarmSearchStatus, AlarmStatus} from '@app/shared/models/alarm.models'; +import { TelemetrySubscriber } from '@app/shared/public-api'; export interface IWidgetAction { name: string; @@ -176,6 +179,8 @@ export class WidgetContext { dialogs: DialogService; customDialog: CustomDialogService; resourceService: ResourceService; + telemetryWsService: TelemetryWebsocketService; + telemetrySubscribers?: TelemetrySubscriber[]; date: DatePipe; translate: TranslateService; http: HttpClient; @@ -394,9 +399,24 @@ export class WidgetContext { this.widgetActions = undefined; } + closeDialog(resultData: any = null) { + const dialogRef = this.$scope.dialogRef || this.stateController.dashboardCtrl.dashboardCtx.getDashboard().dialogRef; + if (dialogRef) { + dialogRef.close(resultData); + } + } + pageLink(pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null): PageLink { return new PageLink(pageSize, page, textSearch, sortOrder); } + + timePageLink(startTime: number, endTime: number, pageSize: number, page: number = 0, textSearch: string = null, sortOrder: SortOrder = null) { + return new TimePageLink(pageSize, page, textSearch, sortOrder, startTime, endTime); + } + + alarmQuery(entityId: EntityId, pageLink: TimePageLink, searchStatus: AlarmSearchStatus, status: AlarmStatus, fetchOriginator: boolean) { + return new AlarmQuery(entityId, pageLink, searchStatus, status, fetchOriginator); + } } export interface IDynamicWidgetComponent { diff --git a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts index f4c7c2f1c9..9e79d2d725 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/two-factor-auth-settings.component.ts @@ -192,7 +192,7 @@ export class TwoFactorAuthSettingsComponent extends PageComponent implements OnI }; switch (provider) { case TwoFactorAuthProviderType.TOTP: - formControlConfig.issuerName = [{value: 'ThingsBoard', disabled: true}, [Validators.required, Validators.pattern(/^\S+$/)]]; + formControlConfig.issuerName = [{value: 'ThingsBoard', disabled: true}, [Validators.required, Validators.pattern(/^(?!^\s+$).*$/)]]; break; case TwoFactorAuthProviderType.SMS: formControlConfig.smsVerificationMessageTemplate = [{value: 'Verification code: ${code}', disabled: true}, [ diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts index 0ec6dbddf4..37ecc8032c 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.ts @@ -210,6 +210,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe ngOnDestroy(): void { this.window.removeEventListener('message', this.onWindowMessageListener); + this.aceEditors.forEach(editor => editor.destroy()); this.aceResize$.disconnect(); this.rxSubscriptions.forEach((subscription) => { subscription.unsubscribe(); @@ -599,6 +600,7 @@ export class WidgetEditorComponent extends PageComponent implements OnInit, OnDe config.title = this.widget.widgetName; this.widget.defaultConfig = JSON.stringify(config); this.iframe.attr('data-widget', JSON.stringify(this.widget)); + // @ts-ignore this.iframe[0].contentWindow.location.reload(true); } diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.html b/ui-ngx/src/app/modules/login/pages/login/login.component.html index 8b34161e90..ea814b4def 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.html +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.html @@ -30,7 +30,7 @@
- diff --git a/ui-ngx/src/app/modules/login/pages/login/login.component.ts b/ui-ngx/src/app/modules/login/pages/login/login.component.ts index 40efeed59a..e889969e4d 100644 --- a/ui-ngx/src/app/modules/login/pages/login/login.component.ts +++ b/ui-ngx/src/app/modules/login/pages/login/login.component.ts @@ -69,4 +69,11 @@ export class LoginComponent extends PageComponent implements OnInit { } } + getOAuth2Uri(oauth2Client: OAuth2ClientInfo): string { + let result = ""; + if (this.authService.redirectUrl) { + result += "?prevUri=" + this.authService.redirectUrl; + } + return oauth2Client.url + result; + } } diff --git a/ui-ngx/src/app/shared/components/css.component.ts b/ui-ngx/src/app/shared/components/css.component.ts index 0156831712..2a8442e6d5 100644 --- a/ui-ngx/src/app/shared/components/css.component.ts +++ b/ui-ngx/src/app/shared/components/css.component.ts @@ -145,6 +145,9 @@ export class CssComponent implements OnInit, OnDestroy, ControlValueAccessor, Va if (this.editorResize$) { this.editorResize$.disconnect(); } + if (this.cssEditor) { + this.cssEditor.destroy(); + } } private onAceEditorResize() { diff --git a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html index 596ddda91b..c0f0ff4b42 100644 --- a/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/node-script-test-dialog.component.html @@ -18,7 +18,7 @@ -

{{ 'rulenode.test-script-function' | translate }}

+

{{ 'rulenode.test-script-function' | translate }} ({{ (scriptLang === scriptLanguage.JS ? 'rulenode.script-lang-java-script' : 'rulenode.script-lang-mvel') | translate }})

+ +
+
+
+ +
+
-
- - - -
diff --git a/ui-ngx/src/app/shared/components/markdown-editor.component.scss b/ui-ngx/src/app/shared/components/markdown-editor.component.scss index a4149c54d7..1a1cf57a63 100644 --- a/ui-ngx/src/app/shared/components/markdown-editor.component.scss +++ b/ui-ngx/src/app/shared/components/markdown-editor.component.scss @@ -58,20 +58,15 @@ overflow: auto; height: 100%; } - .buttons-panel { - position: absolute; - top: 5px; - right: 24px; - z-index: 1; - button.edit-toggle { - min-width: 32px; - min-height: 15px; - padding: 4px; - margin: 0; - font-size: .8rem; - line-height: 15px; - color: #7b7b7b; - background: rgba(220, 220, 220, .35); - } + button.panel-button { + background: rgba(220, 220, 220, .35); + align-items: center; + vertical-align: middle; + min-width: 32px; + min-height: 15px; + padding: 4px; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; } } diff --git a/ui-ngx/src/app/shared/components/markdown-editor.component.ts b/ui-ngx/src/app/shared/components/markdown-editor.component.ts index 7c1c9c7c02..fee904b60d 100644 --- a/ui-ngx/src/app/shared/components/markdown-editor.component.ts +++ b/ui-ngx/src/app/shared/components/markdown-editor.component.ts @@ -14,7 +14,7 @@ /// limitations under the License. /// -import { Component, ElementRef, forwardRef, Input, OnInit, ViewChild } from '@angular/core'; +import { Component, ElementRef, forwardRef, Input, OnDestroy, OnInit, ViewChild } from '@angular/core'; import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms'; import { Ace } from 'ace-builds'; import { coerceBooleanProperty } from '@angular/cdk/coercion'; @@ -32,7 +32,7 @@ import { getAce } from '@shared/models/ace/ace.models'; } ] }) -export class MarkdownEditorComponent implements OnInit, ControlValueAccessor { +export class MarkdownEditorComponent implements OnInit, ControlValueAccessor, OnDestroy { @Input() label: string; @@ -40,6 +40,8 @@ export class MarkdownEditorComponent implements OnInit, ControlValueAccessor { @Input() readonly: boolean; + @Input() helpId: string; + @ViewChild('markdownEditor', {static: true}) markdownEditorElmRef: ElementRef; @@ -104,6 +106,12 @@ export class MarkdownEditorComponent implements OnInit, ControlValueAccessor { } } + ngOnDestroy(): void { + if (this.markdownEditor) { + this.markdownEditor.destroy(); + } + } + registerOnChange(fn: any): void { this.propagateChange = fn; } diff --git a/ui-ngx/src/app/shared/components/phone-input.component.ts b/ui-ngx/src/app/shared/components/phone-input.component.ts index a620fa6263..77f9e21f65 100644 --- a/ui-ngx/src/app/shared/components/phone-input.component.ts +++ b/ui-ngx/src/app/shared/components/phone-input.component.ts @@ -94,8 +94,12 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida set isLoad(value) { if (this.isLoading) { this.isLoading = value; - if (this.phoneFormGroup) { - this.defineCountryFromNumber(this.phoneFormGroup.get('phoneNumber').value); + if (this.defaultCountry) { + this.getFlagAndPhoneNumberData(this.defaultCountry); + } + if (this.phoneFormGroup && this.phoneFormGroup.get('phoneNumber').value) { + const parsedPhoneNumber = this.parsePhoneNumberFromString(this.phoneFormGroup.get('phoneNumber').value); + this.defineCountryFromNumber(parsedPhoneNumber); } } } @@ -107,7 +111,7 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida private countryCallingCode = '+'; private modelValue: string; private changeSubscriptions: Subscription[] = []; - private validators: ValidatorFn[] = [(c: FormControl) => Validators.pattern(this.getPhoneNumberPattern())(c), this.validatePhoneNumber()]; + private validators: ValidatorFn[] = [this.validatePhoneNumber()]; private propagateChange = (v: any) => { }; @@ -125,15 +129,17 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida this.validators.push(Validators.required); } this.phoneFormGroup = this.fb.group({ - country: [this.defaultCountry, []], + country: [null, []], phoneNumber: [null, this.validators] }); - this.flagIcon = this.getFlagIcon(this.phoneFormGroup.get('country').value); - this.changeSubscriptions.push(this.phoneFormGroup.get('phoneNumber').valueChanges.subscribe(value => { - this.updateModel(); - this.defineCountryFromNumber(value); + let parsedPhoneNumber = null; + if (value && this.parsePhoneNumberFromString) { + parsedPhoneNumber = this.parsePhoneNumberFromString(value); + this.defineCountryFromNumber(parsedPhoneNumber); + } + this.updateModel(parsedPhoneNumber); })); this.changeSubscriptions.push(this.phoneFormGroup.get('country').valueChanges.subscribe(value => { @@ -183,6 +189,10 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida return String.fromCodePoint(...countryCode.split('').map(country => this.baseCode + country.charCodeAt(0))); } + private updateModelValueInFormat(parsedPhoneNumber: any) { + this.modelValue = parsedPhoneNumber.format('E.164'); + } + validatePhoneNumber(): ValidatorFn { return (c: FormControl) => { const phoneNumber = c.value; @@ -200,20 +210,13 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida }; } - private defineCountryFromNumber(phoneNumber) { - if (phoneNumber && this.parsePhoneNumberFromString) { - const parsedPhoneNumber = this.parsePhoneNumberFromString(phoneNumber); - const country = this.phoneFormGroup.get('country').value; - if (parsedPhoneNumber?.country && parsedPhoneNumber?.country !== country) { - this.phoneFormGroup.get('country').patchValue(parsedPhoneNumber.country, {emitEvent: true}); - } + private defineCountryFromNumber(parsedPhoneNumber) { + const country = this.phoneFormGroup.get('country').value; + if (parsedPhoneNumber?.country && parsedPhoneNumber?.country !== country) { + this.phoneFormGroup.get('country').patchValue(parsedPhoneNumber.country, {emitEvent: true}); } } - private getPhoneNumberPattern(): RegExp { - return new RegExp(`^${this.countryCallingCode.replace('+', '\\+')}$|^\\+[1-9]\\d{1,14}$`); - } - validate(): ValidationErrors | null { const phoneNumber = this.phoneFormGroup.get('phoneNumber'); return phoneNumber.valid || this.countryCallingCode === phoneNumber.value ? null : { @@ -246,6 +249,8 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida if (phoneNumber) { const parsedPhoneNumber = this.parsePhoneNumberFromString(phoneNumber); if (parsedPhoneNumber?.isValid() && parsedPhoneNumber?.isPossible()) { + country = parsedPhoneNumber?.country || this.defaultCountry; + this.updateModelValueInFormat(parsedPhoneNumber); this.isLegacy = false; } else { const validators = [Validators.maxLength(255)]; @@ -259,18 +264,19 @@ export class PhoneInputComponent implements OnInit, ControlValueAccessor, Valida this.isLegacy = false; } this.phoneFormGroup.updateValueAndValidity({emitEvent: false}); - country = phoneNumber ? this.parsePhoneNumberFromString(phoneNumber)?.country || this.defaultCountry : this.defaultCountry; this.getFlagAndPhoneNumberData(country); } this.phoneFormGroup.reset({phoneNumber, country}, {emitEvent: false}); } - private updateModel() { + private updateModel(parsedPhoneNumber?) { const phoneNumber = this.phoneFormGroup.get('phoneNumber'); if (phoneNumber.value === '+' || phoneNumber.value === this.countryCallingCode) { this.propagateChange(null); } else if (phoneNumber.valid) { - this.modelValue = phoneNumber.value; + if (parsedPhoneNumber) { + this.updateModelValueInFormat(parsedPhoneNumber); + } this.propagateChange(this.modelValue); } else { this.propagateChange(null); diff --git a/ui-ngx/src/app/shared/components/protobuf-content.component.ts b/ui-ngx/src/app/shared/components/protobuf-content.component.ts index 8d83706ca1..eacd7971ae 100644 --- a/ui-ngx/src/app/shared/components/protobuf-content.component.ts +++ b/ui-ngx/src/app/shared/components/protobuf-content.component.ts @@ -129,6 +129,9 @@ export class ProtobufContentComponent implements OnInit, ControlValueAccessor, O if (this.editorResize$) { this.editorResize$.disconnect(); } + if (this.protobufEditor) { + this.protobufEditor.destroy(); + } } registerOnChange(fn: any): void { diff --git a/ui-ngx/src/app/shared/components/public-api.ts b/ui-ngx/src/app/shared/components/public-api.ts index 28b8281a52..e45835a573 100644 --- a/ui-ngx/src/app/shared/components/public-api.ts +++ b/ui-ngx/src/app/shared/components/public-api.ts @@ -19,3 +19,4 @@ export * from './dialog/material-icons-dialog.component'; export * from './page.component'; export * from './dialog.component'; export * from './js-func.component'; +export * from './script-lang.component'; diff --git a/ui-ngx/src/app/shared/components/script-lang.component.html b/ui-ngx/src/app/shared/components/script-lang.component.html new file mode 100644 index 0000000000..ca5d7fb7f6 --- /dev/null +++ b/ui-ngx/src/app/shared/components/script-lang.component.html @@ -0,0 +1,25 @@ + +
+ + {{ 'rulenode.script-lang-mvel' | translate }} + {{ 'rulenode.script-lang-java-script' | translate }} + +
diff --git a/ui-ngx/src/app/shared/components/script-lang.component.scss b/ui-ngx/src/app/shared/components/script-lang.component.scss new file mode 100644 index 0000000000..0ed32b29fa --- /dev/null +++ b/ui-ngx/src/app/shared/components/script-lang.component.scss @@ -0,0 +1,61 @@ +/** + * Copyright © 2016-2022 The Thingsboard Authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +.mat-button-toggle-group.tb-script-lang-toggle-group { + &.mat-button-toggle-group-appearance-standard { + border: none; + border-radius: 18px; + .mat-button-toggle+.mat-button-toggle { + border-left: none; + } + } + .mat-button-toggle { + background: rgba(0, 0, 0, 0.06); + height: 36px; + align-items: center; + display: flex; + + .mat-button-toggle-ripple { + top: 2px; + left: 2px; + right: 2px; + bottom: 2px; + border-radius: 16px; + } + } + .mat-button-toggle-button { + color: #959595; + } + .mat-button-toggle-focus-overlay { + border-radius: 16px; + margin: 2px; + } + .mat-button-toggle-checked .mat-button-toggle-button { + background-color: #305680; + color: #fff; + border-radius: 16px; + margin-left: 2px; + margin-right: 2px; + } + .mat-button-toggle-appearance-standard .mat-button-toggle-label-content { + line-height: 32px; + font-size: 16px; + font-weight: 500; + } + .mat-button-toggle-checked.mat-button-toggle-appearance-standard:not(.mat-button-toggle-disabled):hover .mat-button-toggle-focus-overlay { + opacity: .01; + } +} diff --git a/ui-ngx/src/app/shared/components/script-lang.component.ts b/ui-ngx/src/app/shared/components/script-lang.component.ts new file mode 100644 index 0000000000..df4e7d2e97 --- /dev/null +++ b/ui-ngx/src/app/shared/components/script-lang.component.ts @@ -0,0 +1,87 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { Component, forwardRef, Input, OnInit, ViewEncapsulation } from '@angular/core'; +import { ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR } from '@angular/forms'; +import { PageComponent } from '@shared/components/page.component'; +import { Store } from '@ngrx/store'; +import { AppState } from '@core/core.state'; +import { ScriptLanguage } from '@shared/models/rule-node.models'; + +@Component({ + selector: 'tb-script-lang', + templateUrl: './script-lang.component.html', + styleUrls: ['./script-lang.component.scss'], + providers: [ + { + provide: NG_VALUE_ACCESSOR, + useExisting: forwardRef(() => TbScriptLangComponent), + multi: true + } + ], + encapsulation: ViewEncapsulation.None +}) +export class TbScriptLangComponent extends PageComponent implements ControlValueAccessor, OnInit { + + scriptLangFormGroup: FormGroup; + + scriptLanguage = ScriptLanguage; + + @Input() + disabled: boolean; + + private propagateChange = null; + + constructor(protected store: Store, + private fb: FormBuilder) { + super(store); + this.scriptLangFormGroup = this.fb.group({ + scriptLang: [null] + }); + } + + ngOnInit() { + this.scriptLangFormGroup.get('scriptLang').valueChanges.subscribe( + (scriptLang) => { + this.updateView(scriptLang); + } + ); + } + + registerOnChange(fn: any): void { + this.propagateChange = fn; + } + + registerOnTouched(fn: any): void { + } + + setDisabledState(isDisabled: boolean): void { + this.disabled = isDisabled; + if (this.disabled) { + this.scriptLangFormGroup.disable({emitEvent: false}); + } else { + this.scriptLangFormGroup.enable({emitEvent: false}); + } + } + + writeValue(scriptLang: ScriptLanguage): void { + this.scriptLangFormGroup.get('scriptLang').patchValue(scriptLang, {emitEvent: false}); + } + + updateView(scriptLang: ScriptLanguage) { + this.propagateChange(scriptLang); + } +} diff --git a/ui-ngx/src/app/shared/components/time/timezone-select.component.html b/ui-ngx/src/app/shared/components/time/timezone-select.component.html index 2d3a027144..be3c88f131 100644 --- a/ui-ngx/src/app/shared/components/time/timezone-select.component.html +++ b/ui-ngx/src/app/shared/components/time/timezone-select.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + timezone.timezone , ExportableEntity { tenantId?: TenantId; @@ -55,6 +56,7 @@ export interface GridSettings { autoFillHeight?: boolean; mobileAutoFillHeight?: boolean; mobileRowHeight?: number; + layoutDimension?: LayoutDimension; [key: string]: any; } @@ -69,8 +71,17 @@ export interface DashboardLayoutInfo { gridSettings?: GridSettings; } +export interface LayoutDimension { + type?: LayoutType, + fixedWidth?: number, + fixedLayout?: DashboardLayoutId, + leftWidthPercentage?: number +} + export declare type DashboardLayoutId = 'main' | 'right'; +export declare type LayoutType = 'percentage' | 'fixed'; + export declare type DashboardStateLayouts = {[key in DashboardLayoutId]?: DashboardLayout}; export declare type DashboardLayoutsInfo = {[key in DashboardLayoutId]?: DashboardLayoutInfo}; @@ -112,6 +123,7 @@ export interface DashboardConfiguration { export interface Dashboard extends DashboardInfo { configuration?: DashboardConfiguration; + dialogRef?: MatDialogRef; } export interface HomeDashboard extends Dashboard { diff --git a/ui-ngx/src/app/shared/models/rule-node.models.ts b/ui-ngx/src/app/shared/models/rule-node.models.ts index df8ea3ed87..2c5ccd3015 100644 --- a/ui-ngx/src/app/shared/models/rule-node.models.ts +++ b/ui-ngx/src/app/shared/models/rule-node.models.ts @@ -328,6 +328,11 @@ export interface FcRuleEdge extends FcEdge { labels?: string[]; } +export enum ScriptLanguage { + JS = 'JS', + MVEL = 'MVEL' +} + export interface TestScriptInputParams { script: string; scriptType: string; diff --git a/ui-ngx/src/app/shared/models/settings.models.ts b/ui-ngx/src/app/shared/models/settings.models.ts index c1a7cae87a..83426c8b3b 100644 --- a/ui-ngx/src/app/shared/models/settings.models.ts +++ b/ui-ngx/src/app/shared/models/settings.models.ts @@ -419,6 +419,11 @@ export interface RepositorySettings { privateKeyPassword: string; } +export interface RepositorySettingsInfo { + configured: boolean; + readOnly: boolean; +} + export interface AutoVersionCreateConfig extends VersionCreateConfig { branch: string; } diff --git a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts index 5fef4b832e..1cd44652a9 100644 --- a/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts +++ b/ui-ngx/src/app/shared/models/telemetry/telemetry.models.ts @@ -204,7 +204,7 @@ export class EntityDataCmd implements WebsocketCmd { aggTsCmd?: AggTimeSeriesCmd; public isEmpty(): boolean { - return !this.query && !this.historyCmd && !this.latestCmd && !this.tsCmd; + return !this.query && !this.historyCmd && !this.latestCmd && !this.tsCmd && !this.aggTsCmd && !this.aggHistoryCmd; } } diff --git a/ui-ngx/src/app/shared/shared.module.ts b/ui-ngx/src/app/shared/shared.module.ts index bd070f0b75..720f74d2a6 100644 --- a/ui-ngx/src/app/shared/shared.module.ts +++ b/ui-ngx/src/app/shared/shared.module.ts @@ -168,6 +168,7 @@ import { BranchAutocompleteComponent } from '@shared/components/vc/branch-autoco import { PhoneInputComponent } from '@shared/components/phone-input.component'; import { CustomDateAdapter } from '@shared/adapter/custom-datatime-adapter'; import { CustomPaginatorIntl } from '@shared/services/custom-paginator-intl'; +import { TbScriptLangComponent } from '@shared/components/script-lang.component'; export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { return markedOptionsService; @@ -293,7 +294,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TogglePasswordComponent, ProtobufContentComponent, BranchAutocompleteComponent, - PhoneInputComponent + PhoneInputComponent, + TbScriptLangComponent ], imports: [ CommonModule, @@ -497,7 +499,8 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) TogglePasswordComponent, ProtobufContentComponent, BranchAutocompleteComponent, - PhoneInputComponent + PhoneInputComponent, + TbScriptLangComponent ] }) export class SharedModule { } diff --git a/ui-ngx/src/assets/help/en_US/widget/editor/widget_js_markdown_pattern.md b/ui-ngx/src/assets/help/en_US/widget/editor/widget_js_markdown_pattern.md new file mode 100644 index 0000000000..fd4ab60a9c --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/editor/widget_js_markdown_pattern.md @@ -0,0 +1,68 @@ +#### Markdown pattern + +
+
+ +The Markdown template displays the value of the first found key in the entities in the entity alias. + +
+
+ +#### Examples + +Use # to create a Markdown header. The number of characters # specifies the type of header: # - h1, ## - h2, ### - h3, etc. + +```markdown + ###### Markdown/HTML card +{:copy-code} +``` + ###### Markdown/HTML card + +
+
+ +Use - character to create list item. You can create nested lists separating them with tabs in the pattern: + + ```markdown + - Element 1 + - Element 2 + - Element 2.1 + - Element 2.2 + -Element 3 +{:copy-code} + ``` +- Element 1 +- Element 2 + - Element 2.1 + - Element 2.2 +- Element 3 + +
+
+ +Use * character to choose style: + + ```markdown + - *Element 1* + - **Element 2** + - ***Element 3*** +{:copy-code} + ``` +- *Element 1* +- **Element 2** +- ***Element 3*** + +
+
+ +Use ${} to add some value from your key: + ```markdown + - **Element 1**: ${key1Name} + - **Element 1**: ${key2Name} + - **Element 1**: ${key3Name} +{:copy-code} + ``` + - **Element 1**: key1Value + - **Element 2**: key2Value + - **Element 3**: key3Value + diff --git a/ui-ngx/src/assets/help/en_US/widget/lib/map/clustering_color_fn.md b/ui-ngx/src/assets/help/en_US/widget/lib/map/clustering_color_fn.md new file mode 100644 index 0000000000..ba58fa3cd3 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/widget/lib/map/clustering_color_fn.md @@ -0,0 +1,65 @@ +#### Clustering marker function + +
+
+ +*function (data, childCount): string* + +A JavaScript function used to compute clustering marker color. + +**Parameters:** + +
    +
  • data: FormattedData[] + - the array of total markers contained within each cluster.
    + Represents basic entity properties (ex. entityId, entityName)
    and provides access to other entity attributes/timeseries declared in widget datasource configuration. +
  • +
  • childCount: number - the total number of markers contained within that cluster +
  • +
+ +**Returns:** + +Should return string value presenting color of the marker. + +In case no data is returned, color value from **Color** settings field will be used. + +
+ +##### Examples + +
    +
  • +Calculate color depending on temperature telemetry value: +
  • + + +```javascript +let customColor; +for (let markerData of data) { + if (markerData.temperature > 40) { + customColor = 'red' + } +} +return customColor ? customColor : 'green'; +{:copy-code} +``` + +
  • +Calculate color depending on childCount: +
  • + +```javascript +if (childCount < 10) { + return 'green'; +} else if (childCount < 100) { + return 'yellow'; +} else { + return 'red'; +} +{:copy-code} +``` + +
+
+
diff --git a/ui-ngx/src/assets/locale/locale.constant-de_DE.json b/ui-ngx/src/assets/locale/locale.constant-de_DE.json index 59adb6bf31..d60ed055ad 100644 --- a/ui-ngx/src/assets/locale/locale.constant-de_DE.json +++ b/ui-ngx/src/assets/locale/locale.constant-de_DE.json @@ -23,7 +23,7 @@ "no": "Nein", "update": "Aktualisieren", "remove": "Löschen", - "select": "Auswählen", + "select": "Auswählen", "search": "Suche", "clear-search": "Suchanfrage löschen", "assign": "Zuordnen", @@ -57,7 +57,11 @@ "download": "Download", "next-with-label": "Nächste: {{label}}", "read-more": "Mehr dazu", - "hide": "Verstecken" + "hide": "Verstecken", + "done": "Erledigt", + "print": "Drucken", + "restore": "Wiederherstellen", + "confirm": "Bestätigen" }, "aggregation": { "aggregation": "Aggregation", @@ -74,15 +78,15 @@ "admin": { "general": "Allgemein", "general-settings": "Allgemeine Einstellungen", - "home-settings": "Home Einstellungen", + "home-settings": "Home Einstellungen", "outgoing-mail": "E-Mail Versand", "outgoing-mail-settings": "Konfiguration des Postausgangsservers", "system-settings": "Systemeinstellungen", "test-mail-sent": "Test E-Mail wurde erfolgreich versendet!", "base-url": "Basis-URL", "base-url-required": "Basis-URL ist erforderlich.", - "prohibit-different-url": "Prohibit to use hostname from the client request headers", - "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", + "prohibit-different-url": "Prohibit to use hostname from the client request headers", + "prohibit-different-url-hint": "This setting should be enabled for production environments. May cause security issues when disabled", "mail-from": "E-Mail von", "mail-from-required": "E-Mail von ist erforderlich.", "smtp-protocol": "SMTP Protokoll", @@ -96,7 +100,23 @@ "timeout-invalid": "Das ist keine gültige Wartezeit.", "enable-tls": "TLS aktivieren", "tls-version" : "TLS-Version", + "enable-proxy": "Proxy aktivieren", + "proxy-host": "Proxy Host", + "proxy-host-required": "Proxy Host ist erforderlich.", + "proxy-port": "Proxy Port", + "proxy-port-required": "Proxy Port ist erforderlich.", + "proxy-port-range": "Proxy Port sollte im Bereich 1 bis 65535 sein.", + "proxy-user": "Proxy Benutzername", + "proxy-password": "Proxy Passwort", + "change-password": "Passwort ändern", "send-test-mail": "Test E-Mail senden", + "sms-provider": "SMS Anbieter", + "sms-provider-settings": "SMS Anbieter Einstellungen", + "sms-provider-type": "SMS Anbieter Typ", + "sms-provider-type-required": "SMS Anbieter Typ ist erforderlich.", + "sms-provider-type-aws-sns": "Amazon SNS", + "sms-provider-type-twilio": "Twilio", + "sms-provider-type-smpp": "SMPP", "security-settings": "Sicherheitseinstellungen", "password-policy": "Kennwortrichtlinie", "minimum-password-length": "Minimale Passwortlänge", @@ -126,6 +146,8 @@ "no-alarms-matching": "Keine passenden Alarme zu '{{entity}}' wurden gefunden.", "alarm-required": "Alarm ist erforderlich", "alarm-status": "Alarm Status", + "alarm-status-list": "Alarm Status Liste", + "any-status": "Jeder Status", "search-status": { "ANY": "Jeder", "ACTIVE": "Aktiv", @@ -152,6 +174,8 @@ "end-time": "Endzeit", "ack-time": "Bestätigungszeit", "clear-time": "Zeit gelöscht", + "alarm-severity-list": "Alarm Schwere Liste", + "any-severity": "Jede Schwere", "severity-critical": "Kritisch", "severity-major": "Groß", "severity-minor": "Klein", @@ -159,7 +183,7 @@ "severity-indeterminate": "Unbestimmt", "acknowledge": "Bestätigen", "clear": "Löschen", - "search": "Alarme Suchen", + "search": "Alarme suchen", "selected-alarms": "{ count, plural, 1 {1 Alarm} other {# Alarme} } ausgewählt", "no-data": "Keine Daten zum Anzeigen", "polling-interval": "Alarmabfrageintervall (sec)", @@ -184,6 +208,7 @@ "filter-type-single-entity": "Einzelne Entität", "filter-type-entity-list": "Entitätsliste", "filter-type-entity-name": "Entitätsname", + "filter-type-entity-type": "Entitätstyp", "filter-type-state-entity": "Entität aus dem Dashboard Status", "filter-type-state-entity-description": "Entität aus den Dashboard Status Parametern", "filter-type-asset-type": "Objekttyp", @@ -1890,6 +1915,14 @@ "row-double-click": "Doppelklicken auf Zeile" } }, + "paginator" : { + "items-per-page": "Einträge pro Seite:", + "first-page-label": "Erste Seite", + "last-page-label": "Letzte Seite", + "next-page-label": "Nächste Seite", + "previous-page-label": "Vorherige Seite", + "items-per-page-separator": "von" + }, "language": { "language": "Sprache" } diff --git a/ui-ngx/src/assets/locale/locale.constant-en_US.json b/ui-ngx/src/assets/locale/locale.constant-en_US.json index 434535a785..3f3f70860f 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -328,6 +328,7 @@ "repository-url": "Repository URL", "repository-url-required": "Repository URL is required.", "default-branch": "Default branch name", + "repository-read-only": "Read-only", "authentication-settings": "Authentication settings", "auth-method": "Authentication method", "auth-method-username-password": "Password / access token", @@ -2559,7 +2560,22 @@ "color": "Color", "main": "Main", "right": "Right", - "select": "Select target layout" + "left": "Left", + "select": "Select target layout", + "percentage-width": "Percentage width (%)", + "fixed-width": "Fixed width (px)", + "left-width": "Left column (%)", + "right-width": "Right column (%)", + "pick-fixed-side": "Fixed side: ", + "layout-fixed-width": "Fixed width (px)", + "value-min-error": "Value must be more then {{min}}{{unit}}", + "value-max-error": "Value must be less then {{max}}{{unit}}", + "layout-fixed-width-required": "Fixed width is required", + "right-width-percentage-required": "Right percentage is required", + "left-width-percentage-required": "Left percentage is required", + "divider": "Divider", + "right-side": "Right side layout", + "left-side": "Left side layout" }, "legend": { "direction": "Legend direction", @@ -2974,6 +2990,8 @@ "ui-resources-load-error": "Failed to load configuration ui resources.", "invalid-target-rulechain": "Unable to resolve target rule chain!", "test-script-function": "Test script function", + "script-lang-java-script": "Java Script", + "script-lang-mvel": "MVEL", "message": "Message", "message-type": "Message type", "select-message-type": "Select message type", @@ -3487,7 +3505,8 @@ "sync-strategy-overwrite-hint": "Creates or updates selected entities in the repository. All other repository entities are deleted.", "device-credentials-conflict": "Failed to load the device with external id {{entityId}}
due to the same credentials are already present in the database for another device.
Please consider disabling the load credentials setting in the restore form.", "missing-referenced-entity": "Failed to load the {{sourceEntityTypeName}} with external id {{sourceEntityId}}
because it references missing {{targetEntityTypeName}} with id {{targetEntityId}}.", - "runtime-failed": "Failed: {{message}}" + "runtime-failed": "Failed: {{message}}", + "auto-commit-settings-read-only-hint": "Auto-commit feature doesn't work with enabled read-only option in Repository settings." }, "widget": { "widget-library": "Widgets Library", @@ -3675,6 +3694,7 @@ "display-legend": "Display legend", "datasources": "Datasources", "maximum-datasources": "Maximum { count, plural, 1 {1 datasource is allowed.} other {# datasources are allowed} }", + "timeseries-key-error": "At least one timeseries data key should be specified", "datasource-type": "Type", "datasource-parameters": "Parameters", "remove-datasource": "Remove datasource", @@ -4256,6 +4276,7 @@ "qr-code": { "use-qr-code-text-function": "Use QR code text function", "qr-code-text-pattern": "QR code text pattern (for ex. '${entityName} | ${keyName} - some text.')", + "qr-code-text-pattern-hint": "QR code text pattern use the value of the first found key in the entities in the entity alias.", "qr-code-text-pattern-required": "QR code text pattern is required.", "qr-code-text-function": "QR code text function" }, @@ -4610,7 +4631,10 @@ "point-color-function": "Point color function", "use-point-as-anchor": "Use point as anchor", "point-as-anchor-function": "Point as anchor function", - "independent-point-tooltip": "Independent point tooltip" + "independent-point-tooltip": "Independent point tooltip", + "clustering-markers": "Clustering markers", + "use-icon-create-function": "Use markers colour function", + "marker-color-function": "Marker color function" }, "markdown": { "use-markdown-text-function": "Use markdown/HTML value function", @@ -4656,10 +4680,12 @@ "entity-label-column-title": "Entity label column title", "display-entity-type": "Display entity type column", "default-sort-order": "Default sort order", + "custom-title": "Custom header title", "column-width": "Column width (px or %)", "default-column-visibility": "Default column visibility", "column-visibility-visible": "Visible", "column-visibility-hidden": "Hidden", + "column-visibility-hidden-mobile": "Hidden in mobile mode", "column-selection-to-display": "Column selection in 'Columns to Display'", "column-selection-to-display-enabled": "Enabled", "column-selection-to-display-disabled": "Disabled", diff --git a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json index b7c519f7ef..8263cc1614 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -80,18 +80,18 @@ "mail-from": "邮件来自", "mail-from-required": "邮件发件人必填。", "max-failed-login-attempts": "登录失败之前的最大登录尝试次数", - "minimum-digits": "最小位数", - "minimum-digits-range": "最小位数不能为负数", - "minimum-lowercase-letters": "最小小写字母数", - "minimum-lowercase-letters-range": "小写字母的最小数目不能为负数", + "minimum-digits": "最少数字位数", + "minimum-digits-range": "最少数字位数不能为负数", + "minimum-lowercase-letters": "最少小写字母位数", + "minimum-lowercase-letters-range": "最少小写字母位数不能为负数", "minimum-max-failed-login-attempts-range": "登录失败次数不能为负数", "minimum-password-length": "最小密码长度", "minimum-password-length-range": "最小密码长度应在5到50之间", "minimum-password-length-required": "最小密码长度必填", - "minimum-special-characters": "最小特殊字符数", - "minimum-special-characters-range": "特殊字符的最小数目不能为负数", - "minimum-uppercase-letters": "最小大写字母数", - "minimum-uppercase-letters-range": "大写字母的最小数目不能为负数", + "minimum-special-characters": "最少特殊字符位数", + "minimum-special-characters-range": "最少特殊字符位数不能为负数", + "minimum-uppercase-letters": "最少大写字母位数", + "minimum-uppercase-letters-range": "最少大写字母位数不能为负数", "number-from": "发送方电话号码", "number-from-required": "发送方电话号码必填。", "number-to": "电话号码至", @@ -116,11 +116,11 @@ "customer-name-pattern": "客户名称模式", "default-dashboard-name": "默认仪表板名称", "delete-domain": "删除域", - "delete-domain-text": "请注意:确认后域和所有 provider data 将不可用。", - "delete-domain-title": "确实要删除域 '{{domainName}}' 的设置吗?", + "delete-domain-text": "请注意:确认后,域和所有 provider data 将不可恢复。", + "delete-domain-title": "确定要删除域 '{{domainName}}' 的设置吗?", "delete-provider": "删除 Provider", - "delete-registration-text": "请注意:确认后 provider data 将不可用。", - "delete-registration-title": "确实要删除 provider '{{name}}' 吗?", + "delete-registration-text": "请注意:确认后 provider data 将不可恢复。", + "delete-registration-title": "确定要删除 provider '{{name}}' 吗?", "domain-schema-http": "HTTP", "domain-schema-https": "HTTPS", "domain-schema-mixed": "HTTP+HTTPS", @@ -166,8 +166,8 @@ "password-policy": "密码策略", "password-reuse-frequency-days": "密码重用频率(天)", "password-reuse-frequency-days-range": "天内密码重用频率不能为负", - "phone-number-hint": "E.164格式的电话号码,例如+19995550123", - "phone-number-pattern": "电话号码无效。应为E.164格式,例如+19995550123。", + "phone-number-hint": "E.164格式的手机号码,例如+19995550123", + "phone-number-pattern": "手机号码无效。应为E.164格式,例如+19995550123。", "prohibit-different-url": "禁止从客户端请求头中使用主机名", "prohibit-different-url-hint": "应为生产环境启用此设置。禁用时可能会导致安全问题", "proxy-host": "代理主机", @@ -199,14 +199,79 @@ "test-mail-sent": "测试邮件发送成功!", "test-sms-sent": "测试短信发送成功!", "timeout-invalid": "这看起来不像有效的超时值。", - "timeout-msec": "超时(ms)", + "timeout-msec": "超时时间(毫秒)", "timeout-required": "超时必填。", "tls-version": "TLS版本", "twilio-account-sid": "Twilio帐户SID", "twilio-account-sid-required": "Twilio 帐户的 SID 必填", "twilio-account-token": "Twilio帐户令牌", "twilio-account-token-required": "Twilio 帐户令牌必填", - "user-lockout-notification-email": "如果用户帐户锁定,请发送通知到电子邮件" + "user-lockout-notification-email": "如果用户帐户锁定,请发送通知到电子邮件", + "queue-select-name": "选择队列名称", + "queue-name": "名称", + "queue-name-required": "队列名称必填。", + "queues": "队列", + "queue-partitions": "分区", + "queue-submit-strategy": "提交策略", + "queue-processing-strategy": "处理策略", + "queue-configuration": "队列配置", + "repository-settings": "仓库设置", + "repository-url": "仓库 URL", + "repository-url-required": "仓库 URL 必填。", + "default-branch": "默认分支名称", + "authentication-settings": "身份验证设置", + "auth-method": "身份验证方法", + "auth-method-username-password": "密码/访问令牌", + "auth-method-private-key": "私钥", + "password-access-token": "密码/访问令牌", + "change-password-access-token": "更改密码/访问令牌", + "private-key": "私钥", + "drop-private-key-file-or": "拖放私钥文件或", + "passphrase": "口令", + "enter-passphrase": "输入口令", + "change-passphrase": "更改口令", + "check-access": "检查访问权限", + "check-repository-access-success": "已成功验证仓库访问!", + "delete-repository-settings-title": "确定要删除仓库设置吗?", + "delete-repository-settings-text": "请注意:确认后,仓库设置将被删除,版本控制功能将不可用。", + "auto-commit-settings": "自动提交设置", + "auto-commit-entities": "自动提交实体", + "no-auto-commit-entities-prompt": "没有设置自动提交的实体", + "delete-auto-commit-settings-title": "确定要删除自动提交设置吗?", + "delete-auto-commit-settings-text": "请注意:确认后,自动提交设置将被删除,所有实体的自动提交将被禁用。", + "2fa": { + "2fa": "双因素身份验证", + "available-providers": "可用选项", + "issuer-name": "发行者名称", + "issuer-name-required": "发行者名称必填。", + "max-verification-failures-before-user-lockout": "用户锁定前最大验证失败次数", + "max-verification-failures-before-user-lockout-pattern": "最大验证失败次数必须为正整数。", + "number-of-checking-attempts": "检查尝试次数", + "number-of-checking-attempts-pattern": "检查尝试次数必须为正整数。", + "number-of-checking-attempts-required": "检查尝试次数必填。", + "number-of-codes": "验证码数量", + "number-of-codes-pattern": "验证码数量必须为正整数。", + "number-of-codes-required": "验证码数量必填。", + "provider": "Provider", + "retry-verification-code-period": "重试验证码周期(秒)", + "retry-verification-code-period-pattern": "最短时间为5秒", + "retry-verification-code-period-required": "重试验证代码周期必填。", + "total-allowed-time-for-verification": "总允许验证时间(秒)", + "total-allowed-time-for-verification-pattern": "总允许验证最小时间为60秒", + "total-allowed-time-for-verification-required": "总允许验证时间必填。", + "use-system-two-factor-auth-settings": "使用系统双因素身份验证设置", + "verification-code-check-rate-limit": "验证码检查速率限制", + "verification-code-lifetime": "验证码生存期(秒)", + "verification-code-lifetime-pattern": "验证码生存期必须为正整数。", + "verification-code-lifetime-required": "验证码生存期必填。", + "verification-message-template": "验证消息模板", + "verification-limitations": "验证限制", + "verification-message-template-pattern": "验证消息需要包含模板:${code}", + "verification-message-template-required": "验证消息模板必填。", + "within-time": "在时间内 (秒)", + "within-time-pattern": "时间必须是正整数。", + "within-time-required": "时间必填。" + } }, "aggregation": { "aggregation": "聚合", @@ -214,37 +279,37 @@ "count": "计数", "function": "数据聚合功能", "group-interval": "分组间隔", - "limit": "最大值", + "limit": "限制数", "max": "最大值", - "min": "最少值", - "none": "空", + "min": "最小值", + "none": "无", "sum": "求和" }, "alarm": { "ack-time": "确认时间", "acknowledge": "应答", - "aknowledge-alarm-text": "确定要确认报警吗?", - "aknowledge-alarm-title": "确认报警", - "aknowledge-alarms-text": "确定要确认 { count, plural, 1 {# 个警告} other {# 个警告} }?", - "aknowledge-alarms-title": "确认 { count, plural, 1 {# 个警告} other {# 个警告} }", - "alarm": "警告", - "alarm-details": "报警详细信息", - "alarm-filter": "报警筛选器", - "alarm-required": "警告必填", + "aknowledge-alarm-text": "确定要确认告警吗?", + "aknowledge-alarm-title": "确认告警", + "aknowledge-alarms-text": "确定要确认 { count, plural, 1 {# 个告警} other {# 个告警} }吗?", + "aknowledge-alarms-title": "确认 { count, plural, 1 {# 个告警} other {# 个告警} }", + "alarm": "告警", + "alarm-details": "告警详细信息", + "alarm-filter": "告警筛选器", + "alarm-required": "告警必填", "alarm-severity-list": "警报严重性列表", - "alarm-status": "警告状态", - "alarm-status-filter": "报警状态筛选器", - "alarm-status-list": "报警状态列表", - "alarm-type-list": "报警类型列表", - "alarms": "警告", + "alarm-status": "告警状态", + "alarm-status-filter": "告警状态筛选器", + "alarm-status-list": "告警状态列表", + "alarm-type-list": "告警类型列表", + "alarms": "告警", "any-severity": "任何严重程度", "any-status": "任何状态", "any-type": "任何类型", "clear": "清除", "clear-alarm-text": "确定要清除警报吗?", "clear-alarm-title": "清除警报", - "clear-alarms-text": "确定要清除 { count, plural, 1 {# 个警告} other {# 个警告} }?", - "clear-alarms-title": "清除 { count, plural, 1 {# 个警告} other {# 个警告} }", + "clear-alarms-text": "确定要清除 { count, plural, 1 {# 个告警} other {# 个告警} }?", + "clear-alarms-title": "清除 { count, plural, 1 {# 个告警} other {# 个告警} }", "clear-time": "清除时间", "created-time": "创建时间", "details": "详情", @@ -258,18 +323,18 @@ "fetch-size": "获取大小", "fetch-size-error-min": "最小值为10。", "fetch-size-required": "Fetch size 必填。", - "max-count-load": "要加载的最大报警数(0-无限制)", + "max-count-load": "要加载的最大告警数(0-无限制)", "max-count-load-error-min": "最小值为0。", - "max-count-load-required": "加载的最大报警数必填。", + "max-count-load-required": "加载的最大告警数必填。", "min-polling-interval-message": "轮询间隔至少是1秒。", - "no-alarms-matching": "没有找到匹配 '{{entity}}' 的警告", - "no-alarms-prompt": "未发现警告", + "no-alarms-matching": "未找到匹配 '{{entity}}' 的告警", + "no-alarms-prompt": "未发现告警", "no-data": "无数据显示", "originator": "发起者", "originator-type": "发起者类型", - "polling-interval": "警告轮询间隔(秒)", - "polling-interval-required": "警告轮询间隔必填。", - "search": "查找警告", + "polling-interval": "告警轮询间隔(秒)", + "polling-interval-required": "告警轮询间隔必填。", + "search": "查找告警", "search-propagated-alarms": "检索已传递的警报", "search-status": { "ACK": "已确认", @@ -278,8 +343,8 @@ "CLEARED": "已清除", "UNACK": "未确认" }, - "select-alarm": "选择警告", - "selected-alarms": "已选择 { count, plural, 1 {# 个警告} other {# 个警告} }", + "select-alarm": "选择告警", + "selected-alarms": "已选择 { count, plural, 1 {# 个告警} other {# 个告警} }", "severity": "严重程度", "severity-critical": "危险", "severity-indeterminate": "不确定", @@ -299,7 +364,7 @@ "duplicate-alias": "别名已经存在。", "edit": "编辑别名", "entity-filter": "实体筛选器", - "entity-filter-no-entity-matched": "未找到符合指定筛选条件的实体。", + "entity-filter-no-entity-matched": "未找到匹配指定筛选条件的实体。", "filter-type": "筛选器类型", "filter-type-apiUsageState": "Api使用状态", "filter-type-asset-search-query": "资产搜索查询", @@ -328,14 +393,14 @@ "filter-type-relations-query-description": "具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的 {{entities}} ", "filter-type-required": "筛选器类型必填。", "filter-type-single-entity": "单个实体", - "filter-type-state-entity": "仪表板实体状态", + "filter-type-state-entity": "仪表板状态实体", "filter-type-state-entity-description": "仪表板实体令牌状态参数", "last-level-relation": "仅获取最后一级关联", "max-relation-level": "最大关联层级", "name": "别名", "name-required": "别名必填", "no-entity-filter-specified": "没有指定实体筛选器", - "resolve-multiple": "解决为多实体", + "resolve-multiple": "解析为多实体", "root-entity": "根实体", "root-state-entity": "使用仪表板状态实体作为根实体", "state-entity": "仪表板状态实体", @@ -345,7 +410,7 @@ "api-usage": { "api-usage": "Api 使用统计", "data-points": "数据点", - "data-points-storage-days": "日存储数据点数", + "data-points-storage-days": "存储点天", "email": "Email", "email-messages": "Email messages", "email-messages-daily-activity": "Email messages daily activity", @@ -394,8 +459,8 @@ "transport-monthly-activity": "Transport monthly activity", "view-details": "查看详细信息", "view-statistics": "查看统计信息", - "alarm":"警告", - "alarms-created": "创建警告数", + "alarm":"告警", + "alarms-created": "创建告警数", "notifications": "通知" }, "asset": { @@ -424,12 +489,12 @@ "assignedToCustomer": "分配客户", "copyId": "复制资产ID", "delete": "删除资产", - "delete-asset-text": "请注意:确认后资产及其所有相关数据将不可恢复。", - "delete-asset-title": "确定要删除资产 '{{assetName}}'?", + "delete-asset-text": "请注意:确认后,资产及其所有相关数据将不可恢复。", + "delete-asset-title": "确定要删除资产 '{{assetName}}'吗?", "delete-assets": "删除资产", "delete-assets-action-title": "删除 { count, plural, 1 {# 个资产} other {# 个资产} }", - "delete-assets-text": "请注意:确认后,所有选定的资产将被删除,所有相关的数据将变得不可恢复。", - "delete-assets-title": "确定要删除 { count, plural, 1 {# 个资产} other {# 个资产} }?", + "delete-assets-text": "请注意:确认后,所有选定的资产将被删除,所有相关的数据将不可恢复。", + "delete-assets-title": "确定要删除 { count, plural, 1 {# 个资产} other {# 个资产} }吗?", "description": "说明", "details": "详情", "enter-asset-type": "输入资产类型", @@ -439,16 +504,16 @@ "label": "标签", "make-private": "资产设为私有", "make-private-asset-text": "确认后,资产及其所有数据将被私有化,无法被他人访问。", - "make-private-asset-title": "你确定你想创建私有 '{{assetName}}' 资产?", + "make-private-asset-title": "确定要将资产 '{{assetName}}' 设为私有吗?", "make-public": "资产设为公开", "make-public-asset-text": "确认后,资产及其所有数据将被公开并被他人访问。", - "make-public-asset-title": "你确定你想创建公开'{{assetName}}'资产?", + "make-public-asset-title": "确定要将资产 '{{assetName}}' 设为公开吗?", "management": "资产管理", "name": "名称", "name-required": "名称必填。", "name-starts-with": "资产名称以此开头", - "no-asset-types-matching": "没有找到匹配 '{{entitySubtype}}' 的资产类型。", - "no-assets-matching": "没有找到匹配 '{{entity}}' 的资产。", + "no-asset-types-matching": "未找到匹配 '{{entitySubtype}}' 的资产类型。", + "no-assets-matching": "未找到匹配 '{{entity}}' 的资产。", "no-assets-text": "未找到资产", "public": "公开", "search": "查找资产", @@ -460,17 +525,17 @@ "unassign-asset": "未分配资产", "unassign-asset-from-edge": "取消分配边缘", "unassign-asset-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。", - "unassign-asset-from-edge-title": "您确定要取消对'{{assetName}}'资产的分配吗?", + "unassign-asset-from-edge-title": "确定要取消对'{{assetName}}'资产的分配吗?", "unassign-asset-text": "确认后,资产将未分配,客户无法访问。", - "unassign-asset-title": "您确定要取消对'{{assetName}}'资产的分配吗?", + "unassign-asset-title": "确定要取消对'{{assetName}}'资产的分配吗?", "unassign-assets": "取消分配资产", "unassign-assets-action-title": "从客户处取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }", "unassign-assets-text": "确认后,所有选定的资产将被分配,客户无法访问。", - "unassign-assets-title": "您确定要取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }吗?", + "unassign-assets-title": "确定要取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }吗?", "unassign-from-customer": "取消分配客户", "unassign-assets-from-edge": "取消分配边缘", "unassign-assets-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。", - "unassign-assets-from-edge-title": "您确定要取消分配 { count, plural, 1 {1 资产} other {# 个资产} }吗?", + "unassign-assets-from-edge-title": "确定要取消分配 { count, plural, 1 {1 资产} other {# 个资产} }吗?", "view-assets": "查看资产" }, "attribute": { @@ -481,14 +546,14 @@ "attributes-scope": "设备属性范围", "delete-attributes": "删除属性", "delete-attributes-text": "注意,确认后所有选中的属性都会被删除。", - "delete-attributes-title": "您确定要删除 { count, plural, 1 {# 个属性} other {# 个属性} }吗?", + "delete-attributes-title": "确定要删除 { count, plural, 1 {# 个属性} other {# 个属性} }吗?", "enter-attribute-value": "输入属性值", "key": "键名", "key-required": "属性键必填。", "last-update-time": "最后更新时间", "latest-telemetry": "最新遥测数据", "next-widget": "下一个部件", - "no-attributes-text": "找不到属性", + "no-attributes-text": "未找到属性", "no-telemetry-text": "未找到遥测数据", "prev-widget": "上一个部件", "scope-client": "客户端属性", @@ -498,7 +563,7 @@ "selected-attributes": "已选择{ count, plural, 1 {# 个属性} other {# 个属性} }", "selected-telemetry": "已选择 { count, plural, 1 {# telemetry unit} other {# telemetry units} }", "show-on-widget": "在部件上显示", - "value": "价值", + "value": "数值", "value-required": "属性值必填。", "widget-mode": "部件模式" }, @@ -512,7 +577,7 @@ "entity-name": "实体名称", "entity-type": "实体类型", "failure-details": "失败详情", - "no-audit-logs-prompt": "找不到日志", + "no-audit-logs-prompt": "未找到日志", "search": "查找审计日志", "status": "状态", "status-failure": "失败", @@ -530,8 +595,8 @@ "type-attributes-deleted": "删除属性", "type-attributes-read": "读取属性", "type-attributes-updated": "更新属性", - "type-credentials-read": "读取凭证", - "type-credentials-updated": "更新凭证", + "type-credentials-read": "读取凭据", + "type-credentials-updated": "更新凭据", "type-deleted": "删除", "type-lockout": "锁定", "type-login": "登录", @@ -562,13 +627,13 @@ "open-details-page": "打开详情页" }, "confirm-on-exit": { - "html-message": "您有未保存的更改。
确定要离开此页面吗?", - "message": "您有未保存的更改。确定要离开此页吗?", + "html-message": "有未保存的更改。
确定要离开此页面吗?", + "message": "有未保存的更改,确定要离开此页吗?", "title": "未保存的更改" }, "contact": { "address": "地址", - "address2": "地址2", + "address2": "地址二", "city": "城市", "country": "国家", "email": "电子邮件", @@ -611,10 +676,10 @@ "default-customer-required": "为了调试租户级别上的仪表板,需要默认客户。", "delete": "删除此客户", "delete-customer-text": "请注意:确认后,客户及其所有相关数据将不可恢复。", - "delete-customer-title": "您确定要删除客户'{{customerTitle}}'吗?", + "delete-customer-title": "确定要删除客户'{{customerTitle}}'吗?", "delete-customers-action-title": "删除 { count, plural, 1 {# 个客户} other {# 个客户} }", "delete-customers-text": "请注意:确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", - "delete-customers-title": "您确定要删除 { count, plural, 1 {# 个客户} other {# 个客户} }吗?", + "delete-customers-title": "确定要删除 { count, plural, 1 {# 个客户} other {# 个客户} }吗?", "description": "说明", "details": "详情", "devices": "客户设备", @@ -637,8 +702,8 @@ "manage-public-edges": "管理公共边缘", "manage-users": "管理用户", "management": "客户管理", - "no-customers-matching": "没有找到匹配 '{{entity}}' 的客户。", - "no-customers-text": "没有找到客户", + "no-customers-matching": "未找到匹配 '{{entity}}' 的客户。", + "no-customers-text": "未找到客户", "public-assets": "公共资产", "public-dashboards": "公共仪表板", "public-devices": "公共设备", @@ -693,14 +758,14 @@ "dashboard-required": "仪表板必填。", "dashboards": "仪表板库", "delete": "删除仪表板", - "delete-dashboard-text": "请注意:确认后仪表板及其所有相关数据将不可恢复。", - "delete-dashboard-title": "您确定要删除仪表板 '{{dashboardTitle}}'吗?", + "delete-dashboard-text": "请注意:确认后,仪表板及其所有相关数据将不可恢复。", + "delete-dashboard-title": "确定要删除仪表板 '{{dashboardTitle}}'吗?", "delete-dashboards": "删除仪表板", "delete-dashboards-action-title": "删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", - "delete-dashboards-text": "请注意:确认后所有选定的仪表板将被删除,所有相关数据将不可恢复。", + "delete-dashboards-text": "请注意:确认后,所有选定的仪表板将被删除,所有相关数据将不可恢复。", "delete-dashboards-title": "确定要删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }吗?", "delete-state": "删除仪表板状态", - "delete-state-text": "确定要删除仪表板状态 '{{stateName}}' 吗?", + "delete-state-text": "确定要删除仪表板状态 '{{stateName}}' 吗?", "delete-state-title": "删除仪表板状态", "description": "说明", "details": "详情", @@ -730,7 +795,7 @@ "make-private": "仪表板设为私有", "make-private-dashboard": "仪表板设为私有", "make-private-dashboard-text": "确认后,仪表板将被设为私有,不能被其他人访问。", - "make-private-dashboard-title": "您确定要将仪表板 '{{dashboardTitle}}' 设为私有吗?", + "make-private-dashboard-title": "确定要将仪表板 '{{dashboardTitle}}' 设为私有吗?", "make-public": "仪表板设为公开", "manage-assigned-customers": "管理已分配的客户", "manage-states": "仪表板状态管理", @@ -750,16 +815,16 @@ "mobile-row-height": "移动端行高距(px)", "mobile-row-height-required": "移动端行高距必填。", "new-dashboard-title": "新仪表板标题", - "no-dashboards-matching": "找不到符合 '{{entity}}' 的仪表板。", - "no-dashboards-text": "没有找到仪表板", + "no-dashboards-matching": "未找到匹配 '{{entity}}' 的仪表板。", + "no-dashboards-text": "未找到仪表板", "no-image": "无图像选择", - "no-states-text": "找不到状态", + "no-states-text": "未找到状态", "no-widgets": "没有配置部件", "open-dashboard": "打开仪表板", "open-toolbar": "打开仪表板工具栏", "public": "公开", "public-dashboard-notice": "提示: 不要忘记将相关设备公开以访问其数据。", - "public-dashboard-text": "你的仪表板 {{dashboardTitle}} 已被公开,可通过如下 链接访问:", + "public-dashboard-text": "仪表板 {{dashboardTitle}} 已被公开,可通过如下 链接访问:", "public-dashboard-title": "仪表板现已公开", "public-link": "公共链接", "public-link-copied-message": "仪表板的公共链接已被复制到剪贴板", @@ -789,16 +854,16 @@ "states": "仪表板状态", "title": "标题", "image": "仪表板图片", - "mobile-app-settings": "移动应用设置", - "mobile-order": "移动应用中的仪表板订单", - "mobile-hide": "在移动应用中隐藏仪表板", + "mobile-app-settings": "移动端应用设置", + "mobile-order": "移动端应用中的仪表板排序", + "mobile-hide": "在移动端应用中隐藏仪表板", "title-color": "标题颜色", "title-required": "标题必填。", "toolbar-always-open": "工具栏常驻", "unassign-dashboard": "取消分配仪表板", "unassign-dashboard-from-edge-text": "确认后,所有选定的仪表板将被取消分配,边缘将无法访问。", "unassign-dashboard-text": "确认后,面板将被取消分配,客户将无法访问。", - "unassign-dashboard-title": "您确定要取消分配仪表板 '{{dashboardTitle}}'吗?", + "unassign-dashboard-title": "确定要取消分配仪表板 '{{dashboardTitle}}'吗?", "unassign-dashboards": "取消分配仪表板", "unassign-dashboards-action-text": "取消分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户", "unassign-dashboards-action-title": "取消分配此客户 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", @@ -818,9 +883,9 @@ }, "datakey": { "advanced": "高级", - "alarm": "报警字段", - "alarm-fields-required": "警告字段必填。", - "alarm-fields-timeseries-or-attributes-required": "报警字段或实体 Timeseries/属性必填。", + "alarm": "告警字段", + "alarm-fields-required": "告警字段必填。", + "alarm-fields-timeseries-or-attributes-required": "告警字段或实体 Timeseries/属性必填。", "attributes": "属性", "color": "颜色", "configuration": "数据键配置", @@ -862,31 +927,74 @@ "edit-mode": "编辑模式", "toggle-edit-mode": "切换编辑模式" }, + "asset-profile": { + "asset-profile": "资产配置", + "asset-profiles": "资产配置", + "all-asset-profiles": "全部", + "add": "添加资产配置", + "edit": "编辑资产配置", + "asset-profile-details": "资产配置详情", + "no-asset-profiles-text": "未找到资产配置", + "search": "搜索资产配置", + "selected-asset-profiles": "已选择 { count, plural, 1 {1 个资产配置} other {# 个资产配置} }", + "no-asset-profiles-matching": "未找到与 '{{entity}}' 匹配的资产配置。", + "asset-profile-required": "资产配置必填", + "idCopiedMessage": "资产配置ID已复制到剪贴板", + "set-default": "设为默认资产配置", + "delete": "删除资产配置", + "copyId": "复制资产配置ID", + "name-max-length": "名称长度必须少于256个字符", + "new-device-profile-name": "资产配置名称", + "new-device-profile-name-required": "资产配置名称必填。", + "name": "名称", + "name-required": "名称必填。", + "image": "资产配置图片", + "description": "说明", + "default": "默认", + "default-rule-chain": "默认规则链", + "mobile-dashboard": "移动端仪表板", + "mobile-dashboard-hint": "被移动端应用用作资产详情仪表板", + "select-queue-hint": "从下拉列表中选择。", + "delete-asset-profile-title": "确定要删除 '{{assetProfileName}}' 资产配置吗?", + "delete-asset-profile-text": "请注意:确认后,资产配置和所有相关数据将不可恢复。", + "delete-asset-profiles-title": "确定要删除 { count, plural, 1 {1 个资产配置} other {# 个资产配置} }吗?", + "delete-asset-profiles-text": "请注意:确认后,所有选定的资产配置将被删除,所有相关数据将不可恢复。", + "set-default-asset-profile-title": "确定要将 '{{assetProfileName}}' 设为默认资产配置吗?", + "set-default-asset-profile-text": "确认后,资产配置将被标记为默认,并将用于未指定配置的新资产。", + "no-asset-profiles-found": "未不到资产配置。", + "create-new-asset-profile": "创建一个新的!", + "create-asset-profile": "创建资产配置", + "import": "导入资产配置", + "export": "导出资产配置", + "export-failed-error": "无法导出资产配置: {{error}}", + "asset-profile-file": "资产配置文件", + "invalid-asset-profile-file-error": "无法导入资产配置:无效的资产配置数据结构。" + }, "device-profile": { "add": "添加设备配置", - "add-alarm-rule": "添加报警规则", + "add-alarm-rule": "添加告警规则", "add-alarm-rule-details": "详情模板:", "add-clear-alarm-rule": "添加清除条件", "add-create-alarm-rule": "添加创建条件", - "add-create-alarm-rule-prompt": "请添加创建报警规则", + "add-create-alarm-rule-prompt": "请添加创建告警规则", "advanced-settings": "高级设置", - "alarm-details": "报警详细信息", - "alarm-rule-condition": "报警规则条件", + "alarm-details": "告警详细信息", + "alarm-rule-condition": "告警规则条件", "alarm-rule-details": "详情", "alarm-rule-relation-types-list": "要传递的关联类型", - "alarm-rule-relation-types-list-hint": "如果未选择传递关联类型,则将不按关联类型过滤而传递报警。", - "alarm-rules": "报警规则", - "alarm-rules-with-count": "报警规则 ({{count}})", - "alarm-severity-required": "报警严重级别必填。", - "alarm-type": "报警类型", - "alarm-type-required": "报警类型必填。", + "alarm-rule-relation-types-list-hint": "如果未选择传递关联类型,则将不按关联类型过滤而传递告警。", + "alarm-rules": "告警规则", + "alarm-rules-with-count": "告警规则 ({{count}})", + "alarm-severity-required": "告警严重级别必填。", + "alarm-type": "告警类型", + "alarm-type-required": "告警类型必填。", "alarm-type-unique": "警报类型在设备配置警报规则中必须唯一。", "all-device-profiles": "全部", "attributes-proto-schema": "Attributes proto schema", "attributes-proto-schema-required": "Attributes proto schema 必填。", "attributes-topic-filter": "Attributes topic filter", "attributes-topic-filter-required": "Attributes topic 筛选器必填。", - "clear-alarm-rule": "清除报警规则", + "clear-alarm-rule": "清除告警规则", "coap-device-payload-type": "CoAP 设备消息 Payload", "coap-device-type": "CoAP 设备类型", "coap-device-type-default": "默认", @@ -914,19 +1022,19 @@ "copy-provision-key": "复制预配置密钥名", "copy-provision-secret": "复制预配置密钥", "copyId": "复制设备配置 ID", - "create-alarm-pattern": "创建 {{alarmType}} 报警", - "create-alarm-rules": "创建报警规则", + "create-alarm-pattern": "创建 {{alarmType}} 告警", + "create-alarm-rules": "创建告警规则", "create-device-profile": "创建设备配置", "create-new-device-profile": "创建一个新的!", "default": "默认", "default-rule-chain": "默认规则链", - "mobile-dashboard": "移动仪表板", - "mobile-dashboard-hint": "被移动应用用作设备详情仪表板", + "mobile-dashboard": "移动端仪表板", + "mobile-dashboard-hint": "被移动端应用用作设备详情仪表板", "delete": "删除设备配置", - "delete-device-profile-text": "请注意:确认后设备配置和所有相关数据将不可恢复。", - "delete-device-profile-title": "是否确实要删除设备配置 '{{deviceProfileName}}'?", + "delete-device-profile-text": "请注意:确认后,设备配置和所有相关数据将不可恢复。", + "delete-device-profile-title": "确定要删除 '{{deviceProfileName}}' 设备配置吗?", "delete-device-profiles-text": "请注意:确认后,所有选定的设备配置将被删除,所有相关数据将不可恢复。", - "delete-device-profiles-title": "确定要删除 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }?", + "delete-device-profiles-title": "确定要删除 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }吗?", "description": "说明", "device-profile": "设备配置", "device-profile-details": "设备配置详情", @@ -935,15 +1043,15 @@ "device-profiles": "设备配置", "device-provisioning": "设备预配置", "edit": "编辑设备配置", - "edit-alarm-rule": "编辑报警规则", - "edit-alarm-rule-condition": "编辑报警规则条件", - "edit-schedule": "编辑报警日程表", - "enter-alarm-rule-condition-prompt": "请添加报警规则条件", + "edit-alarm-rule": "编辑告警规则", + "edit-alarm-rule-condition": "编辑告警规则条件", + "edit-schedule": "编辑告警日程表", + "enter-alarm-rule-condition-prompt": "请添加告警规则条件", "export": "导出设备配置", "export-failed-error": "无法导出设备配置文件: {{error}}", "idCopiedMessage": "设备配置 ID 已复制到剪贴板", "import": "导入设备配置", - "invalid-device-profile-file-error": "无法导入设备配置文件:无效的设备配置文件数据结构。", + "invalid-device-profile-file-error": "无法导入设备配置:无效的设备配置数据结构。", "mqtt-device-payload-type": "MQTT 设备 Payload", "mqtt-device-payload-type-json": "JSON", "mqtt-device-payload-type-proto": "Protobuf", @@ -955,12 +1063,12 @@ "name-required": "名称是必需的。", "new-device-profile-name": "设备配置名称", "new-device-profile-name-required": "设备配置名称必填。", - "no-alarm-rules": "未配置报警规则", + "no-alarm-rules": "未配置告警规则", "no-clear-alarm-rule": "未配置明确条件", "no-create-alarm-rules": "未配置创建条件", - "no-device-profiles-found": "找不到设备配置。", - "no-device-profiles-matching": "找不到与 '{{entity}}' 匹配的设备配置。", - "no-device-profiles-text": "找不到设备配置", + "no-device-profiles-found": "未找到设备配置。", + "no-device-profiles-matching": "未找到与 '{{entity}}' 匹配的设备配置。", + "no-device-profiles-text": "未找到设备配置", "not-valid-multi-character": "多级通配符的使用无效", "not-valid-pattern-topic-filter": "无效的 Topic 筛选器模式", "not-valid-single-character": "单级通配符的使用无效", @@ -1005,12 +1113,12 @@ "schedule-type": "计划程序类型", "schedule-type-required": "计划类型必填。", "search": "查找设备配置", - "select-alarm-severity": "选择报警严重性", + "select-alarm-severity": "选择告警严重性", "select-queue-hint": "从下拉列表中选择或添加自定义名称。", "selected-device-profiles": "已选择 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }", "set-default": "设为默认设备配置", "set-default-device-profile-text": "确认后,设备配置将被标记为默认,并将用于未指定配置的新设备。", - "set-default-device-profile-title": "确实要将设备配置 '{{deviceProfileName}}' 设为默认值吗?", + "set-default-device-profile-title": "确定要将设备配置 '{{deviceProfileName}}' 设为默认值吗?", "single-level-wildcards-hint": "[+] is suitable for any topic filter level。例如:v1/devices/+/telemetry or +/devices/+/attributes。", "support-level-wildcards": "支持单[+]和多级[#]通配符。", "telemetry-proto-schema": "遥测数据 proto schema", @@ -1067,11 +1175,11 @@ "credentials": "凭据", "credentials-type": "凭据类型", "delete": "删除设备", - "delete-device-text": "请注意:确认后设备及其所有相关数据将不可恢复。", - "delete-device-title": "您确定要删除设备的{{deviceName}}吗?", + "delete-device-text": "请注意:确认后,设备及其所有相关数据将不可恢复。", + "delete-device-title": "确定要删除设备的{{deviceName}}吗?", "delete-devices": "删除设备", "delete-devices-action-title": "删除 {count,plural,1 {# 个设备} other {# 个设备} }", - "delete-devices-text": "请注意:确认后所有选定的设备将被删除,所有相关数据将不可恢复。", + "delete-devices-text": "请注意:确认后,所有选定的设备将被删除,所有相关数据将不可恢复。", "delete-devices-title": "确定要删除{count,plural,1 {# 个设备} other {# 个设备} } 吗?", "description": "说明", "details": "详情", @@ -1083,7 +1191,7 @@ "device-file": "设备文件", "device-list": "设备列表", "device-list-empty": "没有被选中的设备", - "device-name-filter-no-device-matched": "找不到以'{{device}}' 开头的设备。", + "device-name-filter-no-device-matched": "未找到以'{{device}}' 开头的设备。", "device-name-filter-required": "设备名称筛选器必填。", "device-public": "设备公开", "device-required": "设备必填", @@ -1101,23 +1209,23 @@ "label": "标签", "make-private": "私有", "make-private-device-text": "确认后,设备及其所有数据将被设为私有,不被其他人访问。", - "make-private-device-title": "您确定要将设备 '{{deviceName}}' 设为私有吗?", + "make-private-device-title": "确定要将设备 '{{deviceName}}' 设为私有吗?", "make-public": "公开", "make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。", - "make-public-device-title": "您确定要将设备 '{{deviceName}}' 设为公开吗?", + "make-public-device-title": "确定要将设备 '{{deviceName}}' 设为公开吗?", "manage-credentials": "管理凭据", "management": "设备管理", "mqtt-authentication-copied-message": "设备MQTT身份验证已复制到剪贴板", "name": "名称", "name-required": "名称必填。", "name-starts-with": "名称前缀", - "no-alias-matching": "'{{alias}}' 没有找到。", - "no-aliases-found": "找不到别名。", - "no-device-types-matching": "没有找到符合 '{{entitySubtype}}' 的设备类型。", - "no-devices-matching": "找不到与 '{{entity}}' 匹配的设备。", - "no-devices-text": "找不到设备", - "no-key-matching": "'{{key}}' 没有找到。", - "no-keys-found": "找不到密钥。", + "no-alias-matching": "'{{alias}}' 未找到。", + "no-aliases-found": "未找到别名。", + "no-device-types-matching": "未找到匹配 '{{entitySubtype}}' 的设备类型。", + "no-devices-matching": "未找到与 '{{entity}}' 匹配的设备。", + "no-devices-text": "未找到设备", + "no-key-matching": "'{{key}}' 未找到。", + "no-keys-found": "未找到密钥。", "overwrite-activity-time": "覆盖已连接设备的活动时间", "password": "密码", "public": "公开", @@ -1132,15 +1240,15 @@ "unable-delete-device-alias-text": "设备别名 '{{deviceAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", "unable-delete-device-alias-title": "无法删除设备别名", "unassign-device": "取消分配设备", - "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", - "unassign-device-title": "您确定要取消分配设备 '{{deviceName}}'?", + "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", + "unassign-device-title": "确定要取消分配设备 '{{deviceName}}' 吗?", "unassign-device-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。", - "unassign-device-from-edge-title": "您确定要取消分配设备 '{{deviceName}}'?", + "unassign-device-from-edge-title": "确定要取消分配设备 '{{deviceName}}' 吗?", "unassign-devices": "取消分配设备", "unassign-devices-action-title": "取消分配此客户 {count,plural,1 {# 个设备} other {# 个设备} }", "unassign-devices-from-edge": "取消分配边缘", "unassign-devices-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。", - "unassign-devices-from-edge-title": "您确定要取消分配 { count, plural, 1 {1个 设备} other {# 个设备} } 吗?", + "unassign-devices-from-edge-title": "确定要取消分配 { count, plural, 1 {1 个设备} other {# 个设备} } 吗?", "unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。", "unassign-devices-title": "确定要取消分配 {count,plural,1 {# 个设备} other {# 个设备} } 吗?", "unassign-from-customer": "取消分配客户", @@ -1185,11 +1293,11 @@ "dashboards": "边缘仪表板", "delete": "删除边缘", "delete-edge-text": "当心, 确认后,边缘以及所有关联数据将不可恢复。", - "delete-edge-title": "您确定删除边缘 '{{edgeName}}'?", + "delete-edge-title": "确定删除边缘 '{{edgeName}}'吗?", "delete-edges-text": "当心, 确认后,选定的边缘以及所有关联数据将不可恢复。", - "delete-edges-title": "您确认删除边缘 { count, plural, 1 {1 个边缘} other {# 个边缘} }?", + "delete-edges-title": "确定删除 { count, plural, 1 {1 个边缘} other {# 个边缘} }吗?", "deployed": "已部署", - "description": "描述", + "description": "说明", "details": "详情", "devices": "边缘设备", "downlinks": "下行", @@ -1224,10 +1332,10 @@ "load-entity-error": "加载数据失败,实体已经被删除。", "make-private": "私有", "make-private-edge-text": "确认后,边缘及其所有数据将被设为私有,不被其他人访问。", - "make-private-edge-title": "您确定要将边缘 '{{edgeName}}' 设为私有?", + "make-private-edge-title": "确定要将边缘 '{{edgeName}}' 设为私有吗?", "make-public": "公开", "make-public-edge-text": "确认后,边缘及其所有数据将被设为公开并可被其他人访问。", - "make-public-edge-title": "您确定要将边缘 '{{edgeName}}' 设为公开吗?", + "make-public-edge-title": "确定要将边缘 '{{edgeName}}' 设为公开吗?", "management": "边缘管理", "missing-related-rule-chains-text": "分配给边缘的规则链使用规则节点将消息转发给未分配给当前边缘的规则链。

缺少的规则链列表:
{{missingRuleChains}}", "missing-related-rule-chains-title": "边缘缺少关联规则链", @@ -1235,10 +1343,10 @@ "name-max-length": "名称长度必须少于256个字符", "name-required": "名称必填。", "name-starts-with": "边缘名称前缀", - "no-downlinks-prompt": "没有找到下行", - "no-edge-types-matching": "没有找到匹配的边缘类型 '{{entitySubtype}}'。", - "no-edges-matching": "没有找到匹配的边缘 '{{entity}}'。", - "no-edges-text": "没有找到边缘", + "no-downlinks-prompt": "未找到下行", + "no-edge-types-matching": "未找到匹配的边缘类型 '{{entitySubtype}}'。", + "no-edges-matching": "未找到匹配的边缘 '{{entity}}'。", + "no-edges-text": "未找到边缘", "pending": "待定", "public": "公开", "rulechain-templates": "规则链模版", @@ -1250,17 +1358,17 @@ "sync-process-started-successfully": "同步处理开始成功!", "type-max-length": "类型长度必须少于256个字符", "unassign-edge-text": "确定后,边缘将被取消分配,并且客户将无法访问。", - "unassign-edge-title": "您确定取消分配边缘 '{{edgeName}}'?", + "unassign-edge-title": "确定取消分配边缘 '{{edgeName}}' 吗?", "unassign-edges-text": "确定后,所有选定的边缘将被取消分配,并且客户将无法访问。", - "unassign-edges-title": "您确定要取消分配 {count,plural,1 {1 个边缘} other {# 个边缘} } 吗?", + "unassign-edges-title": "确定要取消分配 {count,plural,1 {1 个边缘} other {# 个边缘} } 吗?", "unassign-from-customer": "取消分配客户", "unassign-from-edge": "取消分配边缘", "widget-datasource-error": "组件只支持边缘实体数据源" }, "edge-event": { "action-type-added": "增加", - "action-type-alarm-ack": "警告确认", - "action-type-alarm-clear": "警告清除", + "action-type-alarm-ack": "告警确认", + "action-type-alarm-clear": "告警清除", "action-type-assigned-to-customer": "分配给客户", "action-type-assigned-to-edge": "分配给边缘", "action-type-attributes-deleted": "属性删除", @@ -1295,14 +1403,14 @@ }, "entity-field": { "address": "地址", - "address2": "地址2", + "address2": "地址二", "city": "城市", "country": "国家", "created-time": "创建时间", "email": "电子邮件", "first-name": "名字", "label": "标签", - "last-name": "姓", + "last-name": "姓氏", "name": "名称", "phone": "电话", "state": "省/州", @@ -1341,11 +1449,11 @@ "date-limits": "日期限制", "delete": "删除实体视图", "delete-entity-view-text": "请注意:确认后实体视图及其所有相关数据将不可恢复。", - "delete-entity-view-title": "确定要删除实体视图 '{{entityViewName}}'?", + "delete-entity-view-title": "确定要删除实体视图 '{{entityViewName}}'吗?", "delete-entity-views": "删除实体视图", "delete-entity-views-action-title": "删除 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }", - "delete-entity-views-text": "请注意:确认后,所有选定的实体视图将被删除,所有相关的数据将变得不可恢复。", - "delete-entity-views-title": "确定要删除 { count, plural, 1 {# 实体视图} other {# 实体视图} }?", + "delete-entity-views-text": "请注意:确认后,所有选定的实体视图将被删除,所有相关的数据将不可恢复。", + "delete-entity-views-title": "确定要删除 { count, plural, 1 {# 实体视图} other {# 实体视图} }吗?", "description": "说明", "details": "详情", "duplicate-alias-error": "找到重复别名 '{{alias}}'。
实体视图别名必须是唯一的。", @@ -1357,7 +1465,7 @@ "entity-view-details": "实体视图详细信息", "entity-view-list": "实体视图列表", "entity-view-list-empty": "没有被选中的实体视图", - "entity-view-name-filter-no-entity-view-matched": "找不到以'{{entityView}}' 开头的实体视图。", + "entity-view-name-filter-no-entity-view-matched": "未找到以'{{entityView}}' 开头的实体视图。", "entity-view-name-filter-required": "实体视图名称筛选器必填。", "entity-view-public": "实体视图是公共的", "entity-view-required": "实体视图必填。", @@ -1367,24 +1475,24 @@ "entity-view-types": "实体视图类型", "entity-views": "实体视图", "events": "事件", - "idCopiedMessage": "实体视图Id已复制到剪贴板", + "idCopiedMessage": "实体视图ID已复制到剪贴板", "make-private": "实体视图设为私有", "make-private-entity-view-text": "确认后,实体视图及其所有数据将被私有化,无法被他人访问。", - "make-private-entity-view-title": "你确定你想创建私有 '{{entityViewName}}' 实体视图?", + "make-private-entity-view-title": "确定要将实体视图 '{{entityViewName}}' 设为私有吗?", "make-public": "实体视图设为公开", - "make-public-entity-view-text": "确认后,实体视图 及其所有数据将被公开并被他人访问。", - "make-public-entity-view-title": "你确定你想创建公开 '{{entityViewName}}' 实体视图?", + "make-public-entity-view-text": "确认后,实体视图及其所有数据将被公开并被他人访问。", + "make-public-entity-view-title": "确定要将实体视图 '{{entityViewName}}' 设为公开吗?", "management": "实体视图管理", "name": "名称", "name-required": "名称必填。", "name-starts-with": "名称前缀", - "no-alias-matching": "'{{alias}}' 没有找到。", - "no-aliases-found": "找不到别名。", - "no-entity-view-types-matching": "没有找到匹配 '{{entitySubtype}}' 的实体视图类型。", - "no-entity-views-matching": "找不到与 '{{entity}}' 匹配的实体视图。", - "no-entity-views-text": "找不到实体视图", - "no-key-matching": "'{{key}}' 没有找到。", - "no-keys-found": "找不到密钥。", + "no-alias-matching": "未找到匹配 '{{alias}}' 的别名。", + "no-aliases-found": "未找到别名。", + "no-entity-view-types-matching": "未找到匹配 '{{entitySubtype}}' 的实体视图类型。", + "no-entity-views-matching": "未找到与 '{{entity}}' 匹配的实体视图。", + "no-entity-views-text": "未找到实体视图", + "no-key-matching": "'{{key}}' 未找到。", + "no-keys-found": "未找到密钥。", "public": "公开", "remove-alias": "删除实体视图别名", "search": "查找实体视图", @@ -1406,24 +1514,24 @@ "unable-entity-view-device-alias-title": "无法删除实体视图别名", "unassign-entity-view": "未分配实体视图", "unassign-entity-view-text": "确认后,实体视图将未分配,客户无法访问。", - "unassign-entity-view-title": "您确定要取消对 '{{entityViewName}}'实体视图的分配吗?", + "unassign-entity-view-title": "确定要取消对 '{{entityViewName}}' 实体视图的分配吗?", "unassign-entity-view-from-edge": "未分配实体视图", "unassign-entity-view-from-edge-text": "确认后,实体视图将未分配,边缘无法访问。", - "unassign-entity-view-from-edge-title": "您确定要取消对 '{{entityViewName}}'实体视图的分配吗?", + "unassign-entity-view-from-edge-title": "确定要取消对 '{{entityViewName}}' 实体视图的分配吗?", "unassign-entity-views": "取消分配实体视图", "unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {# 实体视图} other {# 实体视图} }", "unassign-entity-views-from-edge-action-title": "从边缘处取消分配{count,plural,1 {# 实体视图} other {# 实体视图} }", "unassign-entity-views-from-edge-text": "确认后,所有选定的实体视图将被分配,边缘无法访问。", - "unassign-entity-views-from-edge-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }吗?", + "unassign-entity-views-from-edge-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 吗?", "unassign-entity-views-text": "确认后,所有选定的实体视图将被分配,客户无法访问。", - "unassign-entity-views-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }吗?", + "unassign-entity-views-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 吗?", "unassign-from-customer": "取消分配客户", "use-entity-view-name-filter": "使用筛选器", "view-entity-views": "查看实体视图" }, "entity": { "add-alias": "添加实体别名", - "alarm-name-starts-with": "以 '{{prefix}}' 开头的警告", + "alarm-name-starts-with": "以 '{{prefix}}' 开头的告警", "alias": "别名", "alias-required": "实体别名必填。", "aliases": "实体别名", @@ -1449,7 +1557,7 @@ "entity-list": "实体列表", "entity-list-empty": "没有选择实体。", "entity-name": "实体名", - "entity-name-filter-no-entity-matched": "没有找到以 '{{entity}}' 开头的实体", + "entity-name-filter-no-entity-matched": "未找到以 '{{entity}}' 开头的实体", "entity-name-filter-required": "实体名筛选器必填。", "entity-type": "实体类型", "entity-type-list": "实体类型列表", @@ -1457,31 +1565,31 @@ "entity-types": "实体类型", "entity-view-name-starts-with": "以 '{{prefix}}' 开头的实体视图", "key": "键名", - "key-name": "Key name", - "list-of-alarms": "{ count, plural, 1 {个警告} other {# 个警告} }", - "list-of-assets": "{ count, plural, 1 {个资产} other {# 个资产} }", - "list-of-customers": "{ count, plural, 1 {个客户} other {# 个客户} }", - "list-of-dashboards": "{ count, plural, 1 {个仪表板} other {# 个仪表板} }", - "list-of-device-profiles": "{ count, plural, 1 {个设备配置} other {# 个设备配置} }", - "list-of-devices": "{ count, plural, 1 {个设备} other {# 个设备} }", - "list-of-entity-views": "{ count, plural, 1 {个实体视图} other {# 个实体视图} }", - "list-of-plugins": "{ count, plural, 1 {个插件} other {# 个插件} }", - "list-of-rulechains": "{ count, plural, 1 {个规则链} other {# 个规则链} }", - "list-of-rulenodes": "{ count, plural, 1 {个规则节点} other {# 个规则节点} }", - "list-of-rules": "{ count, plural, 1 {个规则} other {# 个规则} }", - "list-of-tenant-profiles": "{ count, plural, 1 {个租户配置} other {# 个租户配置} }", - "list-of-tenants": "{ count, plural, 1 {个租户} other {# 个租户} }", - "list-of-users": "{ count, plural, 1 {个用户} other {# 个用户} }", + "key-name": "键名", + "list-of-alarms": "{ count, plural, 1 {1 个告警} other {# 个告警} }", + "list-of-assets": "{ count, plural, 1 {1 个资产} other {# 个资产} }", + "list-of-customers": "{ count, plural, 1 {1 个客户} other {# 个客户} }", + "list-of-dashboards": "{ count, plural, 1 {1 个仪表板} other {# 个仪表板} }", + "list-of-device-profiles": "{ count, plural, 1 {1 个设备配置} other {# 个设备配置} }", + "list-of-devices": "{ count, plural, 1 {1 个设备} other {# 个设备} }", + "list-of-entity-views": "{ count, plural, 1 {1 个实体视图} other {# 个实体视图} }", + "list-of-plugins": "{ count, plural, 1 {1 个插件} other {# 个插件} }", + "list-of-rulechains": "{ count, plural, 1 {1 个规则链} other {# 个规则链} }", + "list-of-rulenodes": "{ count, plural, 1 {1 个规则节点} other {# 个规则节点} }", + "list-of-rules": "{ count, plural, 1 {1 个规则} other {# 个规则} }", + "list-of-tenant-profiles": "{ count, plural, 1 {1 个租户配置} other {# 个租户配置} }", + "list-of-tenants": "{ count, plural, 1 {1 个租户} other {# 个租户} }", + "list-of-users": "{ count, plural, 1 {1 个用户} other {# 个用户} }", "missing-entity-filter-error": "别名 '{{alias}}' 缺少筛选器", "name-starts-with": "名称开始于", - "no-alias-matching": "没有找到 '{{alias}}'", - "no-aliases-found": "没有找到别名", + "no-alias-matching": "未找到 '{{alias}}'", + "no-aliases-found": "未找到别名", "no-data": "无数据", - "no-entities-matching": "没有找到符合 '{{entity}}' 的实体。", - "no-entities-prompt": "没有找到实体", - "no-entity-types-matching": "没有找到符合 '{{entityType}}' 类型的实体。", - "no-key-matching": "没有找到键 '{{key}}'", - "no-keys-found": "没有找到键", + "no-entities-matching": "未找到匹配 '{{entity}}' 的实体。", + "no-entities-prompt": "未找到实体", + "no-entity-types-matching": "未找到匹配 '{{entityType}}' 类型的实体。", + "no-key-matching": "未找到键名 '{{key}}'", + "no-keys-found": "未找到键名", "plugin-name-starts-with": "以 '{{prefix}}' 开头的插件", "remove-alias": "移除实体别名", "rule-name-starts-with": "以 '{{prefix}}' 开头的规则", @@ -1493,8 +1601,8 @@ "tenant-name-starts-with": "以 '{{prefix}}' 开头的租户", "tenant-profile-name-starts-with": "名称以 '{{prefix}}' 开头的租户配置", "type": "类型", - "type-alarm": "警告", - "type-alarms": "警告", + "type-alarm": "告警", + "type-alarms": "告警", "type-api-usage-state": "Api使用状态", "type-asset": "资产", "type-assets": "资产", @@ -1527,6 +1635,8 @@ "type-tenants": "租户", "type-user": "用户", "type-users": "用户", + "type-asset-profile": "资产配置", + "type-asset-profiles": "资产配置", "unable-delete-entity-alias-text": "实体别名 '{{entityAlias}}' 被以下部件使用不能删除:
{{widgetsList}}", "unable-delete-entity-alias-title": "无法删除实体别名", "use-entity-name-filter": "用户筛选器", @@ -1542,7 +1652,7 @@ "unknown-error": "未知错误" }, "event": { - "alarm": "报警", + "alarm": "告警", "body": "整体", "data": "数据", "data-type": "数据类型", @@ -1560,7 +1670,7 @@ "messages-processed": "消息处理", "metadata": "元数据", "method": "方法", - "no-events-prompt": "找不到事件", + "no-events-prompt": "未找到事件", "relation-type": "关联类型", "server": "服务器", "status": "状态", @@ -1613,10 +1723,10 @@ "credentials": "证书", "custom": "定制", "delete": "删除扩展", - "delete-extension-text": "请注意:确认后,扩展和所有相关数据将变得不可恢复。", - "delete-extension-title": "确实要删除扩展名'{{extensionId}}'吗?", - "delete-extensions-text": "请注意:确认后,所有选定的扩展将被删除。", - "delete-extensions-title": "确定要删除 { count, plural, 1 {# 个扩展} other {# 个扩展} }吗?", + "delete-extension-text": "请注意:确认后,扩展和所有相关数据将不可恢复。", + "delete-extension-title": "确定要删除扩展 '{{extensionId}}'吗?", + "delete-extensions-text": "请注意:确认后,所有选定的扩展将被删除,所有相关数据将不可恢复。", + "delete-extensions-title": "确定要删除 { count, plural, 1 {# 个扩展} other {# 个扩展} }吗?", "device-name-expression": "设备名称表达式", "device-name-filter": "设备名称筛选器", "device-type-expression": "设备类型表达式", @@ -1646,7 +1756,7 @@ "method-filter": "方法筛选器", "modbus-add-server": "添加 server/slave", "modbus-add-server-prompt": "请添加 server/slave", - "modbus-attributes-poll-period": "轮询属性周期 (ms)", + "modbus-attributes-poll-period": "轮询属性周期 (毫秒)", "modbus-baudrate": "波特率", "modbus-byte-order": "字节顺序", "modbus-databits": "数据位", @@ -1655,7 +1765,7 @@ "modbus-encoding": "编码", "modbus-function": "函数", "modbus-parity": "奇偶性", - "modbus-poll-period": "轮询周期 (ms)", + "modbus-poll-period": "轮询周期 (毫秒)", "modbus-poll-period-range": "轮询周期应为正值。", "modbus-port-name": "串口名称", "modbus-register-address": "寄存器地址", @@ -1670,7 +1780,7 @@ "modbus-stopbits-range": "停止位应该在1到2的范围内。", "modbus-tag": "标签", "modbus-tcp-reconnect": "自动重新连接", - "modbus-timeseries-poll-period": "Timeseries 轮询周期 (ms)", + "modbus-timeseries-poll-period": "Timeseries 轮询周期 (毫秒)", "modbus-transport": "Transport", "modbus-unit-id": "单位编号", "modbus-unit-id-range": "单位ID应该在1到247的范围内", @@ -1701,12 +1811,12 @@ "request-id-json-expression": "请求ID JSON表达式", "request-id-topic-expression": "请求ID主题表达式", "request-topic-expression": "请求主题表达式", - "response-timeout": "毫秒内响应超时", + "response-timeout": "响应超时(毫秒)", "response-topic-expression": "响应主题表达式", - "retry-interval": "以毫秒为单位的重试间隔", + "retry-interval": "重试间隔(毫秒)", "selected-extensions": "已选择 { count, plural, 1 {# 个扩展} other {# 个扩展} }", "server-side-rpc": "服务端RPC", - "ssl": "Ssl", + "ssl": "SSL", "sync": { "last-sync-time": "最后同步时间", "not-available": "无法使用", @@ -1714,7 +1824,7 @@ "status": "状态", "sync": "同步" }, - "timeout": "毫秒超时", + "timeout": "超时时间(毫秒)", "timeseries": "Timeseries", "to-double": "加倍", "token": "安全令牌", @@ -1773,15 +1883,15 @@ "timeseries": "Timeseries" }, "key-value-type-change-message": "如果您确认新的值类型,所有输入的键过滤器将被删除。", - "key-value-type-change-title": "是否确实要更改键值类型?", + "key-value-type-change-title": "确定要更改键值类型吗?", "missing-key-filters-error": "筛选器 '{{filter}}' 的键名筛选条件缺失。", "name": "筛选器名称", "name-required": "筛选器名称必填。", "no-dynamic-value": "无动态值", - "no-filter-matching": "找不到 '{{filter}}' 。", + "no-filter-matching": "未找到 '{{filter}}' 。", "no-filter-text": "未指定筛选器", "no-filters": "未配置筛选器", - "no-filters-found": "找不到筛选器。", + "no-filters-found": "未找到筛选器。", "no-key-filters": "未配置键名筛选器", "operation": { "and": "和", @@ -1819,51 +1929,6 @@ "value-type": "值类型" } }, - "firmware": { - "add": "添加固件", - "checksum": "校验和", - "checksum-algorithm": "校验和算法", - "checksum-copied-message": "固件校验和已复制到剪贴板", - "checksum-required": "校验和为必填项。", - "content-type": "Content type", - "copy-checksum": "复制校验和", - "copyId": "复制固件ID", - "delete": "删除固件", - "delete-firmware-text": "请注意:确认后固件将无法恢复。", - "delete-firmware-title": "确定要删除固件'{{firmwareTitle}}'?", - "delete-firmwares-action-title": "Delete { count, plural, 1 {# 个固件} other {# 个固件} }", - "delete-firmwares-text": "请注意:确认后所有选定的资源都将被删除。", - "delete-firmwares-title": "确定要删除固件 { count, plural, 1 {# 个固件} other {# 个固件} }?", - "description": "描述", - "download": "下载固件", - "drop-file": "拖拽固件文件或单击以选择要上传的文件。", - "empty": "固件为空", - "file-name": "文件名", - "file-size": "文件大小", - "file-size-bytes": "文件大小(字节)", - "firmware": "固件", - "firmware-details": "固件详情", - "firmware-required": "固件为必选项。", - "idCopiedMessage": "固件ID已复制到剪贴板", - "no-firmware-matching": "找不到与 '{{entity}}' 匹配的固件。", - "no-firmware-text": "没有找到固件", - "no-software-matching": "找不到与 '{{entity}}' 匹配的软件。", - "no-software-text": "没有找到软件", - "search": "查找固件", - "selected-firmware": "已选择{ count, plural, 1 {# 个固件} other {# 个固件}", - "software": "软件", - "software-required": "软件为必选项。", - "title": "标题", - "title-required": "标题为必填项。", - "type": "固件类型", - "types": { - "firmware": "固件", - "software": "软件" - }, - "version": "版本号", - "version-required": "版本号为必填项。", - "warning-after-save-no-edit": "保存固件后,将无法更改标题和版本号。" -}, "fullscreen": { "exit": "退出全屏", "expand": "展开到全屏", @@ -1883,7 +1948,7 @@ "connector-type-required": "Connector 类型必填。", "connectors": "连接器配置", "create-new-gateway": "创建新网关", - "create-new-gateway-text": "是否确实要创建名为 '{{gatewayName}}' 的新网关?", + "create-new-gateway-text": "确定要创建名为 '{{gatewayName}}' 的新网关?", "delete": "删除配置", "download-tip": "下载配置", "gateway": "网关", @@ -1895,8 +1960,8 @@ "json-required": "字段不能为空。", "no-connectors": "无连接器", "no-data": "没有配置", - "no-gateway-found": "找不到网关。", - "no-gateway-matching": "找不到 '{{item}}' 。", + "no-gateway-found": "未找到网关。", + "no-gateway-matching": "未找到 '{{item}}' 。", "path-logs": "日志文件的路径", "path-logs-required": "路径是必需的。", "remote": "远程配置", @@ -1950,14 +2015,14 @@ "grid": { "add-item-text": "添加新项目", "delete-item": "删除项目", - "delete-item-text": "注意,确认后此项及其所有相关数据将变得不可恢复。", - "delete-item-title": "您确定要删除此项吗?", + "delete-item-text": "请注意,确认后,项目及其所有相关数据将不可恢复。", + "delete-item-title": "确定要删除此项吗?", "delete-items": "删除项目", "delete-items-action-title": "删除 { count, plural, 1 {# 个元素} other {# 个元素} }", - "delete-items-text": "注意,确认后所有选择的项目将被删除,所有相关数据将不可恢复。", - "delete-items-title": "确定你要删除 { count, plural, 1 {# 项} other {# 项} }吗?", + "delete-items-text": "请注意,确认后所有选择的项目将被删除,所有相关数据将不可恢复。", + "delete-items-title": "确定删除 { count, plural, 1 {# 项} other {# 项} }吗?", "item-details": "项目详细信息", - "no-items-text": "没有找到项目", + "no-items-text": "未找到项目", "scroll-to-top": "滚动到顶部" }, "help": { @@ -1977,6 +2042,13 @@ "select-icon": "选择图标", "show-all": "显示所有图标" }, + "phone-input": { + "phone-input-label": "手机号码", + "phone-input-required": "手机号码必填", + "phone-input-validation": "手机号码无效或不存在", + "phone-input-pattern": "无效的手机号码。应为E.164格式,例如:{{phoneNumber}}", + "phone-input-hint": "E.164格式手机号码,例如:{{phoneNumber}}" + }, "import": { "column-example": "示例值数据", "column-key": "属性/遥测键", @@ -1987,7 +2059,7 @@ "column-type": "列类型", "description": "说明", "entity-field": "实体字段", - "isgateway": "Is网关", + "isgateway": "是否网关", "label": "标签", "name": "名称", "routing-key": "边缘键", @@ -1997,12 +2069,12 @@ "timeseries": "Timeseries", "type": "类型" }, - "column-value": "价值", + "column-value": "数值", "csv-delimiter": "CSV分隔符", "csv-first-line-header": "第一行包含列名", "csv-update-data": "更新属性/遥测", "drop-file": "拖动一个JSON文件或者单击以选择要上传的文件。", - "drop-file-csv": "删除CSV文件或单击以选择要上载的文件。", + "drop-file-csv": "拖动一个CSV文件或单击以选择要上载的文件。", "import-csv-invalid-format-error": "文件格式无效。行: '{{line}}'", "import-csv-number-columns-error": "一个文件至少应该包含两列", "message": { @@ -2015,7 +2087,7 @@ "column-type": "选择列类型", "configuration": "导入配置", "creat-entities": "创建新实体", - "done": "多恩", + "done": "完成", "select-file": "选择一个文件" } }, @@ -2033,7 +2105,16 @@ "key": "键名", "no-data": "没有条目", "remove-entry": "删除条目", - "value": "价值" + "value": "数值" + }, + "paginator": { + "items-per-page-label": "每页条数", + "next-page-label": "下一页", + "previous-page-label": "上一页", + "first-page-label": "首页", + "last-page-label": "尾页", + "range_page_label_1": "共计 {{length}} 条", + "range_page_label_2": "从第 {{startIndex}} 条到第 {{endIndex}} 条, 共计 {{length}} 条" }, "language": { "language": "语言" @@ -2086,7 +2167,20 @@ "request-password-reset": "请求密码重置", "reset-password": "重置密码", "sign-in": "登录 ", - "username": "用户名(电子邮件)" + "username": "用户名(电子邮件)", + "verify-your-identity": "身份验证", + "select-way-to-verify": "选择验证方式", + "resend-code": "重发验证码", + "resend-code-wait": "{ time, plural, 1 {1 秒} other {# 秒} }后重发验证码", + "try-another-way": "尝试其他方法", + "totp-auth-description": "请从验证APP中查看验证码。", + "totp-auth-placeholder": "验证码", + "sms-auth-description": "验证码已发送到手机号码 {{contact}}。", + "sms-auth-placeholder": "SMS 验证码", + "email-auth-description": "验证码已发送到电子邮箱地址 {{contact}}。", + "email-auth-placeholder": "电子邮件验证码", + "backup-code-auth-description": "请输入一个备份验证码。", + "backup-code-auth-placeholder": "备份验证码" }, "ota-update": { "add": "添加包", @@ -2110,11 +2204,11 @@ "copyId": "复制包ID", "copied": "已复制!", "delete": "删除包", - "delete-ota-update-text": "小心,OTA升级确认后不可恢复。", - "delete-ota-update-title": "您确定要删除OTA升级'{{title}}'吗?", - "delete-ota-updates-text": "小心,确认后所有选中的OTA升级将被删除。", - "delete-ota-updates-title": "您确定要删除 { count, plural, 1 {1 OTA升级} other {# OTA升级} } 吗?", - "description": "描述", + "delete-ota-update-text": "请注意:确认后,OTA升级将不可恢复。", + "delete-ota-update-title": "确定要删除 '{{title}}' OTA升级吗?", + "delete-ota-updates-text": "请注意:确认后,所有选中的OTA升级将被删除。", + "delete-ota-updates-title": "确定要删除 { count, plural, 1 {1 OTA升级} other {# OTA升级} } 吗?", + "description": "说明", "direct-url": "直接URL", "direct-url-copied-message": "包直接URL已复制到剪贴板", "direct-url-required": "直接URL必填", @@ -2124,10 +2218,10 @@ "file-size": "文件大小", "file-size-bytes": "文件大小(以字节为单位)", "idCopiedMessage": "包ID已被复制到剪贴板", - "no-firmware-matching": "没有找到与'{{entity}}'匹配的兼容固件OTA升级包。", + "no-firmware-matching": "未找到与'{{entity}}'匹配的兼容固件OTA升级包。", "no-firmware-text": "没有提供兼容的固件OTA升级包。", - "no-packages-text": "没有找到包", - "no-software-matching": "没有找到匹配 '{{entity}}' 的兼容软件OTA升级包。", + "no-packages-text": "未找到包", + "no-software-matching": "未找到匹配 '{{entity}}' 的兼容软件OTA升级包。", "no-software-text": "没有提供兼容的软件OTA升级包。", "ota-update": "OTA 升级", "ota-update-details": "OTA 升级详情", @@ -2161,14 +2255,175 @@ "current-password": "当前密码", "last-login-time": "最后登录", "profile": "属性", + "jwt-token": "JWT 令牌", "copy-jwt-token": "复制 JWT 令牌", + "token-valid-till": "令牌有效期至", "tokenCopiedSuccessMessage": "JWT 令牌已复制到剪贴板", "tokenCopiedWarnMessage": "JWT 令牌已过期!请刷新页面。" }, + "profiles": { + "profiles": "配置" + }, + "security": { + "security": "安全", + "2fa": { + "2fa": "双因素身份验证", + "2fa-description": "双因素身份验证可保护您的帐户免受未经授权的访问。在登录时必须输入安全验证码。", + "authenticate-with": "可以使用以下身份验证:", + "disable-2fa-provider-text": "禁用 {{ name }} 会降低帐户的安全性", + "disable-2fa-provider-title": "确定要禁用 {{ name }} 吗?", + "get-new-code": "获取新验证码", + "main-2fa-method": "用作主要的双因素身份验证方法", + "dialog": { + "activation-step-description-email": "下次登录时,系统将提示您输入电子邮件中的验证码。", + "activation-step-description-sms": "下次登录时,系统将提示您输入短信中的验证码。", + "activation-step-description-totp": "下次登录时,您需要提供一个双因素身份验证码。", + "activation-step-label": "激活", + "backup-code-description": "打印出验证码,以便在您需要时使用它们登录帐户。每个备份验证码可以使用一次。", + "backup-code-warn": "离开此页后,这些代码将无法再次显示。使用以下选项安全存放。", + "download-txt": "下载(txt)", + "email-step-description": "输入用作身份验证的电子邮件", + "email-step-label": "电子邮件", + "enable-email-title": "启用电子邮件验证", + "enable-sms-title": "启用SMS验证", + "enable-totp-title": "启用验证APP", + "enter-verification-code": "输入6位验证码", + "get-backup-code-title": "获取备份验证码", + "next": "下一步", + "scan-qr-code": "使用验证APP扫描二维码", + "send-code": "发送验证码", + "sms-step-description": "输入用作身份验证的手机号码", + "sms-step-label": "手机号码", + "success": "操作成功!", + "totp-step-description-install": "可以安装像Google Authenticator、Authy或Duo这样的应用程序。", + "totp-step-description-open": "在手机上打开验证APP。", + "totp-step-label": "获取APP", + "verification-code": "6位验证码", + "verification-code-invalid": "验证码格式无效", + "verification-code-incorrect": "验证码不正确", + "verification-code-many-request": "请求过多,请检查验证码", + "verification-step-description": "输入发送到 {{ address }} 的6位代码", + "verification-step-label": "验证" + }, + "provider": { + "email": "电子邮件", + "email-description": "使用您电子邮件中的验证码进行身份验证。", + "email-hint": "身份验证码通过电子邮件发送到 {{ info }}", + "sms": "SMS", + "sms-description": "使用短信进行身份验证。当登录时,系统会通过短信向您发送验证码。", + "sms-hint": "身份验证码通过短信发送到 {{ info }}", + "totp": "验证APP", + "totp-description": "使用手机上的Google Authenticator、Authy或Duo等应用程序进行身份验证,它将生成用于登录的验证码。", + "totp-hint": "已为您的帐户设置验证APP", + "backup_code": "备份验证码", + "backup-code-description": "这些可打印的一次性密码允许您在离开手机时登录,比如正在旅行。", + "backup-code-hint": "{{ info }}个一次性代码处于激活状态" + } + }, + "password-requirement": { + "at-least": "至少:", + "character": "{ count, plural, 1 {1 位字符} other {# 位字符} }", + "digit": "{ count, plural, 1 {1 位数字} other {# 位数字} }", + "incorrect-password-try-again": "密码不正确。再试一次", + "lowercase-letter": "{ count, plural, 1 {1 位小写字母} other {# 位小写字母} }", + "new-passwords-not-match": "新密码不匹配", + "password-should-not-contain-spaces": "密码不应包含空格", + "password-not-meet-requirements": "密码不符合要求", + "password-requirements": "密码要求", + "password-should-difference": "新密码应与当前密码不同", + "special-character": "{ count, plural, 1 {1 位特殊字符} other {# 位特殊字符} }", + "uppercase-letter": "{ count, plural, 1 {1 位大写字母} other {# 位大写字母} }" + } + }, "queue": { - "name": "队列名称", - "name_required": "队列名称必填!", - "select_name": "选择队列名称" + "queue-name": "队列", + "no-queues-found": "未找到队列", + "no-queues-matching": "未找到匹配 '{{queue}}' 的队列", + "select-name": "选择队列名称", + "name": "名称", + "name-required": "队列名称必填。", + "name-unique": "队列名称必须唯一。", + "name-pattern": "队列名称不能包含ASCII字母数字以外的字符, '.', '_' 和 '-'等。", + "queue-required": "队列必填。", + "topic-required": "队列主题必填。", + "poll-interval-required": "轮询间隔必填。", + "poll-interval-min-value": "轮询间隔不能小于1", + "partitions-required": "分区必填。", + "partitions-min-value": "分区不能小于1", + "pack-processing-timeout-required": "处理超时时间必填。", + "pack-processing-timeout-min-value": "处理超时时间不能小于1", + "batch-size-required": "批量处理大小必填。", + "batch-size-min-value": "批量处理大小不能小于1", + "retries-required": "重试次数必填。", + "retries-min-value": "重试次数不能为负", + "failure-percentage-required": "失败百分比必填。", + "failure-percentage-min-value": "失败百分比值不能小于0", + "failure-percentage-max-value": "失败百分比值不能大于100", + "pause-between-retries-required": "重试间隔必填。", + "pause-between-retries-min-value": "重试间隔不能小于1", + "max-pause-between-retries-required": "最大重试间隔必填。", + "max-pause-between-retries-min-value": "最大重试间隔不能小于1", + "submit-strategy-type-required": "提交策略类型必填。", + "processing-strategy-type-required": "处理策略类型必填。", + "queues": "队列", + "selected-queues": "已选择 { count, plural, 1 {1 个队列} other {# 个队列} }", + "delete-queue-title": "确定要删除 '{{queueName}}' 队列吗?", + "delete-queues-title": "确定要删除 { count, plural, 1 {1 个队列} other {# 个队列} }吗?", + "delete-queue-text": "请注意:确认后,队列和所有相关数据将不可恢复。", + "delete-queues-text": "确认后,所有选定队列都将被删除,无法访问。", + "search": "搜索队列", + "add" : "添加队列", + "details": "队列详情", + "topic": "主题", + "submit-settings": "提交设置", + "submit-strategy": "策略类型*", + "grouping-parameter": "分组参数", + "processing-settings": "重试处理设置", + "processing-strategy": "处理类型*", + "retries-settings": "重试设置", + "polling-settings": "轮询设置", + "batch-processing": "批量处理", + "poll-interval": "轮询间隔", + "partitions": "分区", + "immediate-processing": "即时处理", + "consumer-per-partition": "每个分区消费者单独轮询消息", + "consumer-per-partition-hint": "每个分区启用单独的消费者", + "processing-timeout": "处理超时(毫秒)", + "batch-size": "批量处理大小", + "retries": "重试次数 (0 – 无限制)", + "failure-percentage": "跳过重试的失败消息百分比", + "pause-between-retries": "重试间隔(秒)", + "max-pause-between-retries": "最大重试间隔(秒)", + "delete": "删除队列", + "copyId": "复制队列ID", + "idCopiedMessage": "队列ID已复制到剪贴板", + "description": "说明", + "description-hint": "此文本将显示在队列说明中,而不是所选策略中", + "alt-description": "提交策略:{{submitStrategy}},处理策略:{{processingStrategy}}", + "strategies": { + "sequential-by-originator-label": "按发起者顺序处理", + "sequential-by-originator-hint": "在确认设备A的前一条消息之前,不会提交设备A的新消息", + "sequential-by-tenant-label": "按租户顺序处理", + "sequential-by-tenant-hint": "在确认租户A的前一条消息之前,不会提交租户A的新消息", + "sequential-label": "顺序处理", + "sequential-hint": "在确认前一条消息之前,不会提交新消息", + "burst-label": "突发处理", + "burst-hint": "所有消息都按到达的顺序提交到规则链", + "batch-label": "批量处理", + "batch-hint": "在确认前一批消息之前,不会提交新批", + "skip-all-failures-label": "跳过所有失败", + "skip-all-failures-hint": "忽略所有失败", + "skip-all-failures-and-timeouts-label": "跳过所有失败和超时", + "skip-all-failures-and-timeouts-hint": "忽略所有失败和超时", + "retry-all-label": "全部重试", + "retry-all-hint": "重试处理包中的所有消息", + "retry-failed-label": "失败重试", + "retry-failed-hint": "重试处理包中的所有失败消息", + "retry-timeout-label": "超时重试", + "retry-timeout-hint": "重试处理包中的所有超时消息", + "retry-failed-and-timeout-label": "失败与超时重试", + "retry-failed-and-timeout-hint": "重试处理包中所有失败和超时的消息" + } }, "relation": { "add": "添加关联", @@ -2178,13 +2433,13 @@ "any-relation-type": "任何类型", "delete": "删除关联", "delete-from-relation-text": "确定删除后,当前实体将与实体 '{{entityName}}' 取消关联", - "delete-from-relation-title": "确定要从实体 '{{entityName}}' 删除关联吗?", + "delete-from-relation-title": "确定要从实体 '{{entityName}}' 删除关联吗?", "delete-from-relations-text": "确定删除所有选择的关联关系后,当前实体将与对应的实体取消关联", - "delete-from-relations-title": "确定删除 { count, plural, 1 {# 个关联} other {# 个关联} } 吗?", + "delete-from-relations-title": "确定删除 { count, plural, 1 {# 个关联} other {# 个关联} } 吗?", "delete-to-relation-text": "确定删除后实体 '{{entityName}}' 将取消与当前实体的关联关系。", - "delete-to-relation-title": "确定要删除实体 '{{entityName}}' 的关联吗?", + "delete-to-relation-title": "确定要删除实体 '{{entityName}}' 的关联吗?", "delete-to-relations-text": "确定删除所有选择的关联关系后,与当前实体对应的所有关联关系将被移除。", - "delete-to-relations-title": "确定要删除 { count, plural, 1 {# 个关联} other {# 个关联} }?", + "delete-to-relations-title": "确定要删除 { count, plural, 1 {# 个关联} other {# 个关联} }吗?", "direction": "方向", "direction-type": { "FROM": "从", @@ -2216,15 +2471,15 @@ "resource": { "add": "添加资源", "delete": "删除资源", - "delete-resource-text": "请注意:确认后资源将不可恢复。", - "delete-resource-title": "确定要删除资源 '{{resourceTitle}}' 吗?", + "delete-resource-text": "请注意:确认后,资源将不可恢复。", + "delete-resource-title": "确定要删除资源 '{{resourceTitle}}' 吗?", "delete-resources-action-title": "删除 { count, plural, 1 {# 个资源} other {# 个资源} }", - "delete-resources-text": "请注意:确认后所有选定的资源都将被删除。", - "delete-resources-title": "确定要删除 { count, plural, 1 {# 个资源} other {# 个资源} }?", + "delete-resources-text": "请注意:确认后,所有选定的资源将被删除。", + "delete-resources-title": "确定要删除 { count, plural, 1 {# 个资源} other {# 个资源} }吗?", "drop-file": "拖拽资源文件或单击以选择要上传的文件。", "empty": "资源为空", - "no-resource-matching": "找不到与 '{{widgetsBundle}}' 匹配的资源。", - "no-resource-text": "找不到资源", + "no-resource-matching": "未找到与 '{{widgetsBundle}}' 匹配的资源。", + "no-resource-text": "未找到资源", "open-widgets-bundle": "打开部件库", "resource": "资源", "resource-library-details": "资源库详情", @@ -2239,22 +2494,22 @@ "rulechain": { "add": "添加规则链", "add-rulechain-text": "添加新的规则链", - "assign-to-edge": "分配规则链给边缘", + "assign-to-edge": "分配给边缘", "assign-rulechain-to-edge-title": "分配规则链给边缘", "assign-rulechain-to-edge-text": "请选择要分配给边缘的规则链", "copyId": "复制规则链ID", "create-new-rulechain": "创建新的规则链", "debug-mode": "调试模式", "delete": "删除规则链", - "delete-rulechain-text": "小心,在确认规则链和所有相关数据将变得不可恢复。", - "delete-rulechain-title": " 确实要删除规则链'{{ruleChainName}}'吗?", + "delete-rulechain-text": "请注意,确认后,规则链和所有相关数据将不可恢复。", + "delete-rulechain-title": " 确定要删除规则链'{{ruleChainName}}'吗?", "delete-rulechains-action-title": "删除 { count, plural, 1 {# 个规则链} other {# 个规则链} }", - "delete-rulechains-text": "请注意:确认后,所有选定的规则链将被删除,所有相关的数据将变得不可恢复。", - "delete-rulechains-title": "确实要删除{count, plural, 1 { 1 个规则链} other {# 个规则链} }吗?", + "delete-rulechains-text": "请注意:确认后,所有选定的规则链将被删除,所有相关的数据将不可恢复。", + "delete-rulechains-title": "确定要删除{count, plural, 1 { 1 个规则链} other {# 个规则链} }吗?", "description": "说明", "details": "详情", "edge-rulechain": "边缘规则链", - "edge-template-root": "根模版", + "edge-template-root": "模版根链", "events": "事件", "export": "导出规则链", "export-failed-error": "无法导出规则链:{{error}}", @@ -2277,22 +2532,22 @@ "search": "查找规则链", "select-rulechain": "选择规则链", "selected-rulechains": "已选择 { count, plural, 1 {# 个规则链} other {# 个规则链} }", - "set-auto-assign-to-edge": "创建时分配规则链给边缘", - "set-auto-assign-to-edge-text": "确认后,创建时规则链将自动分配给边缘。", - "set-auto-assign-to-edge-title": "您确定创建时将规则链'{{ruleChainName}}'自动分配给边缘吗", - "set-edge-template-root-rulechain": "设置规则链为边缘模版根", - "set-edge-template-root-rulechain-text": "确认后,规则链将将会成为边缘模版根,且它会成为新创建边缘的根规则链。", - "set-edge-template-root-rulechain-title": "您确定将规则链 '{{ruleChainName}}' 设置为边缘模版根吗?", + "set-auto-assign-to-edge": "将规则链分配给新创建的边缘", + "set-auto-assign-to-edge-text": "确认后,将自动分配规则链给新创建的边缘。", + "set-auto-assign-to-edge-title": "确定将规则链'{{ruleChainName}}'自动分配给新创建的边缘吗?", + "set-edge-template-root-rulechain": "设置为边缘模版根规则链", + "set-edge-template-root-rulechain-text": "确认后,将会成为边缘模版根规则链,且它会成为新创建边缘的根规则链。", + "set-edge-template-root-rulechain-title": "确定将 '{{ruleChainName}}' 设置为边缘模版根规则链吗?", "set-root": "设置为根规则链", - "set-root-rulechain-text": "确认之后,规则链将变为根规格链,并将处理所有传入的传输消息。", - "set-root-rulechain-title": "您确定要生成规则链'{{ruleChainName}}'根吗?", + "set-root-rulechain-text": "确认后,规则链将变为根规格链,并将处理所有传入的传输消息。", + "set-root-rulechain-title": "确定要设置'{{ruleChainName}}'为根规则链吗?", "system": "系统", "unassign-rulechain-from-edge-text": "确认后,规则链将会取消分配,边缘无法访问。", "unassign-rulechains-from-edge-text": "确认后,选定的规则链将会取消分配,边缘无法访问。", - "unassign-rulechains-from-edge-title": "您确定要取消分配规则链 { count, plural, 1 {1 个规则链} other {# 个规则链} }?", - "unset-auto-assign-to-edge": "创建时分配规则链给边缘", - "unset-auto-assign-to-edge-text": "确认后,创建时规则链将不再自动分配给边缘。", - "unset-auto-assign-to-edge-title": "您确定取消创建时将规则链'{{ruleChainName}}'自动分配给边缘吗?" + "unassign-rulechains-from-edge-title": "确定要取消分配 { count, plural, 1 {1 个规则链} other {# 个规则链} }吗?", + "unset-auto-assign-to-edge": "不将规则链分配给边缘", + "unset-auto-assign-to-edge-text": "确认后,将不再自动分配规则链给新创建的边缘。", + "unset-auto-assign-to-edge-title": "确定不再将规则链'{{ruleChainName}}'自动分配给新创建的边缘吗?" }, "rulenode": { "add": "添加规则节点", @@ -2327,8 +2582,8 @@ "metadata-required": "元数据项不能为空。", "name": "名称", "name-required": "名称必填。", - "no-link-label-matching": "找不到'{{label}}'。", - "no-link-labels-found": "找不到链接标签", + "no-link-label-matching": "未找到匹配 '{{label}}' 的链接标签。", + "no-link-labels-found": "未找到链接标签", "open-node-library": "打开节点库", "output": "输出", "reset-debug-mode": "重置所有节点中的调试模式", @@ -2359,85 +2614,172 @@ "ui-resources-load-error": "加载配置UI资源失败。" }, "tenant-profile": { + "tenant-profile": "租户配置", + "tenant-profiles": "租户配置", "add": "添加租户配置", - "copyId": "复制租户配置Id", - "create-new-tenant-profile": "创建一个新的!", - "data": "纵断面数据", - "default": "默认", - "default-storage-ttl-days": "默认存储 TTL 天数(0-无限制)", - "default-storage-ttl-days-range": "不能为负存储天数", - "default-storage-ttl-days-required": "默认存储 TTL 天数必填。", + "edit": "编辑租户配置", + "tenant-profile-details": "租户配置详细信息", + "no-tenant-profiles-text": "未找到租户配置", + "name-max-length": "名称长度必须少于256个字符", + "search": "查找租户配置", + "selected-tenant-profiles": "已选择 { count, plural, 1 {# 个租户配置} other {# 个租户配置} }", + "no-tenant-profiles-matching": "未找到与 '{{entity}}' 匹配的租户配置。", + "tenant-profile-required": "租户配置必填", + "idCopiedMessage": "租户配置ID已复制到剪贴板", + "set-default": "设置该租户配置为默认", "delete": "删除租户配置", - "delete-tenant-profile-text": "请注意:确认后,租户配置和所有相关数据将不可。", - "delete-tenant-profile-title": "确实要删除租户配置 '{{tenantProfileName}}'吗?", - "delete-tenant-profiles-text": "请注意:确认后,所有选定的租户配置将被删除,所有相关数据将不可恢复。", - "delete-tenant-profiles-title": "确定要删除 { count, plural, 1 {# 个租户配置} other {# 个租户配置} }?", + "copyId": "复制租户配置ID", + "name": "名称", + "name-required": "名称必填。", + "data": "配置数据", + "profile-configuration": "配置设置", "description": "说明", - "edit": "编辑租户配置", - "idCopiedMessage": "租户配置Id已复制到剪贴板", - "max-d-p-storage-days": "最大日存储数据点数(0-无限制)", - "max-d-p-storage-days-range": "最大日存储数据点数不能为负数", - "max-d-p-storage-days-required": "最大日存储数据点数必填。", - "max-emails": "发送的最大电子邮件数(0-无限制)", - "max-emails-range": "发送的最大电子邮件数不能为负数", - "max-emails-required": "最大电子邮件发送数必填。", - "max-j-s-executions": "最大JavaScript执行数(0-不受限制)", - "max-j-s-executions-range": "JavaScript的最大执行数不能为负数", - "max-j-s-executions-required": "最大JavaScript执行数必填。", - "max-r-e-executions": "最大规则引擎执行数(0-不受限制)", - "max-r-e-executions-range": "规则引擎执行的最大数目不能为负", - "max-r-e-executions-required": "最大规则引擎执行数必填。", - "max-rule-node-executions-per-message": "每条消息的最大规则节点执行数(0-无限制)", - "max-rule-node-executions-per-message-range": "每条消息的最大规则节点执行数不能为负", - "max-rule-node-executions-per-message-required": "每个消息的最大规则节点执行数必填。", - "max-sms": "发送的最大短信数(0-无限制)", - "max-sms-range": "发送的最大短信数不能为负数", - "max-sms-required": "最大短信发送数必填。", - "max-transport-data-points": "传输数据点的最大数量(0-无限制)", - "max-transport-data-points-range": "传输数据点的最大数目不能为负", - "max-transport-data-points-required": "最大传输数据点数必填。", - "max-transport-messages": "最大传输消息数(0-无限制)", - "max-transport-messages-range": "传输消息的最大数目不能为负数", - "max-transport-messages-required": "最大传输消息数必填。", - "maximum-assets": "最大资产数量(0-无限)", - "maximum-assets-range": "最大资产数不能为负数", + "default": "默认", + "delete-tenant-profile-title": "确定要删除租户配置 '{{tenantProfileName}}'吗?", + "delete-tenant-profile-text": "请注意:确认后,租户配置和所有相关数据将不可恢复。", + "delete-tenant-profiles-title": "确定要删除 { count, plural, 1 {# 个租户配置} other {# 个租户配置} }吗?", + "delete-tenant-profiles-text": "请注意:确认后,所有选定的租户配置将被删除,所有相关数据将不可恢复。", + "set-default-tenant-profile-title": "确定要将租户配置 '{{tenantProfileName}}' 设为默认值吗?", + "set-default-tenant-profile-text": "确认后,此租户配置将被标记为默认配置,并将用于未指定配置的新租户。", + "no-tenant-profiles-found": "未找到租户配置。", + "create-new-tenant-profile": "创建一个新的!", + "create-tenant-profile": "创建新的租户配置", + "import": "导入租户配置", + "export": "导出租户配置", + "export-failed-error": "无法导出租户配置: {{error}}", + "tenant-profile-file": "租户配置文件", + "invalid-tenant-profile-file-error": "无法导入租户配置:无效的租户配置数据结构。", + "advanced-settings": "高级设置", + "entities": "实体", + "rule-engine": "规则引擎", + "time-to-live": "TTL", + "alarms-and-notifications": "告警与通知", + "ota-files-in-bytes": "OTA文件(字节)", + "ws-title": "WS", + "unlimited": "(0 - 无限制)", + "maximum-devices": "最大设备数", + "maximum-devices-required": "最大设备数必填。", + "maximum-devices-range": "最大设备数不能为负数", + "maximum-assets": "最大资产数", "maximum-assets-required": "最大资产数必填。", - "maximum-customers": "最大客户数(0-无限制)", - "maximum-customers-range": "最大客户数不能为负数", + "maximum-assets-range": "最大资产数不能为负数", + "maximum-customers": "最大客户数", "maximum-customers-required": "最大客户数必填。", - "maximum-dashboards": "仪表板的最大数量(0-无限制)", - "maximum-dashboards-range": "仪表板的最大数量不能为负数", + "maximum-customers-range": "最大客户数不能为负数", + "maximum-users": "最大用户数", + "maximum-users-required": "最大用户数必填。", + "maximum-users-range": "最大用户数不能为负数", + "maximum-dashboards": "最大仪表板数", "maximum-dashboards-required": "最大仪表板数必填。", - "maximum-devices": "最大设备数(0-无限制)", - "maximum-devices-range": "最小设备数不能为负数", - "maximum-devices-required": "最大设备数必填。", - "maximum-rule-chains": "最大规则链数(0-无限制)", - "maximum-rule-chains-range": "最大规则链数不能为负数", + "maximum-dashboards-range": "最大仪表板数不能为负数", + "maximum-rule-chains": "最大规则链数", "maximum-rule-chains-required": "最大规则链数必填。", - "maximum-users": "最大用户数(0-无限制)", - "maximum-users-range": "最大用户数不能为负数", - "maximum-users-required": "最大用户数必填。", - "name": "名称", - "name-required": "名称必填。", - "no-tenant-profiles-found": "找不到租户配置。", - "no-tenant-profiles-matching": "找不到与 '{{entity}}' 匹配的租户配置。", - "no-tenant-profiles-text": "找不到租户配置", - "profile-configuration": "配置设置", - "search": "查找租户配置", - "selected-tenant-profiles": "已选择 { count, plural, 1 {# 个租户配置} other {# 个租户配置} }", - "set-default": "设置该租户配置为默认", - "set-default-tenant-profile-text": "确认后,此租户配置将被标记为默认配置,并将用于未指定配置的新租户。", - "set-default-tenant-profile-title": "确实要将租户配置 '{{tenantProfileName}}' 设为默认值吗?", - "tenant-profile": "租户简介", - "tenant-profile-details": "租户配置详细信息", - "tenant-profile-required": "租户配置必填", - "tenant-profiles": "租户配置", - "transport-device-msg-rate-limit": "传输设备消息速率限制。", - "transport-device-telemetry-data-points-rate-limit": "设备遥测数据点速率限制。", - "transport-device-telemetry-msg-rate-limit": "设备遥测消息速率限制。", - "transport-tenant-msg-rate-limit": "传输租户消息速率限制。", - "transport-tenant-telemetry-data-points-rate-limit": "租户遥测数据点速率限制。", - "transport-tenant-telemetry-msg-rate-limit": "租户遥测消息速率限制。" + "maximum-rule-chains-range": "最大规则链数不能为负数", + "maximum-resources-sum-data-size": "资源文件总大小", + "maximum-resources-sum-data-size-required": "资源文件总大小必填。", + "maximum-resources-sum-data-size-range": "资源文件总大小不能为负数", + "maximum-ota-packages-sum-data-size": "OTA包文件总大小", + "maximum-ota-package-sum-data-size-required": "OTA包文件总大小必填。", + "maximum-ota-package-sum-data-size-range": "OTA包文件总大小不能为负数", + "transport-tenant-msg-rate-limit": "租户消息", + "transport-tenant-telemetry-msg-rate-limit": "租户遥测消息", + "transport-tenant-telemetry-data-points-rate-limit": "租户遥测数据点", + "transport-device-msg-rate-limit": "设备消息", + "transport-device-telemetry-msg-rate-limit": "设备遥测数据点", + "transport-device-telemetry-data-points-rate-limit": "设备遥测消息", + "tenant-entity-export-rate-limit": "实体版本创建", + "tenant-entity-import-rate-limit": "实体版本加载", + "max-transport-messages": "最大传输消息数", + "max-transport-messages-required": "最大传输消息数必填。", + "max-transport-messages-range": "最大传输消息数不能为负数", + "max-transport-data-points": "最大传输数据点数", + "max-transport-data-points-required": "最大传输数据点数必填。", + "max-transport-data-points-range": "最大传输数据点数不能为负", + "max-r-e-executions": "最大规则引擎执行数", + "max-r-e-executions-required": "最大规则引擎执行数必填。", + "max-r-e-executions-range": "最大规则引擎执行数不能为负", + "max-j-s-executions": "最大 JavaScript 执行数", + "max-j-s-executions-required": "最大 JavaScript 执行数必填。", + "max-j-s-executions-range": "最大 JavaScript 执行数不能为负数", + "max-d-p-storage-days": "最大存储点天", + "max-d-p-storage-days-required": "最大存储点天必填。", + "max-d-p-storage-days-range": "最大存储点天不能为负数", + "default-storage-ttl-days": "默认存储TTL天数", + "default-storage-ttl-days-required": "默认存储TTL天数必填。.", + "default-storage-ttl-days-range": "默认存储TTL天数不能为负数", + "alarms-ttl-days": "告警TTL天数", + "alarms-ttl-days-required": "告警TTL天数必填。", + "alarms-ttl-days-days-range": "告警TTL天数不能为负数", + "rpc-ttl-days": "RPC TTL天数", + "rpc-ttl-days-required": "RPC TTL天数必填。", + "rpc-ttl-days-days-range": "RPC TTL天数不能为负数", + "max-rule-node-executions-per-message": "每条消息的最大规则节点执行数", + "max-rule-node-executions-per-message-required": "每个消息的最大规则节点执行数必填。", + "max-rule-node-executions-per-message-range": "每条消息的最大规则节点执行数不能为负", + "max-emails": "最大电子邮件发送数", + "max-emails-required": "最大电子邮件发送数必填。", + "max-emails-range": "最大电子邮件发送数不能为负", + "max-sms": "最大短信发送数", + "max-sms-required": "最大短信发送数必填。", + "max-sms-range": "最大短信发送数不能为负", + "max-created-alarms": "最大创建告警数", + "max-created-alarms-required": "最大创建告警数必填。", + "max-created-alarms-range": "最大创建告警数不能为负数", + "no-queue": "未配置队列", + "add-queue": "添加队列", + "queues-with-count": "队列 ({{count}})", + "tenant-rest-limits": "租户REST请求", + "customer-rest-limits": "客户REST请求", + "incorrect-pattern-for-rate-limits": "格式为以冒号分割容量与周期(秒)并以逗号分割配置对,例如 100:1,2000:60", + "too-small-value-zero": "数值必须大于0", + "too-small-value-one": "数值必须大于1", + "cassandra-tenant-limits-configuration": "租户Cassandra查询", + "ws-limit-max-sessions-per-tenant": "租户最大会话数", + "ws-limit-max-sessions-per-customer": "客户最大会话数", + "ws-limit-max-sessions-per-regular-user": "普通用户最大会话数", + "ws-limit-max-sessions-per-public-user": "公共用户最大会话数", + "ws-limit-queue-per-session": "会话最大消息队列大小", + "ws-limit-max-subscriptions-per-tenant": "租户最大订阅数", + "ws-limit-max-subscriptions-per-customer": "客户最大订阅数", + "ws-limit-max-subscriptions-per-regular-user": "普通用户最大订阅数", + "ws-limit-max-subscriptions-per-public-user": "公共用户最大订阅数", + "ws-limit-updates-per-session": "会话WS更新", + "rate-limits": { + "add-limit": "添加限制", + "advanced-settings": "高级设置", + "edit-limit": "编辑限制", + "but-less-than": "但小于", + "edit-transport-tenant-msg-title": "编辑传输租户消息速率限制", + "edit-transport-tenant-telemetry-msg-title": "编辑传输租户遥测消息速率限制", + "edit-transport-tenant-telemetry-data-points-title": "编辑传输租户遥测数据点速率限制", + "edit-transport-device-msg-title": "编辑传输设备消息速率限制", + "edit-transport-device-telemetry-msg-title": "编辑传输设备遥测消息速率限制", + "edit-transport-device-telemetry-data-points-title": "编辑传输设备遥测数据点速率限制", + "edit-transport-tenant-msg-rate-limit-title": "编辑传输租户消息速率限制", + "edit-customer-rest-limits-title": "编辑客户REST请求速率限制", + "edit-ws-limit-updates-per-session-title": "编辑会话WS更新速率限制", + "edit-cassandra-tenant-limits-configuration-title": "编辑租户Cassandra查询速率限制", + "edit-tenant-entity-export-rate-limit-title": "编辑实体版本创建速率限制", + "edit-tenant-entity-import-rate-limit-title": "编辑实体版本加载速率限制", + "messages-per": "条消息每", + "not-set": "未配置", + "number-of-messages": "消息数量", + "number-of-messages-required": "消息数量必填。", + "number-of-messages-min": "最小值为1。", + "preview": "预览", + "per-seconds": "每秒", + "per-seconds-required": "时间比率必填。", + "per-seconds-min": "最小值为1。", + "rate-limits": "速率限制", + "remove-limit": "删除限制", + "transport-tenant-msg": "传输租户消息", + "transport-tenant-telemetry-msg": "传输租户遥测消息", + "transport-tenant-telemetry-data-points": "传输租户遥测数据点", + "transport-device-msg": "传输设备消息", + "transport-device-telemetry-msg": "传输设备遥测消息", + "transport-device-telemetry-data-points": "传输设备遥测数据点", + "sec": "秒" + } }, "tenant": { "add": "添加租户", @@ -2446,7 +2788,7 @@ "copyId": "复制租户ID", "delete": "删除租户", "delete-tenant-text": "请注意:确认后,租户和所有相关数据将不可恢复。", - "delete-tenant-title": "您确定要删除租户'{{tenantTitle}}'吗?", + "delete-tenant-title": "确定要删除租户'{{tenantTitle}}'吗?", "delete-tenants-action-title": "删除 { count, plural, 1 {# 个租户} other {# 个租户} }", "delete-tenants-text": "请注意:确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", "delete-tenants-title": "确定要删除 {count,plural,1 {# 个租户} other {# 个租户} } 吗?", @@ -2454,12 +2796,12 @@ "details": "详情", "events": "事件", "idCopiedMessage": "租户ID已经复制到粘贴板", - "isolated-tb-rule-engine": "在独立的ThingsBoard规则引擎容器中处理", - "isolated-tb-rule-engine-details": "每个独立租户需要单独的微服务", + "isolated-tb-rule-engine": "使用独立的规则引擎服务", + "isolated-tb-rule-engine-details": "每个独立租户需要单独的规则引擎微服务", "manage-tenant-admins": "管理租户管理员", "management": "租户管理", - "no-tenants-matching": "没有找到符合 '{{entity}}' 的租户", - "no-tenants-text": "没有找到租户", + "no-tenants-matching": "未找到匹配 '{{entity}}' 的租户", + "no-tenants-text": "未找到租户", "search": "查找租户", "select-tenant": "选择租户", "selected-tenants": "已选择 { count, plural, 1 {# 个租户} other {# 个租户} }", @@ -2479,7 +2821,27 @@ "minutes": "分钟", "minutes-interval": "{ minutes, plural, 1 {# 分} other {# 分} }", "seconds": "秒", - "seconds-interval": "{ seconds, plural, 1 {# 秒} other {# 秒} }" + "seconds-interval": "{ seconds, plural, 1 {# 秒} other {# 秒} }", + "predefined": { + "yesterday": "昨天", + "day-before-yesterday": "前天", + "this-day-last-week": "前一周的这一天", + "previous-week": "前一周(周日至周六)", + "previous-week-iso": "前一周(周一至周日)", + "previous-month": "前一个月", + "previous-year": "前一年", + "current-hour": "当前小时", + "current-day": "当前天", + "current-day-so-far": "当天到目前为止", + "current-week": "本周(周日至周六)", + "current-week-iso": "本周(周一至周日)", + "current-week-so-far": "本周到目前为止(周日至周六)", + "current-week-iso-so-far": "本周到目前为止(周一至周日)", + "current-month": "本月", + "current-month-so-far": "本月到目前为止", + "current-year": "本年", + "current-year-so-far": "本年到目前为止" + } }, "timeunit": { "days": "天", @@ -2500,7 +2862,8 @@ "period": "从 {{ startTime }} 到 {{ endTime }}", "realtime": "实时", "seconds": "{ seconds, plural, 0 {- 秒 } 1 {# 秒 } other {# 秒 } }", - "time-period": "时间段" + "time-period": "时间段", + "interval": "区间" }, "timezone": { "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", @@ -2524,7 +2887,7 @@ "default-dashboard": "默认面板", "delete": "删除用户", "delete-user-text": "请注意:确认后,用户和所有相关数据将不可恢复。", - "delete-user-title": "您确定要删除用户 '{{userEmail}}' 吗?", + "delete-user-title": "确定要删除用户 '{{userEmail}}' 吗?", "delete-users-action-title": "删除 { count, plural, 1 {# 个用户} other {# 个用户} }", "delete-users-text": "请注意:确认后,所有选定的用户将被删除,所有相关数据将不可恢复。", "delete-users-title": "确定要删除 { count, plural, 1 {# 个用户} other {# 个用户} } 吗?", @@ -2539,11 +2902,11 @@ "enable-account-message": "已成功启用用户帐户!", "first-name": "名字", "invalid-email-format": "无效的邮件格式。", - "last-name": "姓", + "last-name": "姓氏", "login-as-customer-user": "以客户用户身份登录", "login-as-tenant-admin": "以租户管理员身份登录", - "no-users-matching": "没有找到符合 '{{entity}}' 的用户。", - "no-users-text": "找不到用户", + "no-users-matching": "未找到匹配 '{{entity}}' 的用户。", + "no-users-text": "未找到用户", "resend-activation": "重新发送激活", "search": "查找用户", "select-user": "选择用户", @@ -2579,6 +2942,73 @@ "true": "真", "type": "值类型" }, + "version-control": { + "version-control": "版本控制", + "management": "版本控制管理", + "search": "搜索版本", + "branch": "分支", + "default": "默认", + "select-branch": "选择分支", + "branch-required": "分支必填", + "create-entity-version": "创建实体版本", + "version-name": "版本名称", + "version-name-required": "版本名称必填", + "author": "作者", + "export-relations": "导出关联", + "export-attributes": "导出属性", + "export-credentials": "导出凭据", + "entity-versions": "实体版本", + "versions": "版本", + "created-time": "创建时间", + "version-id": "版本ID", + "no-entity-versions-text": "未找到实体版本", + "no-versions-text": "未找到版本", + "copy-full-version-id": "复制完整版本ID", + "create-version": "创建版本", + "creating-version": "请稍候,正在创建版本...", + "nothing-to-commit": "没有要提交的更改", + "restore-version": "还原版本", + "restore-entity-from-version": "从版本 '{{versionName}}' 还原实体", + "restoring-entity-version": "请稍候,正在还原实体版本...", + "load-relations": "加载关联", + "load-attributes": "加载属性", + "load-credentials": "加载凭据", + "compare-with-current": "与当前比较", + "diff-entity-with-version": "与实体版本 '{{versionName}}' 不同", + "previous-difference": "上一个差异", + "next-difference": "下一个差异", + "current": "当前", + "differences": "{ count, plural, 1 {1 个差异} other {# 个差异} }", + "create-entities-version": "创建实体版本", + "default-sync-strategy": "默认同步策略", + "sync-strategy-merge": "合并", + "sync-strategy-overwrite": "覆盖", + "entities-to-export": "导出的实体", + "entities-to-restore": "还原的实体", + "sync-strategy": "同步策略", + "all-entities": "所有实体", + "no-entities-to-export-prompt": "请指定要导出的实体", + "no-entities-to-restore-prompt": "请指定要还原的实体", + "add-entity-type": "添加实体类型", + "remove-all": "全部删除", + "version-create-result": "{ added, plural, 0 {没有实体} 1 {1 个实体} other {# 个实体} } 被添加。
{ modified, plural, 0 {没有实体} 1 {1 个实体} other {# 个实体} } 被修改。
{ removed, plural, 0 {没有实体} 1 {1 个实体} other {# 个实体} } 被删除。", + "remove-other-entities": "删除其他实体", + "find-existing-entity-by-name": "按名称查找现有实体", + "restore-entities-from-version": "从版本 '{{versionName}}' 还原实体", + "restoring-entities-from-version": "请稍候,正在还原实体...", + "no-entities-restored": "未还原任何实体", + "created": "{{created}} 创建", + "updated": "{{updated}} 更新", + "deleted": "{{deleted}} 删除", + "remove-other-entities-confirm-text": "请注意!在还原版本中不存在的当前实体
将被永久 删除。

请输入 remove other entities 进行确认。", + "auto-commit-to-branch": "自动提交到 {{ branch }} 分支", + "default-create-entity-version-name": "{{entityName}} 更新", + "sync-strategy-merge-hint": "创建或更新选定的实体,仓库其他实体均不修改。", + "sync-strategy-overwrite-hint": "创建或更新选定的实体,仓库其他实体将被删除。", + "device-credentials-conflict": "无法加载外部ID为 {{entityId}} 的设备
因为数据库中已存在相同的凭据。
请考虑禁用还原表单中的 加载凭据 设置。", + "missing-referenced-entity": "无法加载外部ID为 {{sourceEntityId}} 的 {{sourceEntityTypeName}}
因为它引用了缺失的 {{targetEntityTypeName}} (ID:{{targetEntityId}}).", + "runtime-failed": "失败: {{message}}" + }, "widget-action": { "custom": "自定义动作", "custom-pretty": "自定义操作(使用HTML模板)", @@ -2606,7 +3036,7 @@ "add-action": "添加 Action", "add-datasource": "添加数据源", "advanced": "高级", - "alarm-source": "警告源", + "alarm-source": "告警源", "background-color": "背景颜色", "data": "数据", "datasource-parameters": "参数", @@ -2614,7 +3044,7 @@ "datasources": "数据源", "decimals": "浮点数后的位数", "delete-action": "删除 Action", - "delete-action-text": "确定要删除部件动作 '{{actionName}}' 吗?", + "delete-action-text": "确定要删除部件Action '{{actionName}}' 吗?", "delete-action-title": "删除 Widget Action", "display-icon": "显示标题图标", "display-legend": "显示图例", @@ -2630,7 +3060,7 @@ "margin": "边缘", "maximum-datasources": "最大允许 { count, plural, 1 {# 个数据源。} other {# 个数据源。} }", "mobile-mode-settings": "移动端设置", - "no-actions-text": "找不到 Action", + "no-actions-text": "未找到 Action", "order": "顺序", "padding": "填充", "remove-datasource": "移除数据源", @@ -2658,7 +3088,7 @@ "add": "添加部件", "add-resource": "添加资源", "add-widget-type": "添加新的部件类型", - "alarm": "警告部件", + "alarm": "告警部件", "alarm-data-overflow": "Widget displays alarms for {{allowedEntities}} (maximum allowed) entities out of {{totalEntities}} entities", "css": "CSS", "data-overflow": "Widget displays {{count}} out of {{total}} entities", @@ -2673,14 +3103,14 @@ "management": "管理部件", "missing-widget-title-error": "部件标题必须指定!", "no-data": "小部件上没有要显示的数据", - "no-data-found": "没有找到数据", + "no-data-found": "未找到数据", "remove": "删除部件", "remove-resource": "删除资源", "remove-widget-text": "确认后,控件和所有相关数据将变得不可恢复。", - "remove-widget-title": "确实要删除 '{{widgetTitle}}'部件吗?", + "remove-widget-title": "确定要删除 '{{widgetTitle}}'部件吗?", "remove-widget-type": "删除部件类型", "remove-widget-type-text": "确认后,窗口部件类型和所有相关数据将不可恢复。", - "remove-widget-type-title": "您确定要删除部件类型 '{{widgetName}}'吗?", + "remove-widget-type-title": "确定要删除部件类型 '{{widgetName}}'吗?", "resource-is-module": "Is module", "resource-url": "JavaScript/CSS URL", "resources": "资源", @@ -2718,7 +3148,7 @@ "current": "当前组", "delete": "删除部件包", "delete-widgets-bundle-text": "请注意:确认后,部件包和所有相关数据将不可恢复。", - "delete-widgets-bundle-title": "您确定要删除部件包 '{{widgetsBundleTitle}}'吗?", + "delete-widgets-bundle-title": "确定要删除部件包 '{{widgetsBundleTitle}}'吗?", "delete-widgets-bundles-action-title": "删除 { count, plural, 1 {# 个部件包} other {# 个部件包} }", "delete-widgets-bundles-text": "请注意:确认后,所有选定的部件包将被删除,所有相关数据将不可恢复。", "delete-widgets-bundles-title": "确定要删除 { count, plural, 1 {# 个部件包} other {# 个部件包} } 吗?", @@ -2731,8 +3161,8 @@ "import": "导入部件包", "invalid-widgets-bundle-file-error": "无法导入部件包:无效的部件包数据结构。", "loading-widgets-bundles": "加载部件包...", - "no-widgets-bundles-matching": "没有找到与 '{{widgetsBundle}}' 匹配的部件包。", - "no-widgets-bundles-text": "找不到部件包", + "no-widgets-bundles-matching": "未找到与 '{{widgetsBundle}}' 匹配的部件包。", + "no-widgets-bundles-text": "未找到部件包", "open-widgets-bundle": "打开部件包", "search": "查找部件包", "selected-widgets-bundles": "已选择 { count, plural, 1 {# 个部件包} other {# 个部件包} }", @@ -2807,7 +3237,7 @@ "blocked-location": "在浏览器中阻止地理位置", "claim-device": "声明设备", "claim-failed": "声明设备失败!", - "claim-not-found": "找不到设备!", + "claim-not-found": "未找到设备!", "claim-successful": "设备已成功申领!", "date": "日期", "device-name": "设备名称", @@ -2827,7 +3257,7 @@ "no-coordinate-specified": "未指定纬度/经度的数据键", "no-datakey-selected": "未选择数据键", "no-entity-selected": "未选择实体", - "no-found-your-camera": "找不到摄像机", + "no-found-your-camera": "未找到摄像机", "no-image": "没有图像", "no-permission-camera": "权限被用户拒绝/此站点无权使用摄像机", "no-support-geolocation": "您的浏览器不支持地理定位", @@ -2846,7 +3276,7 @@ "update-failed": "更新失败", "update-successful": "更新成功", "update-timeseries": "更新 Timeseries", - "value": "价值" + "value": "数值" } } } diff --git a/ui-ngx/src/typings/leaflet-extend-tb.d.ts b/ui-ngx/src/typings/leaflet-extend-tb.d.ts new file mode 100644 index 0000000000..887356c8de --- /dev/null +++ b/ui-ngx/src/typings/leaflet-extend-tb.d.ts @@ -0,0 +1,24 @@ +/// +/// Copyright © 2016-2022 The Thingsboard Authors +/// +/// Licensed under the Apache License, Version 2.0 (the "License"); +/// you may not use this file except in compliance with the License. +/// You may obtain a copy of the License at +/// +/// http://www.apache.org/licenses/LICENSE-2.0 +/// +/// Unless required by applicable law or agreed to in writing, software +/// distributed under the License is distributed on an "AS IS" BASIS, +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +/// See the License for the specific language governing permissions and +/// limitations under the License. +/// + +import { FormattedData } from '@shared/models/widget.models'; + +// redeclare module, maintains compatibility with @types/leaflet +declare module 'leaflet' { + interface MarkerOptions { + tbMarkerData?: FormattedData; + } +} diff --git a/ui-ngx/tsconfig.json b/ui-ngx/tsconfig.json index 9963cadb75..07c6df4d2d 100644 --- a/ui-ngx/tsconfig.json +++ b/ui-ngx/tsconfig.json @@ -23,7 +23,8 @@ "src/typings/jquery.flot.typings.d.ts", "src/typings/jquery.jstree.typings.d.ts", "src/typings/split.js.typings.d.ts", - "src/typings/leaflet-geoman-extend.d.ts" + "src/typings/leaflet-geoman-extend.d.ts", + "src/typings/leaflet-extend-tb.d.ts", ], "paths": { "@app/*": ["src/app/*"],