diff --git a/application/pom.xml b/application/pom.xml index 598d58c447..61fa75cf49 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard application 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 549d1e4dc2..4776ef2aae 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,9 +57,9 @@ "name": "Log RPC from Device", "debugMode": false, "configuration": { - "scriptLang": "MVEL", + "scriptLang": "TBEL", "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);" + "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { @@ -71,9 +71,9 @@ "name": "Log Other", "debugMode": false, "configuration": { - "scriptLang": "MVEL", + "scriptLang": "TBEL", "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);" + "tbelScript": "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 ae2e43fa73..f908b1661e 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,9 +70,9 @@ "name": "Log RPC from Device", "debugMode": false, "configuration": { - "scriptLang": "MVEL", + "scriptLang": "TBEL", "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);" + "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { @@ -84,9 +84,9 @@ "name": "Log Other", "debugMode": false, "configuration": { - "scriptLang": "MVEL", + "scriptLang": "TBEL", "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);" + "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { @@ -174,11 +174,6 @@ "fromIndex": 3, "toIndex": 7, "type": "Timeseries Updated" - }, - { - "fromIndex": 4, - "toIndex": 7, - "type": "Success" } ], "ruleChainConnections": null 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 7927a4380d..88ef27e2f8 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,9 +57,9 @@ "name": "Log RPC from Device", "debugMode": false, "configuration": { - "scriptLang": "MVEL", + "scriptLang": "TBEL", "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);" + "tbelScript": "return '\\nIncoming message:\\n' + JSON.stringify(msg) + '\\nIncoming metadata:\\n' + JSON.stringify(metadata);" } }, { @@ -71,9 +71,9 @@ "name": "Log Other", "debugMode": false, "configuration": { - "scriptLang": "MVEL", + "scriptLang": "TBEL", "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);" + "tbelScript": "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 5891246b44..45a75c6bd9 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; @@ -21,6 +22,8 @@ $$ -- in case of running the upgrade script a second time: IF NOT (SELECT exists(SELECT FROM pg_tables WHERE tablename = 'old_audit_log')) THEN ALTER TABLE audit_log RENAME TO old_audit_log; + CREATE INDEX IF NOT EXISTS idx_old_audit_log_created_time ON old_audit_log(created_time); + ALTER INDEX IF EXISTS idx_audit_log_tenant_id_and_created_time RENAME TO idx_old_audit_log_tenant_id_and_created_time; FOR table_partition IN SELECT tablename AS name, split_part(tablename, '_', 3) AS partition_ts @@ -50,6 +53,7 @@ CREATE TABLE IF NOT EXISTS audit_log ( action_failure_details varchar(1000000) ) PARTITION BY RANGE (created_time); 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_audit_log_id ON audit_log(id); CREATE OR REPLACE PROCEDURE migrate_audit_logs(IN start_time_ms BIGINT, IN end_time_ms BIGINT, IN partition_size_ms BIGINT) LANGUAGE plpgsql AS @@ -73,3 +77,66 @@ BEGIN 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; + CREATE INDEX IF NOT EXISTS idx_old_blob_entity_created_time_tmp ON old_blob_entity(created_time); + 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 INDEX IF NOT EXISTS idx_edge_event_id ON edge_event(id); + +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/ThingsboardInstallApplication.java b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java index e90ed98351..b4a0e019b6 100644 --- a/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java +++ b/application/src/main/java/org/thingsboard/server/ThingsboardInstallApplication.java @@ -29,6 +29,7 @@ import java.util.Arrays; @ComponentScan({"org.thingsboard.server.install", "org.thingsboard.server.service.component", "org.thingsboard.server.service.install", + "org.thingsboard.server.service.security.auth.jwt.settings", "org.thingsboard.server.dao", "org.thingsboard.server.common.stats", "org.thingsboard.server.common.transport.config.ssl", 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 52cd33a885..a02ae3ac4d 100644 --- a/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/ActorSystemContext.java @@ -34,7 +34,7 @@ import org.thingsboard.rule.engine.api.NotificationManager; 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.script.api.tbel.TbelInvokeService; import org.thingsboard.server.actors.service.ActorService; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.cluster.TbClusterService; @@ -77,6 +77,8 @@ 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.util.DataDecodingEncodingService; @@ -273,7 +275,7 @@ public class ActorSystemContext { @Autowired(required = false) @Getter - private MvelInvokeService mvelInvokeService; + private TbelInvokeService tbelInvokeService; @Autowired @Getter @@ -384,6 +386,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 2ffe2002f9..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 @@ -823,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 bc43e69284..ebe2a8424a 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 @@ -27,6 +27,7 @@ import org.thingsboard.common.util.ListeningExecutor; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.rule.engine.api.NotificationManager; 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; @@ -36,6 +37,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; @@ -90,11 +92,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 org.thingsboard.server.service.script.RuleNodeTbelScriptEngine; import java.util.Collections; import java.util.List; @@ -471,11 +475,11 @@ class DefaultTbContext implements TbContext { 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!"); + private ScriptEngine createTbelScriptEngine(String script, String... argNames) { + if (mainCtx.getTbelInvokeService() == null) { + throw new RuntimeException("TBEL execution is disabled!"); } - return new RuleNodeMvelScriptEngine(getTenantId(), mainCtx.getMvelInvokeService(), script, argNames); + return new RuleNodeTbelScriptEngine(getTenantId(), mainCtx.getTbelInvokeService(), script, argNames); } @Override @@ -489,11 +493,11 @@ class DefaultTbContext implements TbContext { switch (scriptLang) { case JS: return createJsScriptEngine(script, argNames); - case MVEL: + case TBEL: if (Arrays.isNullOrEmpty(argNames)) { - return createMvelScriptEngine(script, "msg", "metadata", "msgType"); + return createTbelScriptEngine(script, "msg", "metadata", "msgType"); } else { - return createMvelScriptEngine(script, argNames); + return createTbelScriptEngine(script, argNames); } default: throw new RuntimeException("Unsupported script language: " + scriptLang.name()); @@ -757,12 +761,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/controller/AdminController.java b/application/src/main/java/org/thingsboard/server/controller/AdminController.java index 772c034f62..29f32b8ac9 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AdminController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AdminController.java @@ -22,7 +22,9 @@ import com.google.common.util.concurrent.MoreExecutors; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiParam; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Lazy; import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import org.springframework.web.context.request.async.DeferredResult; @@ -37,8 +39,13 @@ 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.common.data.security.model.JwtSettings; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.dao.settings.AdminSettingsService; import org.thingsboard.server.queue.util.TbCoreComponent; +import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.model.token.JwtTokenFactory; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; import org.thingsboard.server.service.security.system.SystemSecurityService; @@ -65,6 +72,14 @@ public class AdminController extends BaseController { @Autowired private SystemSecurityService systemSecurityService; + @Lazy + @Autowired + private JwtSettingsService jwtSettingsService; + + @Lazy + @Autowired + private JwtTokenFactory tokenFactory; + @Autowired private EntitiesVersionControlService versionControlService; @@ -152,6 +167,40 @@ public class AdminController extends BaseController { } } + @ApiOperation(value = "Get the JWT Settings object (getJwtSettings)", + notes = "Get the JWT Settings object that contains JWT token policy, etc. " + SYSTEM_AUTHORITY_PARAGRAPH, + produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @RequestMapping(value = "/jwtSettings", method = RequestMethod.GET) + @ResponseBody + public JwtSettings getJwtSettings() throws ThingsboardException { + try { + accessControlService.checkPermission(getCurrentUser(), Resource.ADMIN_SETTINGS, Operation.READ); + return checkNotNull(jwtSettingsService.getJwtSettings()); + } catch (Exception e) { + throw handleException(e); + } + } + + @ApiOperation(value = "Update JWT Settings (saveJwtSettings)", + notes = "Updates the JWT Settings object that contains JWT token policy, etc. The tokenSigningKey field is a Base64 encoded string." + SYSTEM_AUTHORITY_PARAGRAPH, + produces = MediaType.APPLICATION_JSON_VALUE) + @PreAuthorize("hasAuthority('SYS_ADMIN')") + @RequestMapping(value = "/jwtSettings", method = RequestMethod.POST) + @ResponseBody + public JwtPair saveJwtSettings( + @ApiParam(value = "A JSON value representing the JWT Settings.") + @RequestBody JwtSettings jwtSettings) throws ThingsboardException { + try { + SecurityUser securityUser = getCurrentUser(); + accessControlService.checkPermission(securityUser, Resource.ADMIN_SETTINGS, Operation.WRITE); + checkNotNull(jwtSettingsService.saveJwtSettings(jwtSettings)); + return tokenFactory.createTokenPair(securityUser); + } catch (Exception e) { + throw handleException(e); + } + } + @ApiOperation(value = "Send test email (sendTestMail)", notes = "Attempts to send test email to the System Administrator User using Mail Settings provided as a parameter. " + "You may change the 'To' email in the user profile of the System Administrator. " + SYSTEM_AUTHORITY_PARAGRAPH) 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..0cb3a3fc92 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/AuthController.java @@ -42,24 +42,22 @@ 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; -import org.thingsboard.server.service.security.model.JwtTokenPair; +import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.service.security.model.ResetPasswordEmailRequest; import org.thingsboard.server.service.security.model.ResetPasswordRequest; 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()); @@ -239,7 +236,7 @@ public class AuthController extends BaseController { @RequestMapping(value = "/noauth/activate", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) @ResponseBody - public JwtTokenPair activateUser( + public JwtPair activateUser( @ApiParam(value = "Activate user request.") @RequestBody ActivateUserRequest activateRequest, @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, @@ -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); } @@ -284,7 +278,7 @@ public class AuthController extends BaseController { @RequestMapping(value = "/noauth/resetPassword", method = RequestMethod.POST) @ResponseStatus(value = HttpStatus.OK) @ResponseBody - public JwtTokenPair resetPassword( + public JwtPair resetPassword( @ApiParam(value = "Reset password request.") @RequestBody ResetPasswordRequest resetPasswordRequest, HttpServletRequest request) throws ThingsboardException { @@ -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/BaseController.java b/application/src/main/java/org/thingsboard/server/controller/BaseController.java index 8f5fee218f..4e179301eb 100644 --- a/application/src/main/java/org/thingsboard/server/controller/BaseController.java +++ b/application/src/main/java/org/thingsboard/server/controller/BaseController.java @@ -123,6 +123,7 @@ import org.thingsboard.server.dao.queue.QueueService; import org.thingsboard.server.dao.relation.RelationService; import org.thingsboard.server.dao.rpc.RpcService; import org.thingsboard.server.dao.rule.RuleChainService; +import org.thingsboard.server.dao.service.Validator; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.dao.tenant.TenantProfileService; import org.thingsboard.server.dao.tenant.TenantService; @@ -423,9 +424,12 @@ public abstract class BaseController { } PageLink createPageLink(int pageSize, int page, String textSearch, String sortProperty, String sortOrder) throws ThingsboardException { - if (!StringUtils.isEmpty(sortProperty)) { + if (StringUtils.isNotEmpty(sortProperty)) { + if (!Validator.isValidProperty(sortProperty)) { + throw new IllegalArgumentException("Invalid sort property"); + } SortOrder.Direction direction = SortOrder.Direction.ASC; - if (!StringUtils.isEmpty(sortOrder)) { + if (StringUtils.isNotEmpty(sortOrder)) { try { direction = SortOrder.Direction.valueOf(sortOrder.toUpperCase()); } catch (IllegalArgumentException 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 85b8e21095..dc74f36f86 100644 --- a/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java +++ b/application/src/main/java/org/thingsboard/server/controller/RuleChainController.java @@ -38,7 +38,7 @@ 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.script.api.tbel.TbelInvokeService; import org.thingsboard.server.actors.ActorSystemContext; import org.thingsboard.server.actors.tenant.DebugTbRateLimits; import org.thingsboard.server.common.data.EventInfo; @@ -69,7 +69,7 @@ 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.RuleNodeJsScriptEngine; -import org.thingsboard.server.service.script.RuleNodeMvelScriptEngine; +import org.thingsboard.server.service.script.RuleNodeTbelScriptEngine; import org.thingsboard.server.service.security.permission.Operation; import org.thingsboard.server.service.security.permission.Resource; @@ -146,7 +146,7 @@ public class RuleChainController extends BaseController { private JsInvokeService jsInvokeService; @Autowired(required = false) - private MvelInvokeService mvelInvokeService; + private TbelInvokeService tbelInvokeService; @Autowired(required = false) private ActorSystemContext actorContext; @@ -154,8 +154,8 @@ public class RuleChainController extends BaseController { @Value("${actors.rule.chain.debug_mode_rate_limits_per_tenant.enabled}") private boolean debugPerTenantEnabled; - @Value("${mvel.enabled:true}") - private boolean mvelEnabled; + @Value("${tbel.enabled:true}") + private boolean tbelEnabled; @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) @@ -378,13 +378,13 @@ 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) + @ApiOperation(value = "Is TBEL script executor enabled", + notes = "Returns 'True' if the TBEL script execution is enabled" + TENANT_AUTHORITY_PARAGRAPH) @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/ruleChain/mvelEnabled", method = RequestMethod.GET) + @RequestMapping(value = "/ruleChain/tbelEnabled", method = RequestMethod.GET) @ResponseBody - public Boolean isMvelEnabled() { - return mvelEnabled; + public Boolean isTbelEnabled() { + return tbelEnabled; } @ApiOperation(value = "Test Script function", @@ -393,7 +393,7 @@ public class RuleChainController extends BaseController { @RequestMapping(value = "/ruleChain/testScript", method = RequestMethod.POST) @ResponseBody public JsonNode testScript( - @ApiParam(value = "Script language: JS or MVEL") + @ApiParam(value = "Script language: JS or TBEL") @RequestParam(required = false) ScriptLanguage scriptLang, @ApiParam(value = "Test JS request. See API call description above.") @RequestBody JsonNode inputParams) throws ThingsboardException { @@ -418,10 +418,10 @@ public class RuleChainController extends BaseController { if (ScriptLanguage.JS.equals(scriptLang)) { engine = new RuleNodeJsScriptEngine(getTenantId(), jsInvokeService, script, argNames); } else { - if (mvelInvokeService == null) { - throw new IllegalArgumentException("MVEL script engine is disabled!"); + if (tbelInvokeService == null) { + throw new IllegalArgumentException("TBEL script engine is disabled!"); } - engine = new RuleNodeMvelScriptEngine(getTenantId(), mvelInvokeService, script, argNames); + engine = new RuleNodeTbelScriptEngine(getTenantId(), tbelInvokeService, script, argNames); } TbMsg inMsg = TbMsg.newMsg(msgType, null, new TbMsgMetaData(metadata), TbMsgDataType.JSON, data); switch (scriptType) { diff --git a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java index 026ce24723..25d6642b4e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TelemetryController.java @@ -661,10 +661,8 @@ public class TelemetryController extends BaseController { logAttributesDeleted(user, entityId, scope, keys, null); if (entityIdSrc.getEntityType().equals(EntityType.DEVICE)) { DeviceId deviceId = new DeviceId(entityId.getId()); - Set keysToNotify = new HashSet<>(); - keys.forEach(key -> keysToNotify.add(new AttributeKey(scope, key))); tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( - user.getTenantId(), deviceId, keysToNotify), null); + user.getTenantId(), deviceId, scope, keys), null); } result.setResult(new ResponseEntity<>(HttpStatus.OK)); } diff --git a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java index 003b4ab450..5ce46e324e 100644 --- a/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java +++ b/application/src/main/java/org/thingsboard/server/controller/TwoFactorAuthController.java @@ -39,7 +39,7 @@ import org.thingsboard.server.queue.util.TbCoreComponent; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.auth.rest.RestAuthenticationDetails; -import org.thingsboard.server.service.security.model.JwtTokenPair; +import org.thingsboard.server.common.data.security.model.JwtPair; 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; @@ -87,8 +87,8 @@ public class TwoFactorAuthController extends BaseController { "and Too Many Requests error if rate limits are exceeded.") @PostMapping("/verification/check") @PreAuthorize("hasAuthority('PRE_VERIFICATION_TOKEN')") - public JwtTokenPair checkTwoFaVerificationCode(@RequestParam TwoFaProviderType providerType, - @RequestParam String verificationCode, HttpServletRequest servletRequest) throws Exception { + public JwtPair checkTwoFaVerificationCode(@RequestParam TwoFaProviderType providerType, + @RequestParam String verificationCode, HttpServletRequest servletRequest) throws Exception { SecurityUser user = getCurrentUser(); boolean verificationSuccess = twoFactorAuthService.checkVerificationCode(user, providerType, verificationCode, true); if (verificationSuccess) { 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..86e4456638 100644 --- a/application/src/main/java/org/thingsboard/server/controller/UserController.java +++ b/application/src/main/java/org/thingsboard/server/controller/UserController.java @@ -43,12 +43,10 @@ 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.common.data.security.model.JwtPair; 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; @@ -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; @@ -148,7 +145,7 @@ public class UserController extends BaseController { @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN')") @RequestMapping(value = "/user/{userId}/token", method = RequestMethod.GET) @ResponseBody - public JwtTokenPair getUserToken( + public JwtPair getUserToken( @ApiParam(value = USER_ID_PARAM_DESCRIPTION) @PathVariable(USER_ID) String strUserId) throws ThingsboardException { checkParameter(USER_ID, strUserId); @@ -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); } @@ -187,7 +182,7 @@ public class UserController extends BaseController { @RequestBody User user, @ApiParam(value = "Send activation email (or use activation link)", defaultValue = "true") @RequestParam(required = false, defaultValue = "true") boolean sendActivationMail, HttpServletRequest request) throws ThingsboardException { - if (Authority.TENANT_ADMIN.equals(getCurrentUser().getAuthority())) { + if (!Authority.SYS_ADMIN.equals(getCurrentUser().getAuthority())) { user.setTenantId(getCurrentUser().getTenantId()); } checkEntity(user.getId(), user, Resource.USER); @@ -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..9facea6dde 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) { @@ -265,6 +273,7 @@ public class ThingsboardInstallService { systemDataLoaderService.createSysAdmin(); systemDataLoaderService.createDefaultTenantProfiles(); systemDataLoaderService.createAdminSettings(); + systemDataLoaderService.createRandomJwtSettings(); systemDataLoaderService.loadSystemWidgets(); systemDataLoaderService.createOAuth2Templates(); systemDataLoaderService.createQueues(); 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 172820a667..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,7 +16,7 @@ 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; @@ -26,7 +26,7 @@ import org.thingsboard.server.gen.transport.TransportProtos.ToUsageStatsServiceM import org.thingsboard.server.queue.common.TbProtoQueueMsg; import org.thingsboard.server.queue.discovery.event.PartitionChangeEvent; -public interface TbApiUsageStateService extends TbApiUsageStateClient, ApplicationListener { +public interface TbApiUsageStateService extends TbApiUsageStateClient, RuleEngineApiUsageStateService, ApplicationListener { void process(TbProtoQueueMsg msg, TbCallback callback); 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 2342997c65..9571319226 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 @@ -187,10 +187,7 @@ public final class EdgeGrpcSession implements Closeable { public void startSyncProcess(TenantId tenantId, EdgeId edgeId, boolean fullSync) { log.trace("[{}][{}] Staring edge sync process", tenantId, edgeId); syncCompleted = false; - 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)); - } + interruptGeneralProcessingOnSync(tenantId, edgeId); doSync(new EdgeSyncCursor(ctx, edge, fullSync)); } @@ -244,7 +241,7 @@ public final class EdgeGrpcSession implements Closeable { @Override public void onFailure(Throwable t) { - String errorMsg = t.getMessage() != null ? t.getMessage() : ""; + String errorMsg = EdgeUtils.createErrorMsgFromRootCauseAndStackTrace(t); UplinkResponseMsg uplinkResponseMsg = UplinkResponseMsg.newBuilder() .setUplinkMsgId(uplinkMsg.getUplinkMsgId()) .setSuccess(false).setErrorMsg(errorMsg).build(); @@ -265,10 +262,7 @@ public final class EdgeGrpcSession implements Closeable { } if (sessionState.getPendingMsgsMap().isEmpty()) { log.debug("[{}] Pending msgs map is empty. Stopping current iteration", edge.getRoutingKey()); - if (sessionState.getScheduledSendDownlinkTask() != null) { - sessionState.getScheduledSendDownlinkTask().cancel(false); - } - sessionState.getSendDownlinkMsgsFuture().set(null); + stopCurrentSendDownlinkMsgsTask(null); } } catch (Exception e) { log.error("[{}] Can't process downlink response message [{}]", this.sessionId, msg, e); @@ -391,15 +385,14 @@ public final class EdgeGrpcSession implements Closeable { } private ListenableFuture sendDownlinkMsgsPack(List downlinkMsgsPack) { - if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { - String errorMsg = "[" + this.sessionId + "] Previous send downlink future was not properly completed, stopping it now"; - log.error(errorMsg); - sessionState.getSendDownlinkMsgsFuture().setException(new RuntimeException(errorMsg)); - } + interruptPreviousSendDownlinkMsgsTask(); + sessionState.setSendDownlinkMsgsFuture(SettableFuture.create()); sessionState.getPendingMsgsMap().clear(); + downlinkMsgsPack.forEach(msg -> sessionState.getPendingMsgsMap().put(msg.getDownlinkMsgId(), msg)); scheduleDownlinkMsgsPackSend(1); + return sessionState.getSendDownlinkMsgsFuture(); } @@ -422,13 +415,13 @@ public final class EdgeGrpcSession implements Closeable { } 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); + stopCurrentSendDownlinkMsgsTask(null); } } else { - sessionState.getSendDownlinkMsgsFuture().set(null); + stopCurrentSendDownlinkMsgsTask(null); } } catch (Exception e) { - sessionState.getSendDownlinkMsgsFuture().setException(e); + stopCurrentSendDownlinkMsgsTask(e); } }; @@ -611,7 +604,7 @@ public final class EdgeGrpcSession implements Closeable { } if (uplinkMsg.getDeviceRpcCallMsgCount() > 0) { for (DeviceRpcCallMsg deviceRpcCallMsg : uplinkMsg.getDeviceRpcCallMsgList()) { - result.add(ctx.getDeviceProcessor().processDeviceRpcCallResponseFromEdge(edge.getTenantId(), deviceRpcCallMsg)); + result.add(ctx.getDeviceProcessor().processDeviceRpcCallFromEdge(edge.getTenantId(), edge, deviceRpcCallMsg)); } } if (uplinkMsg.getWidgetBundleTypesRequestMsgCount() > 0) { @@ -673,4 +666,29 @@ public final class EdgeGrpcSession implements Closeable { log.debug("[{}] Failed to close output stream: {}", sessionId, e.getMessage()); } } + + private void interruptPreviousSendDownlinkMsgsTask() { + String msg = String.format("[%s] Previous send downlink future was not properly completed, stopping it now!", this.sessionId); + stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg)); + } + + private void interruptGeneralProcessingOnSync(TenantId tenantId, EdgeId edgeId) { + String msg = String.format("[%s][%s] Sync process started. General processing interrupted!", tenantId, edgeId); + stopCurrentSendDownlinkMsgsTask(new RuntimeException(msg)); + } + + public void stopCurrentSendDownlinkMsgsTask(Exception e) { + if (sessionState.getSendDownlinkMsgsFuture() != null && !sessionState.getSendDownlinkMsgsFuture().isDone()) { + if (e != null) { + log.warn(e.getMessage(), e); + sessionState.getSendDownlinkMsgsFuture().setException(e); + } else { + sessionState.getSendDownlinkMsgsFuture().set(null); + } + } + if (sessionState.getScheduledSendDownlinkTask() != null) { + sessionState.getScheduledSendDownlinkTask().cancel(true); + } + } + } diff --git a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java index 98673c9cf5..5a3d8dc0f8 100644 --- a/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java +++ b/application/src/main/java/org/thingsboard/server/service/edge/rpc/EdgeSessionState.java @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.SettableFuture; import lombok.Data; import org.thingsboard.server.gen.edge.v1.DownlinkMsg; +import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import java.util.concurrent.ScheduledFuture; @@ -26,7 +27,7 @@ import java.util.concurrent.ScheduledFuture; @Data public class EdgeSessionState { - private final Map pendingMsgsMap = new LinkedHashMap<>(); + private final Map pendingMsgsMap = Collections.synchronizedMap(new LinkedHashMap<>()); private SettableFuture sendDownlinkMsgsFuture; private ScheduledFuture scheduledSendDownlinkTask; } 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 511910dd8a..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; @@ -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/processor/DeviceEdgeProcessor.java b/application/src/main/java/org/thingsboard/server/service/edge/rpc/processor/DeviceEdgeProcessor.java index e09e036953..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 @@ -52,6 +52,7 @@ 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.gen.edge.v1.DeviceCredentialsRequestMsg; import org.thingsboard.server.gen.edge.v1.DeviceCredentialsUpdateMsg; @@ -325,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())); @@ -357,6 +367,46 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { return futureToSet; } + 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; @@ -413,11 +463,9 @@ public class DeviceEdgeProcessor extends BaseEdgeProcessor { private DownlinkMsg convertRpcCallEventToDownlink(EdgeEvent edgeEvent) { log.trace("Executing convertRpcCallEventToDownlink, edgeEvent [{}]", edgeEvent); - DeviceRpcCallMsg deviceRpcCallMsg = - deviceMsgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody()); return DownlinkMsg.newBuilder() .setDownlinkMsgId(EdgeUtils.nextPositiveInt()) - .addDeviceRpcCallMsg(deviceRpcCallMsg) + .addDeviceRpcCallMsg(deviceMsgConstructor.constructDeviceRpcCallMsg(edgeEvent.getEntityId(), edgeEvent.getBody())) .build(); } 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 722b33b426..f4d713b40f 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 @@ -287,12 +287,8 @@ public class TelemetryEdgeProcessor extends BaseEdgeProcessor { List attributeNames = attributeDeleteMsg.getAttributeNamesList(); attributesService.removeAll(tenantId, entityId, scope, attributeNames); if (EntityType.DEVICE.name().equals(entityType)) { - Set attributeKeys = new HashSet<>(); - for (String attributeName : attributeNames) { - attributeKeys.add(new AttributeKey(scope, attributeName)); - } tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete( - tenantId, (DeviceId) entityId, attributeKeys), new TbQueueCallback() { + tenantId, (DeviceId) entityId, scope, attributeNames), new TbQueueCallback() { @Override public void onSuccess(TbQueueMsgMetadata metadata) { futureToSet.set(null); 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 118dfe3b58..22edb2a01f 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,33 +22,50 @@ 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; import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; 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, asset.getCustomerId(), actionType, user); + tbClusterService.broadcastEntityStateChangeEvent(tenantId, savedAsset.getId(), + asset.getId() == null ? ComponentLifecycleEvent.CREATED : ComponentLifecycleEvent.UPDATED); return savedAsset; } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), asset, actionType, user, e); @@ -65,7 +82,7 @@ public class DefaultTbAssetService extends AbstractTbEntityService implements Tb assetService.deleteAsset(tenantId, assetId); notificationEntityService.notifyDeleteEntity(tenantId, assetId, asset, asset.getCustomerId(), ActionType.DELETED, relatedEdgeIds, user, assetId.toString()); - + tbClusterService.broadcastEntityStateChangeEvent(tenantId, assetId, ComponentLifecycleEvent.DELETED); return removeAlarmsByEntityId(tenantId, assetId); } catch (Exception e) { notificationEntityService.logEntityAction(tenantId, emptyId(EntityType.ASSET), ActionType.DELETED, user, e, 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/DefaultSystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java index 78e03ffe1a..9507912c39 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/DefaultSystemDataLoaderService.java @@ -82,6 +82,7 @@ import org.thingsboard.server.common.data.tenant.profile.DefaultTenantProfileCon import org.thingsboard.server.common.data.tenant.profile.TenantProfileData; import org.thingsboard.server.common.data.tenant.profile.TenantProfileQueueConfiguration; import org.thingsboard.server.common.data.widget.WidgetsBundle; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.dao.attributes.AttributesService; import org.thingsboard.server.dao.customer.CustomerService; import org.thingsboard.server.dao.device.DeviceCredentialsService; @@ -167,6 +168,9 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { @Autowired private QueueService queueService; + @Autowired + private JwtSettingsService jwtSettingsService; + @Bean protected BCryptPasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); @@ -263,6 +267,16 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, mailSettings); } + @Override + public void createRandomJwtSettings() throws Exception { + jwtSettingsService.createRandomJwtSettings(); + } + + @Override + public void saveLegacyYmlSettings() throws Exception { + jwtSettingsService.saveLegacyYmlSettings(); + } + @Override public void createOAuth2Templates() throws Exception { installScripts.createOAuth2Templates(); @@ -656,4 +670,5 @@ public class DefaultSystemDataLoaderService implements SystemDataLoaderService { queueService.saveQueue(sequentialByOriginatorQueue); } } + } 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/SystemDataLoaderService.java b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java index 1ceb1be289..041351f5e4 100644 --- a/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java +++ b/application/src/main/java/org/thingsboard/server/service/install/SystemDataLoaderService.java @@ -23,6 +23,10 @@ public interface SystemDataLoaderService { void createAdminSettings() throws Exception; + void createRandomJwtSettings() throws Exception; + + void saveLegacyYmlSettings() throws Exception; + void createOAuth2Templates() throws Exception; void loadSystemWidgets() throws Exception; 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..367bb049e5 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,15 @@ 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; + case "3.4.2": + log.info("Clearing cache to upgrade from version 3.4.2 to 3.4.3 ..."); + 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..ee34bd7f23 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,22 @@ public class DefaultDataUpdateService implements DataUpdateService { } break; case "3.4.1": + log.info("Updating data from version 3.4.1 to 3.4.2 ..."); + systemDataLoaderService.saveLegacyYmlSettings(); 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/ota/DefaultOtaPackageStateService.java b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java index 6a4cca181f..17ea56de2f 100644 --- a/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java +++ b/application/src/main/java/org/thingsboard/server/service/ota/DefaultOtaPackageStateService.java @@ -181,10 +181,11 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { private void update(TenantId tenantId, DeviceProfile deviceProfile, OtaPackageType otaPackageType) { Consumer updateConsumer; + OtaPackageId packageId = OtaPackageUtil.getOtaPackageId(deviceProfile, otaPackageType); - if (deviceProfile.getFirmwareId() != null) { + if (packageId != null) { long ts = System.currentTimeMillis(); - updateConsumer = d -> send(d.getTenantId(), d.getId(), deviceProfile.getFirmwareId(), ts, otaPackageType); + updateConsumer = d -> send(d.getTenantId(), d.getId(), packageId, ts, otaPackageType); } else { updateConsumer = d -> remove(d, otaPackageType); } @@ -360,9 +361,7 @@ public class DefaultOtaPackageStateService implements OtaPackageStateService { @Override public void onSuccess(@Nullable Void tmp) { log.trace("[{}] Success remove target {} attributes!", device.getId(), otaPackageType); - Set keysToNotify = new HashSet<>(); - attributesKeys.forEach(key -> keysToNotify.add(new AttributeKey(DataConstants.SHARED_SCOPE, key))); - tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), keysToNotify), null); + tbClusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(device.getTenantId(), device.getId(), DataConstants.SHARED_SCOPE, attributesKeys), null); } @Override diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java index 7983a0adcd..e2779ba28b 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbClusterService.java @@ -88,10 +88,8 @@ public class DefaultTbClusterService implements TbClusterService { @Value("${cluster.stats.enabled:false}") private boolean statsEnabled; - @Value("${edges.enabled}") + @Value("${edges.enabled:true}") protected boolean edgesEnabled; - @Value("${service.type:monolith}") - private String serviceType; private final AtomicInteger toCoreMsgs = new AtomicInteger(0); private final AtomicInteger toCoreNfs = new AtomicInteger(0); @@ -584,25 +582,27 @@ public class DefaultTbClusterService implements TbClusterService { } private void doSendQueueNotifications(ToRuleEngineNotificationMsg ruleEngineMsg, ToCoreNotificationMsg coreMsg, ToTransportMsg transportMsg) { - Set tbRuleEngineServices = partitionService.getAllServices(ServiceType.TB_RULE_ENGINE); - for (TransportProtos.ServiceInfo ruleEngineService : tbRuleEngineServices) { - TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngineService.getServiceId()); + Set tbRuleEngineServices = partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE); + Set tbCoreServices = partitionService.getAllServiceIds(ServiceType.TB_CORE); + Set tbTransportServices = partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT); + // No need to push notifications twice + tbTransportServices.removeAll(tbCoreServices); + tbCoreServices.removeAll(tbRuleEngineServices); + + for (String ruleEngineServiceId : tbRuleEngineServices) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngineServiceId); producerProvider.getRuleEngineNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), ruleEngineMsg), null); toRuleEngineNfs.incrementAndGet(); } - if (!serviceType.equals("monolith")) { - Set tbCoreServices = partitionService.getAllServices(ServiceType.TB_CORE); - for (TransportProtos.ServiceInfo coreService : tbCoreServices) { - TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, coreService.getServiceId()); - producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), coreMsg), null); - toCoreNfs.incrementAndGet(); - } - Set tbTransportServices = partitionService.getAllServices(ServiceType.TB_TRANSPORT); - for (TransportProtos.ServiceInfo transportService : tbTransportServices) { - TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportService.getServiceId()); - producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null); - toTransportNfs.incrementAndGet(); - } + for (String coreServiceId : tbCoreServices) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, coreServiceId); + producerProvider.getTbCoreNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), coreMsg), null); + toCoreNfs.incrementAndGet(); + } + for (String transportServiceId : tbTransportServices) { + TopicPartitionInfo tpi = notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transportServiceId); + producerProvider.getTransportNotificationsMsgProducer().send(tpi, new TbProtoQueueMsg<>(UUID.randomUUID(), transportMsg), null); + toTransportNfs.incrementAndGet(); } } } diff --git a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java index 518197f690..d71613cab4 100644 --- a/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java +++ b/application/src/main/java/org/thingsboard/server/service/queue/DefaultTbCoreConsumerService.java @@ -38,6 +38,7 @@ import org.thingsboard.server.common.msg.queue.ServiceType; import org.thingsboard.server.common.msg.queue.TbCallback; import org.thingsboard.server.common.msg.rpc.FromDeviceRpcResponse; import org.thingsboard.server.common.stats.StatsFactory; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.queue.util.DataDecodingEncodingService; import org.thingsboard.server.dao.tenant.TbTenantProfileCache; import org.thingsboard.server.gen.transport.TransportProtos; @@ -152,8 +153,9 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService jwtSettingsService, NotificationSchedulerService notificationSchedulerService) { - super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer()); + super(actorContext, encodingService, tenantProfileCache, deviceProfileCache, assetProfileCache, apiUsageStateService, partitionService, tbCoreQueueFactory.createToCoreNotificationsMsgConsumer(), jwtSettingsService); this.mainConsumer = tbCoreQueueFactory.createToCoreMsgConsumer(); this.usageStatsConsumer = tbCoreQueueFactory.createToUsageStatsServiceMsgConsumer(); this.firmwareStatesConsumer = tbCoreQueueFactory.createToOtaPackageStateServiceMsgConsumer(); @@ -520,7 +522,7 @@ public class DefaultTbCoreConsumerService extends AbstractConsumerService> nfConsumer; + protected final Optional jwtSettingsService; + public AbstractConsumerService(ActorSystemContext actorContext, DataDecodingEncodingService encodingService, TbTenantProfileCache tenantProfileCache, TbDeviceProfileCache deviceProfileCache, TbAssetProfileCache assetProfileCache, TbApiUsageStateService apiUsageStateService, - PartitionService partitionService, TbQueueConsumer> nfConsumer) { + PartitionService partitionService, TbQueueConsumer> nfConsumer, Optional jwtSettingsService) { this.actorContext = actorContext; this.encodingService = encodingService; this.tenantProfileCache = tenantProfileCache; @@ -89,6 +92,7 @@ public abstract class AbstractConsumerService> 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/script/RuleNodeMvelScriptEngine.java b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java similarity index 95% rename from application/src/main/java/org/thingsboard/server/service/script/RuleNodeMvelScriptEngine.java rename to application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java index 6ff08ce9b9..08b2d5e245 100644 --- a/application/src/main/java/org/thingsboard/server/service/script/RuleNodeMvelScriptEngine.java +++ b/application/src/main/java/org/thingsboard/server/service/script/RuleNodeTbelScriptEngine.java @@ -23,7 +23,7 @@ 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.script.api.tbel.TbelInvokeService; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.common.msg.TbMsg; @@ -42,9 +42,9 @@ import java.util.stream.Collectors; @Slf4j -public class RuleNodeMvelScriptEngine extends RuleNodeScriptEngine { +public class RuleNodeTbelScriptEngine extends RuleNodeScriptEngine { - public RuleNodeMvelScriptEngine(TenantId tenantId, MvelInvokeService scriptInvokeService, String script, String... argNames) { + public RuleNodeTbelScriptEngine(TenantId tenantId, TbelInvokeService scriptInvokeService, String script, String... argNames) { super(tenantId, scriptInvokeService, script, argNames); } @@ -64,7 +64,7 @@ public class RuleNodeMvelScriptEngine extends RuleNodeScriptEngine res = new ArrayList<>(); for (Object resObject : (Collection) result) { if (resObject instanceof Map) { - res.add(unbindMsg((Map) result, msg)); + res.add(unbindMsg((Map) resObject, msg)); } else { return wrongResultType(resObject); } 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/settings/DefaultJwtSettingsService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java new file mode 100644 index 0000000000..36beaecea3 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsService.java @@ -0,0 +1,162 @@ +/** + * 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.settings; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; +import org.springframework.beans.factory.annotation.Value; +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.AdminSettings; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent; +import org.thingsboard.server.common.data.security.model.JwtSettings; +import org.thingsboard.server.dao.settings.AdminSettingsService; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Objects; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +@Slf4j +public class DefaultJwtSettingsService implements JwtSettingsService { + + @Lazy + private final AdminSettingsService adminSettingsService; + @Lazy + private final Optional tbClusterService; + private final JwtSettingsValidator jwtSettingsValidator; + + @Value("${security.jwt.tokenExpirationTime:9000}") + private Integer tokenExpirationTime; + @Value("${security.jwt.refreshTokenExpTime:604800}") + private Integer refreshTokenExpTime; + @Value("${security.jwt.tokenIssuer:thingsboard.io}") + private String tokenIssuer; + @Value("${security.jwt.tokenSigningKey:thingsboardDefaultSigningKey}") + private String tokenSigningKey; + + private volatile JwtSettings jwtSettings = null; //lazy init + + /** + * Create JWT admin settings is intended to be called from Install scripts only + */ + @Override + public void createRandomJwtSettings() { + if (getJwtSettingsFromDb() == null) { + log.info("Creating JWT admin settings..."); + this.jwtSettings = getJwtSettingsFromYml(); + if (isSigningKeyDefault(jwtSettings)) { + this.jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString( + RandomStringUtils.randomAlphanumeric(64).getBytes(StandardCharsets.UTF_8))); + } + saveJwtSettings(jwtSettings); + } else { + log.info("Skip creating JWT admin settings because they already exist."); + } + } + + /** + * Create JWT admin settings is intended to be called from Upgrade scripts only + */ + @Override + public void saveLegacyYmlSettings() { + log.info("Saving legacy JWT admin settings from YML..."); + if (getJwtSettingsFromDb() == null) { + saveJwtSettings(getJwtSettingsFromYml()); + } + } + + @Override + public JwtSettings saveJwtSettings(JwtSettings jwtSettings) { + jwtSettingsValidator.validate(jwtSettings); + final AdminSettings adminJwtSettings = mapJwtToAdminSettings(jwtSettings); + final AdminSettings existedSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY); + if (existedSettings != null) { + adminJwtSettings.setId(existedSettings.getId()); + } + + log.info("Saving new JWT admin settings. From this moment, the JWT parameters from YAML and ENV will be ignored"); + adminSettingsService.saveAdminSettings(TenantId.SYS_TENANT_ID, adminJwtSettings); + + tbClusterService.ifPresent(cs -> cs.broadcastEntityStateChangeEvent(TenantId.SYS_TENANT_ID, TenantId.SYS_TENANT_ID, ComponentLifecycleEvent.UPDATED)); + return reloadJwtSettings(); + } + + @Override + public JwtSettings reloadJwtSettings() { + return getJwtSettings(true); + } + + @Override + public JwtSettings getJwtSettings() { + return getJwtSettings(false); + } + + public JwtSettings getJwtSettings(boolean forceReload) { + if (this.jwtSettings == null || forceReload) { + synchronized (this) { + if (this.jwtSettings == null || forceReload) { + JwtSettings result = getJwtSettingsFromDb(); + if (result == null) { + result = getJwtSettingsFromYml(); + log.warn("Loading the JWT settings from YML since there are no settings in DB. Looks like the upgrade script was not applied."); + } + if (isSigningKeyDefault(result)) { + log.warn("WARNING: The platform is configured to use default JWT Signing Key. " + + "This is a security issue that needs to be resolved. Please change the JWT Signing Key using the Web UI. " + + "Navigate to \"System settings -> Security settings\" while logged in as a System Administrator."); + } + this.jwtSettings = result; + } + } + } + return this.jwtSettings; + } + + private JwtSettings getJwtSettingsFromYml() { + return new JwtSettings(this.tokenExpirationTime, this.refreshTokenExpTime, this.tokenIssuer, this.tokenSigningKey); + } + + private JwtSettings getJwtSettingsFromDb() { + AdminSettings adminJwtSettings = adminSettingsService.findAdminSettingsByKey(TenantId.SYS_TENANT_ID, ADMIN_SETTINGS_JWT_KEY); + return adminJwtSettings != null ? mapAdminToJwtSettings(adminJwtSettings) : null; + } + + private JwtSettings mapAdminToJwtSettings(AdminSettings adminSettings) { + Objects.requireNonNull(adminSettings, "adminSettings for JWT is null"); + return JacksonUtil.treeToValue(adminSettings.getJsonValue(), JwtSettings.class); + } + + private AdminSettings mapJwtToAdminSettings(JwtSettings jwtSettings) { + Objects.requireNonNull(jwtSettings, "jwtSettings is null"); + AdminSettings adminJwtSettings = new AdminSettings(); + adminJwtSettings.setTenantId(TenantId.SYS_TENANT_ID); + adminJwtSettings.setKey(ADMIN_SETTINGS_JWT_KEY); + adminJwtSettings.setJsonValue(JacksonUtil.valueToTree(jwtSettings)); + return adminJwtSettings; + } + + private boolean isSigningKeyDefault(JwtSettings settings) { + return TOKEN_SIGNING_KEY_DEFAULT.equals(settings.getTokenSigningKey()); + } + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java new file mode 100644 index 0000000000..bf83179b78 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/DefaultJwtSettingsValidator.java @@ -0,0 +1,69 @@ +/** + * 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.settings; + +import lombok.RequiredArgsConstructor; +import org.apache.commons.lang3.RandomUtils; +import org.apache.commons.lang3.StringUtils; +import org.bouncycastle.util.Arrays; +import org.springframework.stereotype.Component; +import org.thingsboard.server.common.data.security.model.JwtSettings; +import org.thingsboard.server.dao.exception.DataValidationException; + +import java.util.Base64; +import java.util.Optional; +import java.util.concurrent.TimeUnit; + +@Component +@RequiredArgsConstructor +public class DefaultJwtSettingsValidator implements JwtSettingsValidator { + + @Override + public void validate(JwtSettings jwtSettings) { + if (StringUtils.isEmpty(jwtSettings.getTokenIssuer())) { + throw new DataValidationException("JWT token issuer should be specified!"); + } + if (Optional.ofNullable(jwtSettings.getRefreshTokenExpTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(15)) { + throw new DataValidationException("JWT refresh token expiration time should be at least 15 minutes!"); + } + if (Optional.ofNullable(jwtSettings.getTokenExpirationTime()).orElse(0) <= TimeUnit.MINUTES.toSeconds(1)) { + throw new DataValidationException("JWT token expiration time should be at least 1 minute!"); + } + if (jwtSettings.getTokenExpirationTime() >= jwtSettings.getRefreshTokenExpTime()) { + throw new DataValidationException("JWT token expiration time should greater than JWT refresh token expiration time!"); + } + if (StringUtils.isEmpty(jwtSettings.getTokenSigningKey())) { + throw new DataValidationException("JWT token signing key should be specified!"); + } + + byte[] decodedKey; + try { + decodedKey = Base64.getDecoder().decode(jwtSettings.getTokenSigningKey()); + } catch (Exception e) { + throw new DataValidationException("JWT token signing key should be a valid Base64 encoded string! " + e.getMessage()); + } + + if (Arrays.isNullOrEmpty(decodedKey)) { + throw new DataValidationException("JWT token signing key should be non-empty after Base64 decoding!"); + } + if (decodedKey.length * Byte.SIZE < 256 && !JwtSettingsService.TOKEN_SIGNING_KEY_DEFAULT.equals(jwtSettings.getTokenSigningKey())) { + throw new DataValidationException("JWT token signing key should be a Base64 encoded string representing at least 256 bits of data!"); + } + + System.arraycopy(decodedKey, 0, RandomUtils.nextBytes(decodedKey.length), 0, decodedKey.length); //secure memory + } + +} 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/settings/InstallJwtSettingsValidator.java similarity index 50% rename from application/src/main/java/org/thingsboard/server/service/security/auth/jwt/RefreshTokenRepository.java rename to application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/InstallJwtSettingsValidator.java index c5a666d464..6202e60c96 100644 --- 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/settings/InstallJwtSettingsValidator.java @@ -13,26 +13,27 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.security.auth.jwt; +package org.thingsboard.server.service.security.auth.jwt.settings; -import org.springframework.beans.factory.annotation.Autowired; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Primary; +import org.springframework.context.annotation.Profile; 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; +import org.thingsboard.server.common.data.security.model.JwtSettings; -@Component -public class RefreshTokenRepository { +/** + * During Install or upgrade the validation is suppressed to keep existing data + * */ - private final JwtTokenFactory tokenFactory; +@Primary +@Profile("install") +@Component +@RequiredArgsConstructor +public class InstallJwtSettingsValidator implements JwtSettingsValidator { - @Autowired - public RefreshTokenRepository(final JwtTokenFactory tokenFactory) { - this.tokenFactory = tokenFactory; - } + @Override + public void validate(JwtSettings jwtSettings) { - public JwtToken requestRefreshToken(SecurityUser user) { - return tokenFactory.createRefreshToken(user); } } diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.java new file mode 100644 index 0000000000..bf02be573c --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsService.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.server.service.security.auth.jwt.settings; + +import org.thingsboard.server.common.data.security.model.JwtSettings; + +public interface JwtSettingsService { + + String ADMIN_SETTINGS_JWT_KEY = "jwt"; + String TOKEN_SIGNING_KEY_DEFAULT = "thingsboardDefaultSigningKey"; + + JwtSettings getJwtSettings(); + + JwtSettings reloadJwtSettings(); + + void createRandomJwtSettings(); + + void saveLegacyYmlSettings(); + + JwtSettings saveJwtSettings(JwtSettings jwtSettings); + +} diff --git a/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java new file mode 100644 index 0000000000..06a82fc808 --- /dev/null +++ b/application/src/main/java/org/thingsboard/server/service/security/auth/jwt/settings/JwtSettingsValidator.java @@ -0,0 +1,23 @@ +/** + * 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.settings; + +import org.thingsboard.server.common.data.security.model.JwtSettings; + +public interface JwtSettingsValidator { + + void validate(JwtSettings jwtSettings); +} 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/Oauth2AuthenticationSuccessHandler.java b/application/src/main/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandler.java index bb90e65122..7de6c3fe65 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,14 +25,15 @@ 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.common.data.security.model.JwtPair; +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; @@ -54,7 +55,6 @@ import static org.thingsboard.server.service.security.auth.oauth2.HttpCookieOAut 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; @@ -63,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; @@ -106,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); - clearAuthenticationAttributes(request, response); - getRedirectStrategy().sendRedirect(request, response, baseUrl + "/?accessToken=" + accessToken.getToken() + "&refreshToken=" + refreshToken.getToken()); + + JwtPair tokenPair = tokenFactory.createTokenPair(securityUser); + getRedirectStrategy().sendRedirect(request, response, getRedirectUrl(baseUrl, tokenPair)); + 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); @@ -130,4 +128,13 @@ public class Oauth2AuthenticationSuccessHandler extends SimpleUrlAuthenticationS super.clearAuthenticationAttributes(request); httpCookieOAuth2AuthorizationRequestRepository.removeAuthorizationRequestCookies(request, response); } + + String getRedirectUrl(String baseUrl, JwtPair tokenPair) { + if (baseUrl.indexOf("?") > 0) { + baseUrl += "&"; + } else { + baseUrl += "/?"; + } + return baseUrl + "accessToken=" + tokenPair.getToken() + "&refreshToken=" + tokenPair.getRefreshToken(); + } } 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..f6d9fd8666 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,9 +25,8 @@ 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.common.data.security.model.JwtPair; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.token.JwtTokenFactory; @@ -45,13 +44,12 @@ 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, Authentication authentication) throws IOException, ServletException { SecurityUser securityUser = (SecurityUser) authentication.getPrincipal(); - JwtTokenPair tokenPair = new JwtTokenPair(); + JwtPair tokenPair = new JwtPair(); if (authentication instanceof MfaAuthenticationToken) { int preVerificationTokenLifetime = twoFaConfigManager.getPlatformTwoFaSettings(securityUser.getTenantId(), true) @@ -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..71fd66231a 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 @@ -24,8 +24,8 @@ import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.GrantedAuthority; import org.springframework.stereotype.Component; @@ -35,9 +35,9 @@ 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.common.data.security.model.JwtToken; -import org.thingsboard.server.config.JwtSettings; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.exception.JwtExpiredTokenException; -import org.thingsboard.server.service.security.model.JwtTokenPair; +import org.thingsboard.server.common.data.security.model.JwtPair; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; @@ -49,6 +49,7 @@ import java.util.UUID; import java.util.stream.Collectors; @Component +@RequiredArgsConstructor @Slf4j public class JwtTokenFactory { @@ -60,13 +61,9 @@ 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; - - @Autowired - public JwtTokenFactory(JwtSettings settings) { - this.settings = settings; - } + private final JwtSettingsService jwtSettingsService; /** * Factory method for issuing new JWT Tokens. @@ -79,7 +76,7 @@ public class JwtTokenFactory { UserPrincipal principal = securityUser.getUserPrincipal(); JwtBuilder jwtBuilder = setUpToken(securityUser, securityUser.getAuthorities().stream() - .map(GrantedAuthority::getAuthority).collect(Collectors.toList()), settings.getTokenExpirationTime()); + .map(GrantedAuthority::getAuthority).collect(Collectors.toList()), jwtSettingsService.getJwtSettings().getTokenExpirationTime()); jwtBuilder.claim(FIRST_NAME, securityUser.getFirstName()) .claim(LAST_NAME, securityUser.getLastName()) .claim(ENABLED, securityUser.isEnabled()) @@ -119,6 +116,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) { @@ -138,7 +138,7 @@ public class JwtTokenFactory { public JwtToken createRefreshToken(SecurityUser securityUser) { UserPrincipal principal = securityUser.getUserPrincipal(); - String token = setUpToken(securityUser, Collections.singletonList(Authority.REFRESH_TOKEN.name()), settings.getRefreshTokenExpTime()) + String token = setUpToken(securityUser, Collections.singletonList(Authority.REFRESH_TOKEN.name()), jwtSettingsService.getJwtSettings().getRefreshTokenExpTime()) .claim(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID) .setId(UUID.randomUUID().toString()).compact(); @@ -161,6 +161,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,35 +186,38 @@ 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(); return Jwts.builder() .setClaims(claims) - .setIssuer(settings.getTokenIssuer()) + .setIssuer(jwtSettingsService.getJwtSettings().getTokenIssuer()) .setIssuedAt(Date.from(currentTime.toInstant())) .setExpiration(Date.from(currentTime.plusSeconds(expirationTime).toInstant())) - .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()); + .signWith(SignatureAlgorithm.HS512, jwtSettingsService.getJwtSettings().getTokenSigningKey()); } public Jws parseTokenClaims(JwtToken token) { try { return Jwts.parser() - .setSigningKey(settings.getTokenSigningKey()) + .setSigningKey(jwtSettingsService.getJwtSettings().getTokenSigningKey()) .parseClaimsJws(token.getToken()); - } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException | SignatureException ex) { + } catch (UnsupportedJwtException | MalformedJwtException | IllegalArgumentException ex) { log.debug("Invalid JWT Token", ex); throw new BadCredentialsException("Invalid JWT token: ", ex); - } catch (ExpiredJwtException expiredEx) { + } catch (SignatureException | ExpiredJwtException expiredEx) { log.debug("JWT Token is expired", expiredEx); throw new JwtExpiredTokenException(token, "JWT Token expired", expiredEx); } } - public JwtTokenPair createTokenPair(SecurityUser securityUser) { + public JwtPair createTokenPair(SecurityUser securityUser) { JwtToken accessToken = createAccessJwtToken(securityUser); JwtToken refreshToken = createRefreshToken(securityUser); - return new JwtTokenPair(accessToken.getToken(), refreshToken.getToken()); + return new JwtPair(accessToken.getToken(), refreshToken.getToken()); } } 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/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 keys, TbCallback callback) { + public void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, TbCallback callback) { onLocalTelemetrySubUpdate(entityId, s -> { if (TbSubscriptionType.ATTRIBUTES.equals(s.getType())) { @@ -397,7 +397,13 @@ public class DefaultSubscriptionManagerService extends TbApplicationEventListene return subscriptionUpdate; }, false); if (entityId.getEntityType() == EntityType.DEVICE) { - deleteDeviceInactivityTimeout(tenantId, entityId, keys); + if (TbAttributeSubscriptionScope.SERVER_SCOPE.name().equalsIgnoreCase(scope) + || TbAttributeSubscriptionScope.ANY_SCOPE.name().equalsIgnoreCase(scope)) { + deleteDeviceInactivityTimeout(tenantId, entityId, keys); + } else if (TbAttributeSubscriptionScope.SHARED_SCOPE.name().equalsIgnoreCase(scope) && notifyDevice) { + clusterService.pushMsgToCore(DeviceAttributesEventNotificationMsg.onDelete(tenantId, + new DeviceId(entityId.getId()), scope, keys), null); + } } callback.onSuccess(); } diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java index 4661730f9c..bb0693669a 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/DefaultTbEntityDataSubscriptionService.java @@ -21,6 +21,7 @@ import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.MoreExecutors; import lombok.Getter; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.ArrayUtils; import org.checkerframework.checker.nullness.qual.Nullable; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -561,8 +562,18 @@ public class DefaultTbEntityDataSubscriptionService implements TbEntityDataSubsc if (queryResults != null) { for (ReadTsKvQueryResult queryResult : queryResults) { String queryKey = queriesKeys.get(queryResult.getQueryId()); - entityData.getTimeseries().put(queryKey, queryResult.toTsValues()); - lastTsMap.put(queryKey, queryResult.getLastEntryTs()); + if (queryKey != null) { + TsValue[] tsValues = entityData.getTimeseries().get(queryKey); + if (tsValues == null) { + tsValues = queryResult.toTsValues(); + } else { + tsValues = ArrayUtils.addAll(tsValues, queryResult.toTsValues()); + } + entityData.getTimeseries().put(queryKey, tsValues); + } else { + log.warn("ReadTsKvQueryResult for {} {} has queryId not matching the initial query", + entityData.getEntityId().getEntityType(), entityData.getEntityId()); + } } } // Populate with empty values if no data found. diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java index 8079bdbcce..e94e683075 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/SubscriptionManagerService.java @@ -41,7 +41,7 @@ public interface SubscriptionManagerService extends ApplicationListener attributes, boolean notifyDevice, TbCallback callback); - void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys, TbCallback empty); + void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, TbCallback empty); void onTimeSeriesDelete(TenantId tenantId, EntityId entityId, List keys, TbCallback callback); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java index 11fa6b4c35..67525060bf 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbAbstractDataSubCtx.java @@ -18,6 +18,7 @@ package org.thingsboard.server.service.subscription; import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.AbstractDataQuery; import org.thingsboard.server.common.data.query.EntityData; @@ -197,6 +198,9 @@ public abstract class TbAbstractDataSubCtx { long ts = Arrays.stream(v).map(TsValue::getTs).max(Long::compareTo).orElse(0L); log.trace("[{}][{}] Updating key: {} with ts: {}", serviceId, cmdId, k, ts); + if (!Aggregation.NONE.equals(getCurrentAggregation()) && ts < endTs) { + ts = endTs; + } keyStates.put(k, ts); }); } @@ -247,4 +251,5 @@ public abstract class TbAbstractDataSubCtx { } } + @Override + protected Aggregation getCurrentAggregation() { + return Aggregation.NONE; + } + private void sendWsMsg(String sessionId, AlarmSubscriptionUpdate subscriptionUpdate) { Alarm alarm = subscriptionUpdate.getAlarm(); AlarmId alarmId = alarm.getId(); diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java index f5c701916d..36ecf97a76 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbEntityDataSubCtx.java @@ -19,6 +19,7 @@ import lombok.Getter; import lombok.Setter; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.kv.Aggregation; import org.thingsboard.server.common.data.page.PageData; import org.thingsboard.server.common.data.query.EntityData; import org.thingsboard.server.common.data.query.EntityDataQuery; @@ -86,6 +87,11 @@ public class TbEntityDataSubCtx extends TbAbstractDataSubCtx { } } + @Override + protected Aggregation getCurrentAggregation() { + return (this.curTsCmd == null || this.curTsCmd.getAgg() == null) ? Aggregation.NONE : this.curTsCmd.getAgg(); + } + private void sendLatestWsMsg(EntityId entityId, String sessionId, TelemetrySubscriptionUpdate subscriptionUpdate, EntityKeyType keyType) { Map latestUpdate = new HashMap<>(); subscriptionUpdate.getData().forEach((k, v) -> { diff --git a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java index d35f7eb275..8412a75c62 100644 --- a/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java +++ b/application/src/main/java/org/thingsboard/server/service/subscription/TbSubscriptionUtils.java @@ -276,7 +276,7 @@ public class TbSubscriptionUtils { return ToCoreMsg.newBuilder().setToSubscriptionMgrMsg(msgBuilder.build()).build(); } - public static ToCoreMsg toAttributesDeleteProto(TenantId tenantId, EntityId entityId, String scope, List keys) { + public static ToCoreMsg toAttributesDeleteProto(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice) { TbAttributeDeleteProto.Builder builder = TbAttributeDeleteProto.newBuilder(); builder.setEntityType(entityId.getEntityType().name()); builder.setEntityIdMSB(entityId.getId().getMostSignificantBits()); @@ -285,6 +285,7 @@ public class TbSubscriptionUtils { builder.setTenantIdLSB(tenantId.getId().getLeastSignificantBits()); builder.setScope(scope); builder.addAllKeys(keys); + builder.setNotifyDevice(notifyDevice); SubscriptionMgrMsgProto.Builder msgBuilder = SubscriptionMgrMsgProto.newBuilder(); msgBuilder.setAttrDelete(builder); 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 eb84c3b40f..3b8a72a21a 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 @@ -126,6 +126,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 a2bdb4c1b0..76a9582bd2 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 @@ -269,14 +269,20 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer @Override public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback) { checkInternalEntity(entityId); - deleteAndNotifyInternal(tenantId, entityId, scope, keys, callback); + deleteAndNotifyInternal(tenantId, entityId, scope, keys, false, callback); } @Override - public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback) { + public void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback) { + checkInternalEntity(entityId); + deleteAndNotifyInternal(tenantId, entityId, scope, keys, notifyDevice, callback); + } + + @Override + public void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback) { ListenableFuture> deleteFuture = attrService.removeAll(tenantId, entityId, scope, keys); addVoidCallback(deleteFuture, callback); - addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys)); + addWsCallback(deleteFuture, success -> onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice)); } @Override @@ -374,11 +380,11 @@ public class DefaultTelemetrySubscriptionService extends AbstractSubscriptionSer }); } - private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys) { + private void onAttributesDelete(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice) { forwardToSubscriptionManagerService(tenantId, entityId, subscriptionManagerService -> { - subscriptionManagerService.onAttributesDelete(tenantId, entityId, scope, keys, TbCallback.EMPTY); + subscriptionManagerService.onAttributesDelete(tenantId, entityId, scope, keys, notifyDevice, TbCallback.EMPTY); }, () -> { - return TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys); + return TbSubscriptionUtils.toAttributesDeleteProto(tenantId, entityId, scope, keys, notifyDevice); }); } diff --git a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java index c35864ee92..13aa5e1536 100644 --- a/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java +++ b/application/src/main/java/org/thingsboard/server/service/telemetry/InternalTelemetryService.java @@ -37,7 +37,7 @@ public interface InternalTelemetryService extends RuleEngineTelemetryService { void saveLatestAndNotifyInternal(TenantId tenantId, EntityId entityId, List ts, FutureCallback callback); - void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback); + void deleteAndNotifyInternal(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback); void deleteLatestInternal(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback); 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 d0d1baf9f3..31cf413a48 100644 --- a/application/src/main/resources/thingsboard.yml +++ b/application/src/main/resources/thingsboard.yml @@ -107,11 +107,11 @@ plugins: # Security parameters security: # JWT Token parameters - jwt: + jwt: # Since 3.4.2 values are persisted to the database during install or upgrade. On Install, the key will be generated randomly if no custom value set. You can change it later from Web UI under SYS_ADMIN 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}" + tokenSigningKey: "${JWT_TOKEN_SIGNING_KEY:thingsboardDefaultSigningKey}" # Base64 encoded # Enable/disable access to Tenant Administrators JWT token by System Administrator or Customer Users JWT token by Tenant Administrator user_token_access_enabled: "${SECURITY_USER_TOKEN_ACCESS_ENABLED:true}" # Enable/disable case-sensitive username login @@ -148,7 +148,7 @@ ui: # Help parameters help: # Base url for UI help assets - base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.1}" + base-url: "${UI_HELP_BASE_URL:https://raw.githubusercontent.com/thingsboard/thingsboard-ui-help/release-3.4.2}" database: ts_max_intervals: "${DATABASE_TS_MAX_INTERVALS:700}" # Max number of DB queries generated by single API call to fetch telemetry records @@ -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: @@ -219,6 +227,7 @@ cassandra: default_fetch_size: "${CASSANDRA_DEFAULT_FETCH_SIZE:2000}" # Specify partitioning size for timestamp key-value storage. Example: MINUTES, HOURS, DAYS, MONTHS, INDEFINITE ts_key_value_partitioning: "${TS_KV_PARTITIONING:MONTHS}" + use_ts_key_value_partitioning_on_read: "${USE_TS_KV_PARTITIONING_ON_READ:true}" ts_key_value_partitions_max_cache_size: "${TS_KV_PARTITIONS_MAX_CACHE_SIZE:100000}" ts_key_value_ttl: "${TS_KV_TTL:0}" buffer_size: "${CASSANDRA_QUERY_BUFFER_SIZE:200000}" @@ -265,6 +274,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 notifications: @@ -422,8 +432,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}" @@ -535,6 +546,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 @@ -601,24 +613,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}" +tbel: + enabled: "${TBEL_ENABLED:true}" + max_total_args_size: "${TBEL_MAX_TOTAL_ARGS_SIZE:100000}" + max_result_size: "${TBEL_MAX_RESULT_SIZE:300000}" + max_script_body_size: "${TBEL_MAX_SCRIPT_BODY_SIZE:50000}" + # Maximum allowed TBEL script execution memory + max_memory_limit_mb: "${TBEL_MAX_MEMORY_LIMIT_MB: 8}" + # Maximum allowed TBEL script execution errors before it will be blacklisted + max_errors: "${TBEL_MAX_ERRORS:3}" + # TBEL Eval max request timeout in milliseconds. 0 - no timeout + max_requests_timeout: "${TBEL_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}" + max_black_list_duration_sec: "${TBEL_MAX_BLACKLIST_DURATION_SEC:60}" # Specify thread pool size for javascript executor service - thread_pool_size: "${MVEL_THREAD_POOL_SIZE:50}" + thread_pool_size: "${TBEL_THREAD_POOL_SIZE:50}" + compiled_scripts_cache_size: "${TBEL_COMPILED_SCRIPTS_CACHE_SIZE:1000}" stats: - enabled: "${TB_MVEL_STATS_ENABLED:false}" - print_interval_ms: "${TB_MVEL_STATS_PRINT_INTERVAL_MS:10000}" + enabled: "${TB_TBEL_STATS_ENABLED:false}" + print_interval_ms: "${TB_TBEL_STATS_PRINT_INTERVAL_MS:10000}" js: evaluator: "${JS_EVALUATOR:local}" # local/remote @@ -919,7 +932,7 @@ edges: storage: max_read_records_count: "${EDGES_STORAGE_MAX_READ_RECORDS_COUNT:50}" no_read_records_sleep: "${EDGES_NO_READ_RECORDS_SLEEP:1000}" - sleep_between_batches: "${EDGES_SLEEP_BETWEEN_BATCHES:1000}" + sleep_between_batches: "${EDGES_SLEEP_BETWEEN_BATCHES:10000}" scheduler_pool_size: "${EDGES_SCHEDULER_POOL_SIZE:1}" send_scheduler_pool_size: "${EDGES_SEND_SCHEDULER_POOL_SIZE:1}" grpc_callback_thread_pool_size: "${EDGES_GRPC_CALLBACK_POOL_SIZE:1}" 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 a625fb9b1f..9ea269918e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractWebTest.java @@ -88,6 +88,7 @@ import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRequest; import org.thingsboard.server.service.security.auth.rest.LoginRequest; import java.io.IOException; +import java.lang.reflect.Field; import java.sql.SQLException; import java.util.ArrayList; import java.util.Arrays; @@ -234,7 +235,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 +337,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { Assert.assertNotNull(savedDifferentCustomer); differentCustomerId = savedDifferentCustomer.getId(); - logout(); + resetTokens(); } protected void deleteDifferentTenant() throws Exception { @@ -350,7 +351,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,7 +376,7 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { } protected void login(String username, String password) throws Exception { - logout(); + resetTokens(); JsonNode tokenInfo = readResponse(doPost("/api/auth/login", new LoginRequest(username, password)).andExpect(status().isOk()), JsonNode.class); validateAndSetJwtToken(tokenInfo, username); } @@ -411,12 +412,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); @@ -764,4 +769,10 @@ public abstract class AbstractWebTest extends AbstractInMemoryStorageTest { throw new AssertionError("Unexpected status " + mvcResult.getResponse().getStatus()); } + protected T getFieldValue(Object target, String fieldName) throws Exception { + Field field = target.getClass().getDeclaredField(fieldName); + field.setAccessible(true); + return (T) field.get(target); + } + } diff --git a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java index 81ecce8300..285d647a14 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAdminControllerTest.java @@ -17,14 +17,21 @@ package org.thingsboard.server.controller; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.RandomStringUtils; import org.junit.Test; import org.mockito.Mockito; import org.springframework.beans.factory.annotation.Autowired; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.MailService; import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.service.mail.DefaultMailService; +import java.nio.charset.StandardCharsets; +import java.util.Base64; + +import static org.assertj.core.api.Assertions.assertThat; import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -32,8 +39,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; - +@Slf4j public abstract class BaseAdminControllerTest extends AbstractControllerTest { + final JwtSettings defaultJwtSettings = new JwtSettings(9000, 604800, "thingsboard.io", "thingsboardDefaultSigningKey"); @Autowired MailService mailService; @@ -45,67 +53,67 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest { public void testFindAdminSettingsByKey() throws Exception { loginSysAdmin(); doGet("/api/admin/settings/general") - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.id", notNullValue())) - .andExpect(jsonPath("$.key", is("general"))) - .andExpect(jsonPath("$.jsonValue.baseUrl", is("http://localhost:8080"))); - + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", notNullValue())) + .andExpect(jsonPath("$.key", is("general"))) + .andExpect(jsonPath("$.jsonValue.baseUrl", is("http://localhost:8080"))); + doGet("/api/admin/settings/mail") - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.id", notNullValue())) - .andExpect(jsonPath("$.key", is("mail"))) - .andExpect(jsonPath("$.jsonValue.smtpProtocol", is("smtp"))) - .andExpect(jsonPath("$.jsonValue.smtpHost", is("localhost"))) - .andExpect(jsonPath("$.jsonValue.smtpPort", is("25"))); - + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.id", notNullValue())) + .andExpect(jsonPath("$.key", is("mail"))) + .andExpect(jsonPath("$.jsonValue.smtpProtocol", is("smtp"))) + .andExpect(jsonPath("$.jsonValue.smtpHost", is("localhost"))) + .andExpect(jsonPath("$.jsonValue.smtpPort", is("25"))); + doGet("/api/admin/settings/unknown") - .andExpect(status().isNotFound()); - + .andExpect(status().isNotFound()); + } - + @Test public void testSaveAdminSettings() throws Exception { loginSysAdmin(); - AdminSettings adminSettings = doGet("/api/admin/settings/general", AdminSettings.class); - + AdminSettings adminSettings = doGet("/api/admin/settings/general", AdminSettings.class); + JsonNode jsonValue = adminSettings.getJsonValue(); ((ObjectNode) jsonValue).put("baseUrl", "http://myhost.org"); adminSettings.setJsonValue(jsonValue); doPost("/api/admin/settings", adminSettings).andExpect(status().isOk()); - + doGet("/api/admin/settings/general") - .andExpect(status().isOk()) - .andExpect(content().contentType(contentType)) - .andExpect(jsonPath("$.jsonValue.baseUrl", is("http://myhost.org"))); - + .andExpect(status().isOk()) + .andExpect(content().contentType(contentType)) + .andExpect(jsonPath("$.jsonValue.baseUrl", is("http://myhost.org"))); + ((ObjectNode) jsonValue).put("baseUrl", "http://localhost:8080"); adminSettings.setJsonValue(jsonValue); - + doPost("/api/admin/settings", adminSettings) - .andExpect(status().isOk()); + .andExpect(status().isOk()); } @Test public void testSaveAdminSettingsWithEmptyKey() throws Exception { loginSysAdmin(); - AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); adminSettings.setKey(null); doPost("/api/admin/settings", adminSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("Key should be specified"))); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("Key should be specified"))); } - + @Test public void testChangeAdminSettingsKey() throws Exception { loginSysAdmin(); - AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); + AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); adminSettings.setKey("newKey"); doPost("/api/admin/settings", adminSettings) - .andExpect(status().isBadRequest()) - .andExpect(statusReason(containsString("is prohibited"))); + .andExpect(status().isBadRequest()) + .andExpect(statusReason(containsString("is prohibited"))); } @Test @@ -113,7 +121,7 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest { loginSysAdmin(); AdminSettings adminSettings = doGet("/api/admin/settings/mail", AdminSettings.class); doPost("/api/admin/settings/testMail", adminSettings) - .andExpect(status().isOk()); + .andExpect(status().isOk()); } @Test @@ -139,4 +147,48 @@ public abstract class BaseAdminControllerTest extends AbstractControllerTest { doPost("/api/admin/settings/testMail", adminSettings).andExpect(status().is5xxServerError()); Mockito.doNothing().when(mailService).sendTestMail(Mockito.any(), Mockito.any()); } + + void resetJwtSettingsToDefault() throws Exception { + loginSysAdmin(); + doPost("/api/admin/jwtSettings", defaultJwtSettings).andExpect(status().isOk()); // jwt test scenarios are always started from + loginTenantAdmin(); + } + + @Test + public void testGetAndSaveDefaultJwtSettings() throws Exception { + JwtSettings jwtSettings; + loginSysAdmin(); + + jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class); + assertThat(jwtSettings).isEqualTo(defaultJwtSettings); + + doPost("/api/admin/jwtSettings", jwtSettings).andExpect(status().isOk()); + + jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class); + assertThat(jwtSettings).isEqualTo(defaultJwtSettings); + + resetJwtSettingsToDefault(); + } + + @Test + public void testCreateJwtSettings() throws Exception { + loginSysAdmin(); + + JwtSettings jwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class); + assertThat(jwtSettings).isEqualTo(defaultJwtSettings); + + jwtSettings.setTokenSigningKey(Base64.getEncoder().encodeToString( + RandomStringUtils.randomAlphanumeric(256 / Byte.SIZE).getBytes(StandardCharsets.UTF_8))); + + doPost("/api/admin/jwtSettings", jwtSettings).andExpect(status().isOk()); + + doGet("/api/admin/jwtSettings").andExpect(status().isUnauthorized()); //the old JWT token does not work after signing key was changed! + + loginSysAdmin(); + JwtSettings newJwtSettings = doGet("/api/admin/jwtSettings", JwtSettings.class); + assertThat(jwtSettings).isEqualTo(newJwtSettings); + + resetJwtSettingsToDefault(); + } + } 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/BaseAuditLogControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java index c46f7d9c46..16ac33b16e 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseAuditLogControllerTest.java @@ -44,6 +44,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.reset; import static org.mockito.Mockito.verify; @@ -202,6 +203,21 @@ public abstract class BaseAuditLogControllerTest extends AbstractControllerTest }); } + @Test + public void whenSavingAuditLogAndPartitionSaveErrorOccurred_thenSaveAuditLogAnyway() throws Exception { + // creating partition bigger than sql.audit_logs.partition_size + partitioningRepository.createPartitionIfNotExists("audit_log", System.currentTimeMillis(), TimeUnit.DAYS.toMillis(7)); + List partitions = partitioningRepository.fetchPartitions("audit_log"); + assertThat(partitions).size().isOne(); + partitioningRepository.cleanupPartitionsCache("audit_log", System.currentTimeMillis(), 0); + + assertDoesNotThrow(() -> { + // expecting partition overlap error on partition save + createAuditLog(ActionType.LOGIN, tenantAdminUserId); + }); + assertThat(partitioningRepository.fetchPartitions("audit_log")).isEqualTo(partitions); + } + private AuditLog createAuditLog(ActionType actionType, EntityId entityId) { AuditLog auditLog = new AuditLog(); auditLog.setTenantId(tenantId); 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/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/BaseEntityQueryControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java index 8c692ef8a2..85d68a479d 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseEntityQueryControllerTest.java @@ -22,6 +22,7 @@ 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.common.util.JacksonUtil; import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; @@ -55,6 +56,7 @@ import java.util.List; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; public abstract class BaseEntityQueryControllerTest extends AbstractControllerTest { @@ -381,4 +383,24 @@ public abstract class BaseEntityQueryControllerTest extends AbstractControllerTe Assert.assertEquals("1" + i, alarmActiveTime); } } + + @Test + public void givenInvalidEntityDataPageLink_thenReturnError() throws Exception { + DeviceTypeFilter filter = new DeviceTypeFilter(); + filter.setDeviceType("default"); + filter.setDeviceNameFilter(""); + + String invalidSortProperty = "created(Time)"; + EntityDataSortOrder sortOrder = new EntityDataSortOrder( + new EntityKey(EntityKeyType.ENTITY_FIELD, invalidSortProperty), EntityDataSortOrder.Direction.ASC + ); + EntityDataPageLink pageLink = new EntityDataPageLink(10, 0, null, sortOrder); + List entityFields = Collections.singletonList(new EntityKey(EntityKeyType.ENTITY_FIELD, "name")); + List latestValues = Collections.singletonList(new EntityKey(EntityKeyType.ATTRIBUTE, "temperature")); + EntityDataQuery query = new EntityDataQuery(filter, pageLink, entityFields, latestValues, null); + + ResultActions result = doPost("/api/entitiesQuery/find", query).andExpect(status().isBadRequest()); + assertThat(getErrorMessage(result)).contains("Invalid").contains("sort property"); + } + } 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 1980337458..6429574e38 100644 --- a/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/BaseUserControllerTest.java @@ -28,6 +28,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Primary; import org.springframework.http.HttpHeaders; import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.web.servlet.ResultActions; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.Tenant; @@ -106,7 +107,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 +124,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"); @@ -218,7 +219,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 +245,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") @@ -729,6 +730,17 @@ public abstract class BaseUserControllerTest extends AbstractControllerTest { testEntityDaoWithRelationsTransactionalException(userDao, tenantId, userId, "/api/user/" + userId); } + @Test + public void givenInvalidPageLink_thenReturnError() throws Exception { + loginTenantAdmin(); + + String invalidSortProperty = "abc(abc)"; + + ResultActions result = doGet("/api/users?page={page}&pageSize={pageSize}&sortProperty={sortProperty}", 0, 100, invalidSortProperty) + .andExpect(status().isBadRequest()); + assertThat(getErrorMessage(result)).containsIgnoringCase("invalid sort property"); + } + private User createUser() throws Exception { loginSysAdmin(); String email = "tenant2@thingsboard.org"; diff --git a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java index 9839843b0c..6c7dfc3cd7 100644 --- a/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/TwoFactorAuthTest.java @@ -51,7 +51,7 @@ import org.thingsboard.server.dao.user.UserService; import org.thingsboard.server.service.security.auth.mfa.TwoFactorAuthService; import org.thingsboard.server.service.security.auth.mfa.config.TwoFaConfigManager; import org.thingsboard.server.service.security.auth.rest.LoginRequest; -import org.thingsboard.server.service.security.model.JwtTokenPair; +import org.thingsboard.server.common.data.security.model.JwtPair; import java.time.Duration; import java.util.Arrays; @@ -396,7 +396,7 @@ public abstract class TwoFactorAuthTest extends AbstractControllerTest { private void logInWithPreVerificationToken(String username, String password) throws Exception { LoginRequest loginRequest = new LoginRequest(username, password); - JwtTokenPair response = readResponse(doPost("/api/auth/login", loginRequest).andExpect(status().isOk()), JwtTokenPair.class); + JwtPair response = readResponse(doPost("/api/auth/login", loginRequest).andExpect(status().isOk()), JwtPair.class); assertThat(response.getToken()).isNotNull(); assertThat(response.getRefreshToken()).isNull(); assertThat(response.getScope()).isEqualTo(Authority.PRE_VERIFICATION_TOKEN); diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java index 392a6814d4..a3162ebc5f 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseDeviceEdgeTest.java @@ -439,9 +439,9 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { Assert.assertTrue(edgeImitator.waitForResponses()); Assert.assertTrue(edgeImitator.waitForMessages()); - AbstractMessage latestMessage = edgeImitator.getMessageFromTail(2); - Assert.assertTrue(latestMessage instanceof DeviceUpdateMsg); - DeviceUpdateMsg latestDeviceUpdateMsg = (DeviceUpdateMsg) latestMessage; + Optional deviceUpdateMsgOpt = edgeImitator.findMessageByType(DeviceUpdateMsg.class); + Assert.assertTrue(deviceUpdateMsgOpt.isPresent()); + DeviceUpdateMsg latestDeviceUpdateMsg = deviceUpdateMsgOpt.get(); Assert.assertNotEquals(deviceOnCloudName, latestDeviceUpdateMsg.getName()); Assert.assertEquals(deviceOnCloudName, latestDeviceUpdateMsg.getConflictName()); @@ -453,9 +453,9 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { Assert.assertNotNull(device); Assert.assertNotEquals(deviceOnCloudName, device.getName()); - latestMessage = edgeImitator.getLatestMessage(); - Assert.assertTrue(latestMessage instanceof DeviceCredentialsRequestMsg); - DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = (DeviceCredentialsRequestMsg) latestMessage; + Optional deviceCredentialsUpdateMsgOpt = edgeImitator.findMessageByType(DeviceCredentialsRequestMsg.class); + Assert.assertTrue(deviceCredentialsUpdateMsgOpt.isPresent()); + DeviceCredentialsRequestMsg latestDeviceCredentialsRequestMsg = deviceCredentialsUpdateMsgOpt.get(); Assert.assertEquals(uuid.getMostSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdMSB()); Assert.assertEquals(uuid.getLeastSignificantBits(), latestDeviceCredentialsRequestMsg.getDeviceIdLSB()); @@ -511,8 +511,11 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { 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); + 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()); @@ -522,6 +525,8 @@ abstract public class BaseDeviceEdgeTest extends AbstractEdgeTest { 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, diff --git a/application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java b/application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java index 25280de666..8930166396 100644 --- a/application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java +++ b/application/src/test/java/org/thingsboard/server/edge/BaseTelemetryEdgeTest.java @@ -34,7 +34,7 @@ abstract public class BaseTelemetryEdgeTest extends AbstractEdgeTest { @Test public void testTimeseriesWithFailures() throws Exception { - int numberOfTimeseriesToSend = 1000; + int numberOfTimeseriesToSend = 333; Device device = findDeviceByName("Edge Device 1"); 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 668f702a87..4fa3b5e723 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 @@ -365,11 +365,7 @@ public class EdgeImitator { } public AbstractMessage getLatestMessage() { - return getMessageFromTail(1); - } - - public AbstractMessage getMessageFromTail(int offset) { - return downlinkMsgs.get(downlinkMsgs.size() - offset); + return downlinkMsgs.get(downlinkMsgs.size() - 1); } public void ignoreType(Class type) { diff --git a/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java new file mode 100644 index 0000000000..27eeda0b5b --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/queue/DefaultTbClusterServiceTest.java @@ -0,0 +1,247 @@ +/** + * 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.queue; + +import com.google.common.collect.Sets; +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.junit4.SpringRunner; +import org.thingsboard.server.cluster.TbClusterService; +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.common.msg.queue.ServiceType; +import org.thingsboard.server.gen.transport.TransportProtos; +import org.thingsboard.server.queue.TbQueueProducer; +import org.thingsboard.server.queue.common.TbProtoQueueMsg; +import org.thingsboard.server.queue.discovery.NotificationsTopicService; +import org.thingsboard.server.queue.discovery.PartitionService; +import org.thingsboard.server.queue.provider.TbQueueProducerProvider; +import org.thingsboard.server.queue.util.DataDecodingEncodingService; +import org.thingsboard.server.service.gateway_device.GatewayNotificationsService; +import org.thingsboard.server.service.profile.TbAssetProfileCache; +import org.thingsboard.server.service.profile.TbDeviceProfileCache; + +import java.util.UUID; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.ArgumentMatchers.isNull; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@Slf4j +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = DefaultTbClusterService.class) +public class DefaultTbClusterServiceTest { + + public static final String MONOLITH = "monolith"; + + public static final String CORE = "core"; + + public static final String RULE_ENGINE = "rule_engine"; + + public static final String TRANSPORT = "transport"; + + @MockBean + protected DataDecodingEncodingService encodingService; + @MockBean + protected TbDeviceProfileCache deviceProfileCache; + @MockBean + protected TbAssetProfileCache assetProfileCache; + @MockBean + protected GatewayNotificationsService gatewayNotificationsService; + @MockBean + protected PartitionService partitionService; + @MockBean + protected TbQueueProducerProvider producerProvider; + + @SpyBean + protected NotificationsTopicService notificationsTopicService; + @SpyBean + protected TbClusterService clusterService; + + @Test + public void testOnQueueChangeSingleMonolith() { + when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(MONOLITH)); + when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(MONOLITH)); + when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(MONOLITH)); + + TbQueueProducer> tbQueueProducer = mock(TbQueueProducer.class); + + when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer); + + clusterService.onQueueChange(createTestQueue()); + + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH); + verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any()); + verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_TRANSPORT), any()); + + verify(tbQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH)), any(TbProtoQueueMsg.class), isNull()); + + verify(producerProvider, never()).getTbCoreNotificationsMsgProducer(); + verify(producerProvider, never()).getTransportNotificationsMsgProducer(); + } + + @Test + public void testOnQueueChangeMultipleMonoliths() { + String monolith1 = MONOLITH + 1; + String monolith2 = MONOLITH + 2; + when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(monolith1, monolith2)); + when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(monolith1, monolith2)); + when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(monolith1, monolith2)); + + TbQueueProducer> tbQueueProducer = mock(TbQueueProducer.class); + + when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbQueueProducer); + + clusterService.onQueueChange(createTestQueue()); + + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1); + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2); + verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any()); + verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_TRANSPORT), any()); + + verify(tbQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1)), any(TbProtoQueueMsg.class), isNull()); + verify(tbQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2)), any(TbProtoQueueMsg.class), isNull()); + + verify(producerProvider, never()).getTbCoreNotificationsMsgProducer(); + verify(producerProvider, never()).getTransportNotificationsMsgProducer(); + } + + @Test + public void testOnQueueChangeSingleMonolithAndSingleRemoteTransport() { + when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(MONOLITH)); + when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(MONOLITH)); + when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(MONOLITH, TRANSPORT)); + + TbQueueProducer> tbREQueueProducer = mock(TbQueueProducer.class); + TbQueueProducer> tbTransportQueueProducer = mock(TbQueueProducer.class); + + when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbREQueueProducer); + when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer); + + clusterService.onQueueChange(createTestQueue()); + + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH); + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, TRANSPORT); + verify(notificationsTopicService, never()).getNotificationsTopic(eq(ServiceType.TB_CORE), any()); + + verify(tbREQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, MONOLITH)), any(TbProtoQueueMsg.class), isNull()); + + verify(tbTransportQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, TRANSPORT)), any(TbProtoQueueMsg.class), isNull()); + verify(tbTransportQueueProducer, never()) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, MONOLITH)), any(TbProtoQueueMsg.class), isNull()); + + verify(tbTransportQueueProducer, never()) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, MONOLITH)), any(TbProtoQueueMsg.class), isNull()); + + verify(producerProvider, never()).getTbCoreNotificationsMsgProducer(); + } + + @Test + public void testOnQueueChangeMultipleMicroservices() { + String monolith1 = MONOLITH + 1; + String monolith2 = MONOLITH + 2; + + String core1 = CORE + 1; + String core2 = CORE + 2; + + String ruleEngine1 = RULE_ENGINE + 1; + String ruleEngine2 = RULE_ENGINE + 2; + + String transport1 = TRANSPORT + 1; + String transport2 = TRANSPORT + 2; + + when(partitionService.getAllServiceIds(ServiceType.TB_RULE_ENGINE)).thenReturn(Sets.newHashSet(monolith1, monolith2, ruleEngine1, ruleEngine2)); + when(partitionService.getAllServiceIds(ServiceType.TB_CORE)).thenReturn(Sets.newHashSet(monolith1, monolith2, core1, core2)); + when(partitionService.getAllServiceIds(ServiceType.TB_TRANSPORT)).thenReturn(Sets.newHashSet(monolith1, monolith2, transport1, transport2)); + + TbQueueProducer> tbREQueueProducer = mock(TbQueueProducer.class); + TbQueueProducer> tbCoreQueueProducer = mock(TbQueueProducer.class); + TbQueueProducer> tbTransportQueueProducer = mock(TbQueueProducer.class); + + when(producerProvider.getRuleEngineNotificationsMsgProducer()).thenReturn(tbREQueueProducer); + when(producerProvider.getTbCoreNotificationsMsgProducer()).thenReturn(tbCoreQueueProducer); + when(producerProvider.getTransportNotificationsMsgProducer()).thenReturn(tbTransportQueueProducer); + + clusterService.onQueueChange(createTestQueue()); + + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1); + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2); + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine1); + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine2); + + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_CORE, core1); + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_CORE, core2); + verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_CORE, monolith1); + verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_CORE, monolith2); + + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, transport1); + verify(notificationsTopicService, times(1)).getNotificationsTopic(ServiceType.TB_TRANSPORT, transport2); + verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith1); + verify(notificationsTopicService, never()).getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith2); + + verify(tbREQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith1)), any(TbProtoQueueMsg.class), isNull()); + verify(tbREQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, monolith2)), any(TbProtoQueueMsg.class), isNull()); + verify(tbREQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine1)), any(TbProtoQueueMsg.class), isNull()); + verify(tbREQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_RULE_ENGINE, ruleEngine2)), any(TbProtoQueueMsg.class), isNull()); + + verify(tbCoreQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, core1)), any(TbProtoQueueMsg.class), isNull()); + verify(tbCoreQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, core2)), any(TbProtoQueueMsg.class), isNull()); + verify(tbCoreQueueProducer, never()) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, monolith1)), any(TbProtoQueueMsg.class), isNull()); + verify(tbCoreQueueProducer, never()) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_CORE, monolith2)), any(TbProtoQueueMsg.class), isNull()); + + verify(tbTransportQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transport1)), any(TbProtoQueueMsg.class), isNull()); + verify(tbTransportQueueProducer, times(1)) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, transport2)), any(TbProtoQueueMsg.class), isNull()); + verify(tbTransportQueueProducer, never()) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith1)), any(TbProtoQueueMsg.class), isNull()); + verify(tbTransportQueueProducer, never()) + .send(eq(notificationsTopicService.getNotificationsTopic(ServiceType.TB_TRANSPORT, monolith2)), any(TbProtoQueueMsg.class), isNull()); + } + + protected Queue createTestQueue() { + TenantId tenantId = TenantId.SYS_TENANT_ID; + Queue queue = new Queue(new QueueId(UUID.randomUUID())); + queue.setTenantId(tenantId); + queue.setName("Main"); + queue.setTopic("main"); + queue.setPartitions(10); + return queue; + } +} \ No newline at end of file diff --git a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java index 6703252749..c303625f1d 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/NashornJsInvokeServiceTest.java @@ -70,7 +70,7 @@ class NashornJsInvokeServiceTest extends AbstractControllerTest { } long duration = System.currentTimeMillis() - startTs; System.out.println(iterations + " invocations took: " + duration + "ms"); - Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(3)); + Assert.assertTrue(duration < TimeUnit.MINUTES.toMillis(4)); } @Test diff --git a/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java similarity index 51% rename from application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java rename to application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java index a40a087514..7fa45984db 100644 --- a/application/src/test/java/org/thingsboard/server/service/script/MvelInvokeServiceTest.java +++ b/application/src/test/java/org/thingsboard/server/service/script/TbelInvokeServiceTest.java @@ -16,6 +16,7 @@ package org.thingsboard.server.service.script; import com.fasterxml.jackson.databind.node.ObjectNode; +import com.github.benmanes.caffeine.cache.Cache; import org.junit.Assert; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -23,31 +24,38 @@ 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.script.api.tbel.TbelInvokeService; +import org.thingsboard.script.api.tbel.TbelScript; import org.thingsboard.server.common.data.id.TenantId; import org.thingsboard.server.controller.AbstractControllerTest; import org.thingsboard.server.dao.service.DaoSqlTest; +import java.io.Serializable; +import java.util.ArrayList; +import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThat; 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", + "tbel.max_script_body_size=100", + "tbel.max_total_args_size=50", + "tbel.max_result_size=50", + "tbel.max_errors=2", + "tbel.compiled_scripts_cache_size=100" }) -class MvelInvokeServiceTest extends AbstractControllerTest { +class TbelInvokeServiceTest extends AbstractControllerTest { @Autowired - private MvelInvokeService invokeService; + private TbelInvokeService invokeService; - @Value("${mvel.max_errors}") + @Value("${tbel.max_errors}") private int maxJsErrors; @Test @@ -110,6 +118,89 @@ class MvelInvokeServiceTest extends AbstractControllerTest { assertThatScriptIsBlocked(scriptId); } + @Test + void givenScriptsWithSameBody_thenCompileAndCacheOnlyOnce() throws Exception { + String script = "return msg.temperature > 20;"; + List scriptsIds = new ArrayList<>(); + for (int i = 0; i < 100; i++) { + UUID scriptId = evalScript(script); + scriptsIds.add(scriptId); + } + + Map scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash"); + Map scriptMap = getFieldValue(invokeService, "scriptMap"); + Cache compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache"); + + String scriptHash = scriptIdToHash.get(scriptsIds.get(0)); + + assertThat(scriptsIds.stream().map(scriptIdToHash::get)).containsOnly(scriptHash); + assertThat(scriptMap).containsKey(scriptHash); + assertThat(compiledScriptsCache.getIfPresent(scriptHash)).isNotNull(); + } + + @Test + public void whenReleasingScript_thenCheckForScriptHashUsages() throws Exception { + String script = "return msg.temperature > 20;"; + List scriptsIds = new ArrayList<>(); + for (int i = 0; i < 10; i++) { + UUID scriptId = evalScript(script); + scriptsIds.add(scriptId); + } + + Map scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash"); + Map scriptMap = getFieldValue(invokeService, "scriptMap"); + Cache compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache"); + + String scriptHash = scriptIdToHash.get(scriptsIds.get(0)); + for (int i = 0; i < 9; i++) { + UUID scriptId = scriptsIds.get(i); + assertThat(scriptIdToHash).containsKey(scriptId); + invokeService.release(scriptId); + assertThat(scriptIdToHash).doesNotContainKey(scriptId); + } + assertThat(scriptMap).containsKey(scriptHash); + assertThat(compiledScriptsCache.getIfPresent(scriptHash)).isNotNull(); + + invokeService.release(scriptsIds.get(9)); + assertThat(scriptMap).doesNotContainKey(scriptHash); + assertThat(compiledScriptsCache.getIfPresent(scriptHash)).isNull(); + } + + @Test + public void whenCompiledScriptsCacheIsTooBig_thenRemoveRarelyUsedScripts() throws Exception { + Map scriptIdToHash = getFieldValue(invokeService, "scriptIdToHash"); + Cache compiledScriptsCache = getFieldValue(invokeService, "compiledScriptsCache"); + + List scriptsIds = new ArrayList<>(); + for (int i = 0; i < 110; i++) { // tbel.compiled_scripts_cache_size = 100 + String script = "return msg.temperature > " + i; + UUID scriptId = evalScript(script); + scriptsIds.add(scriptId); + + for (int j = 0; j < i; j++) { + invokeScript(scriptId, "{ \"temperature\": 12 }"); // so that scriptsIds is ordered by number of invocations + } + } + + ConcurrentMap cache = compiledScriptsCache.asMap(); + + for (int i = 0; i < 10; i++) { // iterating rarely used scripts + UUID scriptId = scriptsIds.get(i); + String scriptHash = scriptIdToHash.get(scriptId); + assertThat(cache).doesNotContainKey(scriptHash); + } + for (int i = 10; i < 110; i++) { + UUID scriptId = scriptsIds.get(i); + String scriptHash = scriptIdToHash.get(scriptId); + assertThat(cache).containsKey(scriptHash); + } + + UUID scriptRemovedFromCache = scriptsIds.get(0); + assertThat(compiledScriptsCache.getIfPresent(scriptIdToHash.get(scriptRemovedFromCache))).isNull(); + invokeScript(scriptRemovedFromCache, "{ \"temperature\": 12 }"); + assertThat(compiledScriptsCache.getIfPresent(scriptIdToHash.get(scriptRemovedFromCache))).isNotNull(); + } + private void assertThatScriptIsBlocked(UUID scriptId) { assertThatThrownBy(() -> { invokeScript(scriptId, "{}"); diff --git a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java index f865c9b5e0..bf89eadda5 100644 --- a/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/JwtTokenFactoryTest.java @@ -23,7 +23,8 @@ 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.common.data.security.model.JwtToken; -import org.thingsboard.server.config.JwtSettings; +import org.thingsboard.server.common.data.security.model.JwtSettings; +import org.thingsboard.server.service.security.auth.jwt.settings.JwtSettingsService; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; import org.thingsboard.server.service.security.model.token.AccessJwtToken; @@ -36,6 +37,8 @@ import java.util.UUID; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.BDDMockito.willReturn; +import static org.mockito.Mockito.mock; public class JwtTokenFactoryTest { @@ -50,7 +53,10 @@ public class JwtTokenFactoryTest { jwtSettings.setTokenExpirationTime((int) TimeUnit.HOURS.toSeconds(2)); jwtSettings.setRefreshTokenExpTime((int) TimeUnit.DAYS.toSeconds(7)); - tokenFactory = new JwtTokenFactory(jwtSettings); + JwtSettingsService jwtSettingsService = mock(JwtSettingsService.class); + willReturn(jwtSettings).given(jwtSettingsService).getJwtSettings(); + + tokenFactory = new JwtTokenFactory(jwtSettingsService); } @Test 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/security/auth/oauth2/Oauth2AuthenticationSuccessHandlerTest.java b/application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandlerTest.java new file mode 100644 index 0000000000..9b1e356b75 --- /dev/null +++ b/application/src/test/java/org/thingsboard/server/service/security/auth/oauth2/Oauth2AuthenticationSuccessHandlerTest.java @@ -0,0 +1,68 @@ +/** + * 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.oauth2; + +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.springframework.beans.factory.annotation.Autowired; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.controller.AbstractControllerTest; +import org.thingsboard.server.dao.service.DaoSqlTest; +import org.thingsboard.server.service.security.model.SecurityUser; +import org.thingsboard.server.service.security.model.token.JwtTokenFactory; + +import java.util.UUID; + +import static org.junit.Assert.assertEquals; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.when; + +@DaoSqlTest +public class Oauth2AuthenticationSuccessHandlerTest extends AbstractControllerTest { + + @Autowired + private Oauth2AuthenticationSuccessHandler oauth2AuthenticationSuccessHandler; + + @Mock + private JwtTokenFactory jwtTokenFactory; + + private SecurityUser securityUser; + + @Before + public void before() { + UserId userId = new UserId(UUID.randomUUID()); + securityUser = new SecurityUser(userId); + when(jwtTokenFactory.createTokenPair(eq(securityUser))).thenReturn(new JwtPair("testAccessToken", "testRefreshToken")); + } + + @Test + public void testGetRedirectUrl() { + JwtPair jwtPair = jwtTokenFactory.createTokenPair(securityUser); + + String urlWithoutParams = "http://localhost:8080/dashboardGroups/3fa13530-6597-11ed-bd76-8bd591f0ec3e"; + String urlWithParams = "http://localhost:8080/dashboardGroups/3fa13530-6597-11ed-bd76-8bd591f0ec3e?state=someState&page=1"; + + String redirectUrl = oauth2AuthenticationSuccessHandler.getRedirectUrl(urlWithoutParams, jwtPair); + String expectedUrl = urlWithoutParams + "/?accessToken=" + jwtPair.getToken() + "&refreshToken=" + jwtPair.getRefreshToken(); + assertEquals(expectedUrl, redirectUrl); + + redirectUrl = oauth2AuthenticationSuccessHandler.getRedirectUrl(urlWithParams, jwtPair); + expectedUrl = urlWithParams + "&accessToken=" + jwtPair.getToken() + "&refreshToken=" + jwtPair.getRefreshToken(); + assertEquals(expectedUrl, redirectUrl); + } +} \ No newline at end of file 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 index 61e2036b12..2f7efae923 100644 --- a/application/src/test/java/org/thingsboard/server/system/BaseRestApiLimitsTest.java +++ b/application/src/test/java/org/thingsboard/server/system/BaseRestApiLimitsTest.java @@ -63,15 +63,15 @@ public abstract class BaseRestApiLimitsTest extends AbstractControllerTest { public void before() throws Exception { loginSysAdmin(); tenantProfile = getDefaultTenantProfile(); - logout(); + resetTokens(); } @After public void after() throws Exception { - logout(); + resetTokens(); loginSysAdmin(); saveTenantProfileWitConfiguration(tenantProfile, new DefaultTenantProfileConfiguration()); - logout(); + resetTokens(); service.shutdown(); } @@ -85,7 +85,7 @@ public abstract class BaseRestApiLimitsTest extends AbstractControllerTest { saveTenantProfileWitConfiguration(tenantProfile, configurationWithCustomerRestLimits); - logout(); + resetTokens(); loginCustomerUser(); @@ -105,7 +105,7 @@ public abstract class BaseRestApiLimitsTest extends AbstractControllerTest { saveTenantProfileWitConfiguration(tenantProfile, configurationWithTenantRestLimits); - logout(); + resetTokens(); loginCustomerUser(); @@ -125,7 +125,7 @@ public abstract class BaseRestApiLimitsTest extends AbstractControllerTest { saveTenantProfileWitConfiguration(tenantProfile, configurationWithTenantRestLimits); - logout(); + resetTokens(); loginTenantAdmin(); 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/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/application/src/test/resources/logback-test.xml b/application/src/test/resources/logback-test.xml index d3301bf660..3762c8aa7c 100644 --- a/application/src/test/resources/logback-test.xml +++ b/application/src/test/resources/logback-test.xml @@ -16,6 +16,8 @@ + + diff --git a/common/actor/pom.xml b/common/actor/pom.xml index 268b8e698b..8f2c1e1b13 100644 --- a/common/actor/pom.xml +++ b/common/actor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/cache/pom.xml b/common/cache/pom.xml index e2b743574f..e48d232b16 100644 --- a/common/cache/pom.xml +++ b/common/cache/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common 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/cluster-api/pom.xml b/common/cluster-api/pom.xml index 3df2d5e8bc..a8dfc522ce 100644 --- a/common/cluster-api/pom.xml +++ b/common/cluster-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/cluster-api/src/main/proto/queue.proto b/common/cluster-api/src/main/proto/queue.proto index 05a8f41be1..29c847342e 100644 --- a/common/cluster-api/src/main/proto/queue.proto +++ b/common/cluster-api/src/main/proto/queue.proto @@ -630,6 +630,7 @@ message TbAttributeDeleteProto { int64 tenantIdLSB = 5; string scope = 6; repeated string keys = 7; + bool notifyDevice = 8; } message TbTimeSeriesDeleteProto { diff --git a/common/coap-server/pom.xml b/common/coap-server/pom.xml index 596aa90e98..57bb96d2c2 100644 --- a/common/coap-server/pom.xml +++ b/common/coap-server/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/dao-api/pom.xml b/common/dao-api/pom.xml index c75186c0a0..cba811343a 100644 --- a/common/dao-api/pom.xml +++ b/common/dao-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common 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/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 fd927c13d8..9a95c08b65 100644 --- a/common/data/pom.xml +++ b/common/data/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common 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..970fd6177e 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 @@ -28,6 +28,7 @@ public class DataConstants { public static final String CLIENT_SCOPE = "CLIENT_SCOPE"; public static final String SERVER_SCOPE = "SERVER_SCOPE"; public static final String SHARED_SCOPE = "SHARED_SCOPE"; + public static final String NOTIFY_DEVICE_METADATA_KEY = "notifyDevice"; public static final String LATEST_TS = "LATEST_TS"; public static final String IS_NEW_ALARM = "isNewAlarm"; public static final String IS_EXISTING_ALARM = "isExistingAlarm"; @@ -40,6 +41,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/EdgeUtils.java b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java index 44eff251c7..a60db2108d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/EdgeUtils.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data; import com.fasterxml.jackson.databind.JsonNode; +import com.google.common.base.Throwables; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.edge.EdgeEvent; import org.thingsboard.server.common.data.edge.EdgeEventActionType; @@ -29,6 +30,8 @@ import java.util.concurrent.ThreadLocalRandom; @Slf4j public final class EdgeUtils { + private static final int STACK_TRACE_LIMIT = 10; + private EdgeUtils() { } @@ -93,4 +96,20 @@ public final class EdgeUtils { edgeEvent.setBody(body); return edgeEvent; } + + public static String createErrorMsgFromRootCauseAndStackTrace(Throwable t) { + Throwable rootCause = Throwables.getRootCause(t); + StringBuilder errorMsg = new StringBuilder(rootCause.getMessage() != null ? rootCause.getMessage() : ""); + if (rootCause.getStackTrace().length > 0) { + int idx = 0; + for (StackTraceElement stackTraceElement : rootCause.getStackTrace()) { + errorMsg.append("\n").append(stackTraceElement.toString()); + idx++; + if (idx > STACK_TRACE_LIMIT) { + break; + } + } + } + return errorMsg.toString(); + } } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java index 3545b290bd..8d7fba5238 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/SearchTextBasedWithAdditionalInfo.java @@ -21,6 +21,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.extern.slf4j.Slf4j; import org.thingsboard.server.common.data.id.UUIDBased; +import org.thingsboard.server.common.data.validation.NoXss; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -36,6 +37,7 @@ import java.util.function.Consumer; public abstract class SearchTextBasedWithAdditionalInfo extends SearchTextBased implements HasAdditionalInfo { public static final ObjectMapper mapper = new ObjectMapper(); + @NoXss private transient JsonNode additionalInfo; @JsonIgnore private byte[] additionalInfoBytes; 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 cb97f56bcf..4fcf27df6e 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 @@ -165,6 +165,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/kv/BaseReadTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java index 29b98d5e83..49c2d60876 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseReadTsKvQuery.java @@ -47,4 +47,12 @@ public class BaseReadTsKvQuery extends BaseTsKvQuery implements ReadTsKvQuery { this(key, startTs, endTs, endTs - startTs, limit, Aggregation.NONE, order); } + public BaseReadTsKvQuery(ReadTsKvQuery query, long startTs, long endTs) { + super(query.getId(), query.getKey(), startTs, endTs); + this.interval = query.getInterval(); + this.limit = query.getLimit(); + this.aggregation = query.getAggregation(); + this.order = query.getOrder(); + } + } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java index 245af53d55..2842ee77c7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/kv/BaseTsKvQuery.java @@ -28,8 +28,12 @@ public class BaseTsKvQuery implements TsKvQuery { private final long endTs; public BaseTsKvQuery(String key, long startTs, long endTs) { - this.id = idSeq.get(); + this(idSeq.get(), key, startTs, endTs); idSeq.set(id + 1); + } + + protected BaseTsKvQuery(int id, String key, long startTs, long endTs) { + this.id = id; this.key = key; this.startTs = startTs; this.endTs = endTs; 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 index 3f8a2fae45..f26f10d5e8 100644 --- 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 @@ -16,5 +16,5 @@ package org.thingsboard.server.common.data.script; public enum ScriptLanguage { - JS, MVEL + JS, TBEL } 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/application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java similarity index 85% rename from application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java index 02e28cd885..eb50a11a92 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/JwtTokenPair.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtPair.java @@ -13,19 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.service.security.model; +package org.thingsboard.server.common.data.security.model; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; import org.thingsboard.server.common.data.security.Authority; -@ApiModel(value = "JWT Token Pair") +@ApiModel(value = "JWT Pair") @Data @NoArgsConstructor -public class JwtTokenPair { +public class JwtPair { @ApiModelProperty(position = 1, value = "The JWT Access Token. Used to perform API calls.", example = "AAB254FF67D..") private String token; @@ -34,7 +33,7 @@ public class JwtTokenPair { private Authority scope; - public JwtTokenPair(String token, String refreshToken) { + public JwtPair(String token, String refreshToken) { this.token = token; this.refreshToken = refreshToken; } diff --git a/application/src/main/java/org/thingsboard/server/config/JwtSettings.java b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtSettings.java similarity index 58% rename from application/src/main/java/org/thingsboard/server/config/JwtSettings.java rename to common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtSettings.java index 95e510612a..f5668ff088 100644 --- a/application/src/main/java/org/thingsboard/server/config/JwtSettings.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/security/model/JwtSettings.java @@ -13,35 +13,43 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.server.config; +package org.thingsboard.server.common.data.security.model; +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.AllArgsConstructor; import lombok.Data; -import org.springframework.boot.context.properties.ConfigurationProperties; -import org.springframework.stereotype.Component; -import org.thingsboard.server.common.data.security.model.JwtToken; +import lombok.NoArgsConstructor; -@Component -@ConfigurationProperties(prefix = "security.jwt") +@ApiModel(value = "JWT Settings") +@AllArgsConstructor +@NoArgsConstructor @Data public class JwtSettings { + /** * {@link JwtToken} will expire after this time. */ + @ApiModelProperty(position = 1, value = "The JWT will expire after seconds.", example = "9000") private Integer tokenExpirationTime; + /** + * {@link JwtToken} can be refreshed during this timeframe. + */ + @ApiModelProperty(position = 2, value = "The JWT can be refreshed during seconds.", example = "604800") + private Integer refreshTokenExpTime; + /** * Token issuer. */ + @ApiModelProperty(position = 3, value = "The JWT issuer.", example = "thingsboard.io") private String tokenIssuer; /** * Key is used to sign {@link JwtToken}. + * Base64 encoded */ + @ApiModelProperty(position = 4, value = "The JWT key is used to sing token. Base64 encoded.", example = "cTU4WnNqemI2aU5wbWVjdm1vYXRzanhjNHRUcXliMjE=") private String tokenSigningKey; - /** - * {@link JwtToken} can be refreshed during this timeframe. - */ - private Integer refreshTokenExpTime; - } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java index 8dccecbc29..7055dbaf2d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/JsonTbEntity.java @@ -16,6 +16,7 @@ package org.thingsboard.server.common.data.sync; import com.fasterxml.jackson.annotation.JacksonAnnotationsInside; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes.Type; import com.fasterxml.jackson.annotation.JsonTypeInfo; @@ -49,5 +50,6 @@ import java.lang.annotation.Target; @Type(name = "ENTITY_VIEW", value = EntityView.class), @Type(name = "WIDGETS_BUNDLE", value = WidgetsBundle.class) }) +@JsonIgnoreProperties(value = {"tenantId", "createdTime"}, ignoreUnknown = true) public @interface JsonTbEntity { } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java index afe6c925c7..5283894739 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/sync/ie/EntityExportData.java @@ -55,7 +55,6 @@ public class EntityExportData> { .comparing(AttributeExportData::getKey).thenComparing(AttributeExportData::getLastUpdateTs); @JsonProperty(index = 2) - @JsonIgnoreProperties({"tenantId", "createdTime"}) @JsonTbEntity private E entity; @JsonProperty(index = 1) 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 4d39377218..f8f8e5c104 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 @@ -32,6 +32,7 @@ public class RepositorySettings implements Serializable { private String privateKeyPassword; private String defaultBranch; private boolean readOnly; + private boolean showMergeCommits; public RepositorySettings() { } @@ -46,5 +47,6 @@ public class RepositorySettings implements Serializable { this.privateKeyPassword = settings.getPrivateKeyPassword(); this.defaultBranch = settings.getDefaultBranch(); this.readOnly = settings.isReadOnly(); + this.showMergeCommits = settings.isShowMergeCommits(); } } 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/edge-api/pom.xml b/common/edge-api/pom.xml index 576f2aeced..e09de1135f 100644 --- a/common/edge-api/pom.xml +++ b/common/edge-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/edge-api/src/main/proto/edge.proto b/common/edge-api/src/main/proto/edge.proto index a2e3b5989b..249f251238 100644 --- a/common/edge-api/src/main/proto/edge.proto +++ b/common/edge-api/src/main/proto/edge.proto @@ -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 { diff --git a/common/message/pom.xml b/common/message/pom.xml index de18be2e21..5176e4af1a 100644 --- a/common/message/pom.xml +++ b/common/message/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/pom.xml b/common/pom.xml index dcf4685dea..c7b6d86eaf 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard common diff --git a/common/queue/pom.xml b/common/queue/pom.xml index 1ab33a3593..8485c2d63d 100644 --- a/common/queue/pom.xml +++ b/common/queue/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/script/pom.xml b/common/script/pom.xml index 6f445fff76..a849cf2684 100644 --- a/common/script/pom.xml +++ b/common/script/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/script/remote-js-client/pom.xml b/common/script/remote-js-client/pom.xml index 3068901013..651eb153ed 100644 --- a/common/script/remote-js-client/pom.xml +++ b/common/script/remote-js-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT script org.thingsboard.common.script diff --git a/common/script/script-api/pom.xml b/common/script/script-api/pom.xml index 62dc00ca97..01fef3059b 100644 --- a/common/script/script-api/pom.xml +++ b/common/script/script-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT script org.thingsboard.common.script @@ -56,6 +56,10 @@ com.google.code.gson gson + + com.github.ben-manes.caffeine + caffeine + org.slf4j slf4j-api @@ -86,7 +90,7 @@ org.thingsboard - mvel2 + tbel org.springframework.boot 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 deleted file mode 100644 index ae38c13125..0000000000 --- a/common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbUtils.java +++ /dev/null @@ -1,167 +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.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/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/tbel/DefaultTbelInvokeService.java similarity index 55% rename from common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/DefaultMvelInvokeService.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/DefaultTbelInvokeService.java index 901a49180e..412a61848e 100644 --- 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/tbel/DefaultTbelInvokeService.java @@ -13,8 +13,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.script.api.mvel; +package org.thingsboard.script.api.tbel; +import com.github.benmanes.caffeine.cache.Cache; +import com.github.benmanes.caffeine.cache.Caffeine; +import com.google.common.hash.Hasher; +import com.google.common.hash.Hashing; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -23,8 +27,8 @@ import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; import org.mvel2.ExecutionContext; import org.mvel2.MVEL; +import org.mvel2.ParserContext; import org.mvel2.SandboxedParserConfiguration; -import org.mvel2.SandboxedParserContext; import org.mvel2.ScriptMemoryOverflowException; import org.mvel2.optimizers.OptimizerFactory; import org.springframework.beans.factory.annotation.Value; @@ -42,63 +46,73 @@ import org.thingsboard.server.common.stats.TbApiUsageStateClient; import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import java.io.Serializable; +import java.nio.charset.StandardCharsets; +import java.util.Calendar; import java.util.Collections; import java.util.Map; import java.util.Optional; +import java.util.Random; import java.util.UUID; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Executor; -import java.util.regex.Pattern; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; @Slf4j -@ConditionalOnProperty(prefix = "mvel", value = "enabled", havingValue = "true", matchIfMissing = true) +@ConditionalOnProperty(prefix = "tbel", value = "enabled", havingValue = "true", matchIfMissing = true) @Service -public class DefaultMvelInvokeService extends AbstractScriptInvokeService implements MvelInvokeService { +public class DefaultTbelInvokeService extends AbstractScriptInvokeService implements TbelInvokeService { - protected Map scriptMap = new ConcurrentHashMap<>(); - private SandboxedParserConfiguration parserConfig; + protected final Map scriptIdToHash = new ConcurrentHashMap<>(); + protected final Map scriptMap = new ConcurrentHashMap<>(); + protected Cache compiledScriptsCache; - private static final Pattern NEW_KEYWORD_PATTERN = Pattern.compile("new\\s"); + private SandboxedParserConfiguration parserConfig; @Getter - @Value("${mvel.max_total_args_size:100000}") + @Value("${tbel.max_total_args_size:100000}") private long maxTotalArgsSize; @Getter - @Value("${mvel.max_result_size:300000}") + @Value("${tbel.max_result_size:300000}") private long maxResultSize; @Getter - @Value("${mvel.max_script_body_size:50000}") + @Value("${tbel.max_script_body_size:50000}") private long maxScriptBodySize; @Getter - @Value("${mvel.max_errors:3}") + @Value("${tbel.max_errors:3}") private int maxErrors; @Getter - @Value("${mvel.max_black_list_duration_sec:60}") + @Value("${tbel.max_black_list_duration_sec:60}") private int maxBlackListDurationSec; @Getter - @Value("${mvel.max_requests_timeout:0}") + @Value("${tbel.max_requests_timeout:0}") private long maxInvokeRequestsTimeout; @Getter - @Value("${mvel.stats.enabled:false}") + @Value("${tbel.stats.enabled:false}") private boolean statsEnabled; - @Value("${mvel.thread_pool_size:50}") + @Value("${tbel.thread_pool_size:50}") private int threadPoolSize; - @Value("${mvel.max_memory_limit_mb:8}") + @Value("${tbel.max_memory_limit_mb:8}") private long maxMemoryLimitMb; + @Value("${tbel.compiled_scripts_cache_size:1000}") + private int compiledScriptsCacheSize; + private ListeningExecutorService executor; - protected DefaultMvelInvokeService(Optional apiUsageStateClient, Optional apiUsageReportClient) { + private final Lock lock = new ReentrantLock(); + + protected DefaultTbelInvokeService(Optional apiUsageStateClient, Optional apiUsageReportClient) { super(apiUsageStateClient, apiUsageReportClient); } - @Scheduled(fixedDelayString = "${mvel.stats.print_interval_ms:10000}") + @Scheduled(fixedDelayString = "${tbel.stats.print_interval_ms:10000}") public void printStats() { super.printStats(); } @@ -108,18 +122,23 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem public void init() { super.init(); OptimizerFactory.setDefaultOptimizer(OptimizerFactory.SAFE_REFLECTIVE); - parserConfig = new SandboxedParserConfiguration(); + parserConfig = ParserContext.enableSandboxedMode(); parserConfig.addImport("JSON", TbJson.class); parserConfig.registerDataType("Date", TbDate.class, date -> 8L); + parserConfig.registerDataType("Random", Random.class, date -> 8L); + parserConfig.registerDataType("Calendar", Calendar.class, date -> 8L); TbUtils.register(parserConfig); - executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(threadPoolSize, "mvel-executor")); + executor = MoreExecutors.listeningDecorator(ThingsBoardExecutors.newWorkStealingPool(threadPoolSize, "tbel-executor")); try { - // Special command to warm up MVEL engine - Serializable script = MVEL.compileExpression("var warmUp = {}; warmUp", new SandboxedParserContext(parserConfig)); + // Special command to warm up TBEL engine + Serializable script = compileScript("var warmUp = {}; warmUp"); MVEL.executeTbExpression(script, new ExecutionContext(parserConfig), Collections.emptyMap()); } catch (Exception e) { // do nothing } + compiledScriptsCache = Caffeine.newBuilder() + .maximumSize(compiledScriptsCacheSize) + .build(); } @PreDestroy @@ -131,7 +150,7 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem @Override protected String getStatsName() { - return "MVEL Scripts Stats"; + return "TBEL Scripts Stats"; } @Override @@ -141,16 +160,22 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem @Override protected boolean isScriptPresent(UUID scriptId) { - return scriptMap.containsKey(scriptId); + return scriptIdToHash.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); + String scriptHash = hash(scriptBody, argNames); + compiledScriptsCache.get(scriptHash, k -> compileScript(scriptBody)); + lock.lock(); + try { + scriptIdToHash.put(scriptId, scriptHash); + scriptMap.computeIfAbsent(scriptHash, k -> new TbelScript(scriptBody, argNames)); + } finally { + lock.unlock(); + } return scriptId; } catch (Exception e) { throw new TbScriptException(scriptId, TbScriptException.ErrorCode.COMPILATION, scriptBody, e); @@ -159,15 +184,17 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem } @Override - protected MvelScriptExecutionTask doInvokeFunction(UUID scriptId, Object[] args) { + protected TbelScriptExecutionTask 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) { + return new TbelScriptExecutionTask(executionContext, executor.submit(() -> { + String scriptHash = scriptIdToHash.get(scriptId); + if (scriptHash == null) { throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, null, new RuntimeException("Script not found!")); } + TbelScript script = scriptMap.get(scriptHash); + Serializable compiledScript = compiledScriptsCache.get(scriptHash, k -> compileScript(script.getScriptBody())); try { - return MVEL.executeTbExpression(script.getCompiledScript(), executionContext, script.createVars(args)); + return MVEL.executeTbExpression(compiledScript, executionContext, script.createVars(args)); } catch (ScriptMemoryOverflowException e) { throw new TbScriptException(scriptId, TbScriptException.ErrorCode.OTHER, script.getScriptBody(), new RuntimeException("Script memory overflow!")); } catch (Exception e) { @@ -177,7 +204,33 @@ public class DefaultMvelInvokeService extends AbstractScriptInvokeService implem } @Override - protected void doRelease(UUID scriptId) throws Exception { - scriptMap.remove(scriptId); + protected void doRelease(UUID scriptId) { + String scriptHash = scriptIdToHash.remove(scriptId); + if (scriptHash != null) { + lock.lock(); + try { + if (!scriptIdToHash.containsValue(scriptHash)) { + scriptMap.remove(scriptHash); + compiledScriptsCache.invalidate(scriptHash); + } + } finally { + lock.unlock(); + } + } } + + private Serializable compileScript(String scriptBody) { + return MVEL.compileExpression(scriptBody, new ParserContext()); + } + + @SuppressWarnings("UnstableApiUsage") + protected String hash(String scriptBody, String[] argNames) { + Hasher hasher = Hashing.murmur3_128().newHasher(); + hasher.putUnencodedChars(scriptBody); + for (String argName : argNames) { + hasher.putString(argName, StandardCharsets.UTF_8); + } + return hasher.hash().toString(); + } + } 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/tbel/TbDate.java similarity index 65% rename from common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbDate.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbDate.java index 9b58bd9357..b59be0ca0d 100644 --- 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/tbel/TbDate.java @@ -13,10 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.script.api.mvel; +package org.thingsboard.script.api.tbel; import java.text.DateFormat; import java.text.SimpleDateFormat; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.time.temporal.TemporalAccessor; import java.util.Date; import java.util.GregorianCalendar; import java.util.Locale; @@ -24,6 +32,11 @@ import java.util.TimeZone; public class TbDate extends Date { + private static final DateTimeFormatter isoDateFormatter = DateTimeFormatter.ofPattern( + "yyyy-MM-dd[[ ]['T']HH:mm[:ss[.SSS]][ ][XXX][Z][z][VV][O]]").withZone(ZoneId.systemDefault()); + + private static final DateFormat isoDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ"); + public TbDate() { super(); } @@ -60,8 +73,7 @@ public class TbDate extends Date { } public String toISOString() { - DateFormat formatter = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.sssZ"); - return formatter.format(this); + return isoDateFormat.format(this); } public String toLocaleString(String locale) { @@ -79,14 +91,32 @@ public class TbDate extends Date { return System.currentTimeMillis(); } - public static long parse(String value) { + public static long parse(String value, String format) { try { - return Date.parse(value); - } catch (IllegalArgumentException e) { + DateFormat dateFormat = new SimpleDateFormat(format); + return dateFormat.parse(value).getTime(); + } catch (Exception e) { return -1; } } + public static long parse(String value) { + try { + TemporalAccessor accessor = isoDateFormatter.parseBest(value, + ZonedDateTime::from, + LocalDateTime::from, + LocalDate::from); + Instant instant = Instant.from(accessor); + return Instant.EPOCH.until(instant, ChronoUnit.MILLIS); + } catch (Exception e) { + try { + return Date.parse(value); + } catch (IllegalArgumentException e2) { + 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/tbel/TbJson.java similarity index 98% rename from common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/TbJson.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbJson.java index 18f3abb4df..ddf40bbfde 100644 --- 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/tbel/TbJson.java @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.script.api.mvel; +package org.thingsboard.script.api.tbel; import com.fasterxml.jackson.databind.JsonNode; import org.mvel2.ExecutionContext; diff --git a/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.java new file mode 100644 index 0000000000..dc0d0a8b09 --- /dev/null +++ b/common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbUtils.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.script.api.tbel; + +import org.mvel2.ExecutionContext; +import org.mvel2.ParserConfiguration; +import org.mvel2.execution.ExecutionArrayList; +import org.mvel2.util.MethodStub; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.math.BigDecimal; +import java.math.RoundingMode; +import java.nio.ByteBuffer; +import java.nio.ByteOrder; +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.List; + +public class TbUtils { + + private static final byte[] HEX_ARRAY = "0123456789ABCDEF".getBytes(StandardCharsets.US_ASCII); + + 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("decodeToString", new MethodStub(TbUtils.class.getMethod("bytesToString", + List.class))); + parserConfig.addImport("decodeToJson", new MethodStub(TbUtils.class.getMethod("decodeToJson", + ExecutionContext.class, List.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))); + parserConfig.addImport("parseLittleEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseLittleEndianHexToInt", + String.class))); + parserConfig.addImport("parseBigEndianHexToInt", new MethodStub(TbUtils.class.getMethod("parseBigEndianHexToInt", + String.class))); + parserConfig.addImport("parseHexToInt", new MethodStub(TbUtils.class.getMethod("parseHexToInt", + String.class))); + parserConfig.addImport("parseHexToInt", new MethodStub(TbUtils.class.getMethod("parseHexToInt", + String.class, boolean.class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + List.class, int.class, int.class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + List.class, int.class, int.class, boolean.class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + byte[].class, int.class, int.class))); + parserConfig.addImport("parseBytesToInt", new MethodStub(TbUtils.class.getMethod("parseBytesToInt", + byte[].class, int.class, int.class, boolean.class))); + parserConfig.addImport("toFixed", new MethodStub(TbUtils.class.getMethod("toFixed", + double.class, int.class))); + parserConfig.addImport("hexToBytes", new MethodStub(TbUtils.class.getMethod("hexToBytes", + ExecutionContext.class, String.class))); + parserConfig.addImport("base64ToHex", new MethodStub(TbUtils.class.getMethod("base64ToHex", + String.class))); + parserConfig.addImport("base64ToBytes", new MethodStub(TbUtils.class.getMethod("base64ToBytes", + String.class))); + parserConfig.addImport("bytesToBase64", new MethodStub(TbUtils.class.getMethod("bytesToBase64", + byte[].class))); + parserConfig.addImport("bytesToHex", new MethodStub(TbUtils.class.getMethod("bytesToHex", + byte[].class))); + parserConfig.addImport("bytesToHex", new MethodStub(TbUtils.class.getMethod("bytesToHex", + ExecutionArrayList.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 Object decodeToJson(ExecutionContext ctx, List bytesList) throws IOException { + return TbJson.parse(ctx, bytesToString(bytesList)); + } + + 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 (byte aByte : bytes) { + list.add(aByte); + } + 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; + } + + public static int parseLittleEndianHexToInt(String hex) { + return parseHexToInt(hex, false); + } + + public static int parseBigEndianHexToInt(String hex) { + return parseHexToInt(hex, true); + } + + public static int parseHexToInt(String hex) { + return parseHexToInt(hex, true); + } + + public static int parseHexToInt(String hex, boolean bigEndian) { + int length = hex.length(); + if (length > 8) { + throw new IllegalArgumentException("Hex string is too large. Maximum 8 symbols allowed."); + } + if (length % 2 > 0) { + throw new IllegalArgumentException("Hex string must be even-length."); + } + byte[] data = new byte[length / 2]; + for (int i = 0; i < length; i += 2) { + data[i / 2] = (byte) ((Character.digit(hex.charAt(i), 16) << 4) + Character.digit(hex.charAt(i + 1), 16)); + } + return parseBytesToInt(data, 0, data.length, bigEndian); + } + + public static ExecutionArrayList hexToBytes(ExecutionContext ctx, String hex) { + int len = hex.length(); + if (len % 2 > 0) { + throw new IllegalArgumentException("Hex string must be even-length."); + } + ExecutionArrayList data = new ExecutionArrayList<>(ctx); + for (int i = 0; i < len; i += 2) { + data.add((byte)((Character.digit(hex.charAt(i), 16) << 4) + + Character.digit(hex.charAt(i + 1), 16))); + } + return data; + } + + public static String base64ToHex(String base64) { + return bytesToHex(Base64.getDecoder().decode(base64)); + } + + public static String bytesToBase64(byte[] bytes) { + return Base64.getEncoder().encodeToString(bytes); + } + + public static byte[] base64ToBytes(String input) { + return Base64.getDecoder().decode(input); + } + + public static int parseBytesToInt(List data, int offset, int length) { + return parseBytesToInt(data, offset, length, true); + } + + public static int parseBytesToInt(List data, int offset, int length, boolean bigEndian) { + final byte[] bytes = new byte[data.size()]; + for (int i = 0; i < bytes.length; i++) { + bytes[i] = data.get(i); + } + return parseBytesToInt(bytes, offset, length, bigEndian); + } + + public static int parseBytesToInt(byte[] data, int offset, int length) { + return parseBytesToInt(data, offset, length, true); + } + + public static int parseBytesToInt(byte[] data, int offset, int length, boolean bigEndian) { + if (offset > data.length) { + throw new IllegalArgumentException("Offset: " + offset + " is out of bounds for array with length: " + data.length + "!"); + } + if (length > 4) { + throw new IllegalArgumentException("Length: " + length + " is too large. Maximum 4 bytes is allowed!"); + } + if (offset + length > data.length) { + throw new IllegalArgumentException("Offset: " + offset + " and Length: " + length + " is out of bounds for array with length: " + data.length + "!"); + } + var bb = ByteBuffer.allocate(4); + if (!bigEndian) { + bb.order(ByteOrder.LITTLE_ENDIAN); + } + bb.position(bigEndian ? 4 - length : 0); + bb.put(data, offset, length); + bb.position(0); + return bb.getInt(); + } + + public static String bytesToHex(ExecutionArrayList bytesList) { + byte[] bytes = new byte[bytesList.size()]; + for (int i = 0; i < bytesList.size(); i++) { + bytes[i] = Byte.parseByte(bytesList.get(i).toString()); + } + return bytesToHex(bytes); + } + + public static String bytesToHex(byte[] bytes) { + byte[] hexChars = new byte[bytes.length * 2]; + for (int j = 0; j < bytes.length; j++) { + int v = bytes[j] & 0xFF; + hexChars[j * 2] = HEX_ARRAY[v >>> 4]; + hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F]; + } + return new String(hexChars, StandardCharsets.UTF_8); + } + + public static double toFixed(double value, int precision) { + return BigDecimal.valueOf(value).setScale(precision, RoundingMode.HALF_UP).doubleValue(); + } + + 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/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/tbel/TbelInvokeService.java similarity index 85% rename from common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelInvokeService.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelInvokeService.java index cd5678018a..8986d87236 100644 --- 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/tbel/TbelInvokeService.java @@ -13,16 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.script.api.mvel; +package org.thingsboard.script.api.tbel; import org.thingsboard.script.api.ScriptInvokeService; import org.thingsboard.server.common.data.script.ScriptLanguage; -public interface MvelInvokeService extends ScriptInvokeService { +public interface TbelInvokeService extends ScriptInvokeService { @Override default ScriptLanguage getLanguage() { - return ScriptLanguage.MVEL; + return ScriptLanguage.TBEL; } } 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/tbel/TbelScript.java similarity index 88% rename from common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScript.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelScript.java index 7a84c7b0af..92604dcd39 100644 --- 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/tbel/TbelScript.java @@ -13,18 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.script.api.mvel; +package org.thingsboard.script.api.tbel; import lombok.Data; -import java.io.Serializable; import java.util.HashMap; import java.util.Map; @Data -public class MvelScript { +public class TbelScript { - private final Serializable compiledScript; private final String scriptBody; private final String[] argNames; 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/tbel/TbelScriptExecutionTask.java similarity index 82% rename from common/script/script-api/src/main/java/org/thingsboard/script/api/mvel/MvelScriptExecutionTask.java rename to common/script/script-api/src/main/java/org/thingsboard/script/api/tbel/TbelScriptExecutionTask.java index e95b5b7b0c..eb6064a5f8 100644 --- 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/tbel/TbelScriptExecutionTask.java @@ -13,20 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package org.thingsboard.script.api.mvel; +package org.thingsboard.script.api.tbel; 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 { +public class TbelScriptExecutionTask extends TbScriptExecutionTask { private final ExecutionContext context; - public MvelScriptExecutionTask(ExecutionContext context, ListenableFuture resultFuture) { + public TbelScriptExecutionTask(ExecutionContext context, ListenableFuture resultFuture) { super(resultFuture); this.context = context; } diff --git a/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java new file mode 100644 index 0000000000..4955fcfabe --- /dev/null +++ b/common/script/script-api/src/test/java/org/thingsboard/script/api/tbel/TbUtilsTest.java @@ -0,0 +1,98 @@ +/** + * 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.tbel; + +import org.junit.Assert; +import org.junit.Test; + +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.List; + +public class TbUtilsTest { + + @Test + public void parseHexToInt() { + Assert.assertEquals(0xAB, TbUtils.parseHexToInt("AB")); + Assert.assertEquals(0xABBA, TbUtils.parseHexToInt("ABBA", true)); + Assert.assertEquals(0xBAAB, TbUtils.parseHexToInt("ABBA", false)); + Assert.assertEquals(0xAABBCC, TbUtils.parseHexToInt("AABBCC", true)); + Assert.assertEquals(0xAABBCC, TbUtils.parseHexToInt("CCBBAA", false)); + Assert.assertEquals(0xAABBCCDD, TbUtils.parseHexToInt("AABBCCDD", true)); + Assert.assertEquals(0xAABBCCDD, TbUtils.parseHexToInt("DDCCBBAA", false)); + Assert.assertEquals(0xDDCCBBAA, TbUtils.parseHexToInt("DDCCBBAA", true)); + Assert.assertEquals(0xDDCCBBAA, TbUtils.parseHexToInt("AABBCCDD", false)); + } + + @Test + public void parseBytesToInt_checkPrimitives() { + int expected = 257; + byte[] data = ByteBuffer.allocate(4).putInt(expected).array(); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4)); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 2, 2, true)); + Assert.assertEquals(1, TbUtils.parseBytesToInt(data, 3, 1, true)); + + expected = Integer.MAX_VALUE; + data = ByteBuffer.allocate(4).putInt(expected).array(); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true)); + + expected = 0xAABBCCDD; + data = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD}; + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true)); + data = new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA}; + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, false)); + + expected = 0xAABBCC; + data = new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC}; + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, true)); + data = new byte[]{(byte) 0xCC, (byte) 0xBB, (byte) 0xAA}; + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, false)); + } + + @Test + public void parseBytesToInt_checkLists() { + int expected = 257; + List data = toList(ByteBuffer.allocate(4).putInt(expected).array()); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4)); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 2, 2, true)); + Assert.assertEquals(1, TbUtils.parseBytesToInt(data, 3, 1, true)); + + expected = Integer.MAX_VALUE; + data = toList(ByteBuffer.allocate(4).putInt(expected).array()); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true)); + + expected = 0xAABBCCDD; + data = toList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC, (byte) 0xDD}); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, true)); + data = toList(new byte[]{(byte) 0xDD, (byte) 0xCC, (byte) 0xBB, (byte) 0xAA}); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 4, false)); + + expected = 0xAABBCC; + data = toList(new byte[]{(byte) 0xAA, (byte) 0xBB, (byte) 0xCC}); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, true)); + data = toList(new byte[]{(byte) 0xCC, (byte) 0xBB, (byte) 0xAA}); + Assert.assertEquals(expected, TbUtils.parseBytesToInt(data, 0, 3, false)); + } + + private static List toList(byte[] data) { + List result = new ArrayList<>(data.length); + for (Byte b : data) { + result.add(b); + } + return result; + } + +} diff --git a/common/stats/pom.xml b/common/stats/pom.xml index d316980e97..2fcac6e059 100644 --- a/common/stats/pom.xml +++ b/common/stats/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/coap/pom.xml b/common/transport/coap/pom.xml index 70b2470dc4..f4089d9a79 100644 --- a/common/transport/coap/pom.xml +++ b/common/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/http/pom.xml b/common/transport/http/pom.xml index c0bb5d9001..c6c8f9869d 100644 --- a/common/transport/http/pom.xml +++ b/common/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/lwm2m/pom.xml b/common/transport/lwm2m/pom.xml index d9f92d685d..3c6fae874f 100644 --- a/common/transport/lwm2m/pom.xml +++ b/common/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/mqtt/pom.xml b/common/transport/mqtt/pom.xml index 9906b39ba7..9442a66646 100644 --- a/common/transport/mqtt/pom.xml +++ b/common/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.common.transport diff --git a/common/transport/pom.xml b/common/transport/pom.xml index 125139c523..47432f47f2 100644 --- a/common/transport/pom.xml +++ b/common/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/transport/snmp/pom.xml b/common/transport/snmp/pom.xml index c9611333f8..939eecb009 100644 --- a/common/transport/snmp/pom.xml +++ b/common/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport diff --git a/common/transport/transport-api/pom.xml b/common/transport/transport-api/pom.xml index 932629bb12..06fbfc5771 100644 --- a/common/transport/transport-api/pom.xml +++ b/common/transport/transport-api/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.common - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.common.transport 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 fac988ed8b..bf03122b18 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 @@ -32,6 +32,7 @@ import org.thingsboard.common.util.ThingsBoardExecutors; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.server.common.data.ApiUsageRecordKey; import org.thingsboard.server.common.data.ApiUsageState; +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; @@ -585,7 +586,7 @@ public class DefaultTransportService implements TransportService { TbMsgMetaData metaData = new TbMsgMetaData(); metaData.putValue("deviceName", sessionInfo.getDeviceName()); metaData.putValue("deviceType", sessionInfo.getDeviceType()); - metaData.putValue("notifyDevice", "false"); + metaData.putValue(DataConstants.NOTIFY_DEVICE_METADATA_KEY, "false"); CustomerId customerId = getCustomerId(sessionInfo); sendToRuleEngine(tenantId, deviceId, customerId, sessionInfo, json, metaData, SessionMsgType.POST_ATTRIBUTES_REQUEST, new TransportTbQueueCallback(new ApiStatsProxyCallback<>(tenantId, customerId, msg.getKvList().size(), callback))); diff --git a/common/util/pom.xml b/common/util/pom.xml index 5717362ff0..1a178b4c1e 100644 --- a/common/util/pom.xml +++ b/common/util/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common diff --git a/common/util/src/main/java/org/thingsboard/common/util/RegexUtils.java b/common/util/src/main/java/org/thingsboard/common/util/RegexUtils.java index af968a6bff..1f47b41a14 100644 --- a/common/util/src/main/java/org/thingsboard/common/util/RegexUtils.java +++ b/common/util/src/main/java/org/thingsboard/common/util/RegexUtils.java @@ -33,4 +33,8 @@ public class RegexUtils { }); } + public static boolean matches(String input, Pattern pattern) { + return pattern.matcher(input).matches(); + } + } diff --git a/common/version-control/pom.xml b/common/version-control/pom.xml index 7929a49a3b..6ff68da760 100644 --- a/common/version-control/pom.xml +++ b/common/version-control/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT common org.thingsboard.common 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 4c0dae4a12..4bfca630ba 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 @@ -197,12 +197,7 @@ public class GitRepository { LogCommand command = git.log() .add(branchId); - if (StringUtils.isNotEmpty(pageLink.getTextSearch())) { - command.setRevFilter(new NoMergesAndCommitMessageFilter(pageLink.getTextSearch())); - } else { - command.setRevFilter(RevFilter.NO_MERGES); - } - + command.setRevFilter(new CommitFilter(pageLink.getTextSearch(), settings.isShowMergeCommits())); if (StringUtils.isNotEmpty(path)) { command.addPath(path); } @@ -478,17 +473,20 @@ public class GitRepository { } } - private static class NoMergesAndCommitMessageFilter extends RevFilter { + private static class CommitFilter extends RevFilter { private final String textSearch; + private final boolean showMergeCommits; - NoMergesAndCommitMessageFilter(String textSearch) { + CommitFilter(String textSearch, boolean showMergeCommits) { this.textSearch = textSearch.toLowerCase(); + this.showMergeCommits = showMergeCommits; } @Override public boolean include(RevWalk walker, RevCommit c) { - return c.getParentCount() < 2 && c.getFullMessage().toLowerCase().contains(this.textSearch); + return (showMergeCommits || c.getParentCount() < 2) && (StringUtils.isEmpty(textSearch) + || c.getFullMessage().toLowerCase().contains(textSearch)); } @Override @@ -501,10 +499,6 @@ public class GitRepository { return false; } - @Override - public String toString() { - return "NO_MERGES_AND_COMMIT_MESSAGE"; - } } @Data diff --git a/dao/pom.xml b/dao/pom.xml index 35e253312e..5335492759 100644 --- a/dao/pom.xml +++ b/dao/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard dao 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 1316e52405..76637218dc 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 @@ -65,6 +65,7 @@ import java.util.concurrent.Executors; import java.util.stream.Collectors; import java.util.stream.Stream; +import static org.thingsboard.server.dao.service.Validator.validateEntityDataPageLink; import static org.thingsboard.server.dao.service.Validator.validateId; @Service @@ -139,6 +140,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ public PageData findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection orderedEntityIds) { validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + validateEntityDataPageLink(query.getPageLink()); return alarmDao.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds); } @@ -262,9 +264,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()); } 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/asset/AssetProfileServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java index 3b0f2d32e6..58470cfc6b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetProfileServiceImpl.java @@ -95,7 +95,7 @@ public class AssetProfileServiceImpl extends AbstractCachedEntityService assetProfileDao.findByName(tenantId, profileName), true); + () -> assetProfileDao.findByName(tenantId, profileName), false); } @Override 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/entity/BaseEntityService.java b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java index 49cea353fb..bb1c633f85 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/entity/BaseEntityService.java @@ -20,6 +20,7 @@ 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.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.util.CollectionUtils; @@ -59,6 +60,7 @@ import org.thingsboard.server.dao.tenant.TenantService; import org.thingsboard.server.dao.user.UserService; import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID; +import static org.thingsboard.server.dao.service.Validator.validateEntityDataPageLink; import static org.thingsboard.server.dao.service.Validator.validateId; /** @@ -238,16 +240,6 @@ public class BaseEntityService extends AbstractEntityService implements EntitySe validateEntityDataPageLink(query.getPageLink()); } - private static void validateEntityDataPageLink(EntityDataPageLink pageLink) { - if (pageLink == null) { - throw new IncorrectParameterException("Entity Data Page link must be specified."); - } else if (pageLink.getPageSize() < 1) { - throw new IncorrectParameterException("Incorrect entity data page link page size '" + pageLink.getPageSize() + "'. Page size must be greater than zero."); - } else if (pageLink.getPage() < 0) { - throw new IncorrectParameterException("Incorrect entity data page link page '" + pageLink.getPage() + "'. Page must be positive integer."); - } - } - private static void validateRelationQuery(RelationsQueryFilter queryFilter) { if (queryFilter.isMultiRoot() && queryFilter.getMultiRootEntitiesType() ==null){ throw new IncorrectParameterException("Multi-root relation query filter should contain 'multiRootEntitiesType'"); 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/NoXssValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java index 5e61d75fea..31496b84d5 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/NoXssValidator.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.dao.service; +import com.fasterxml.jackson.databind.JsonNode; import lombok.extern.slf4j.Slf4j; import org.owasp.validator.html.AntiSamy; import org.owasp.validator.html.Policy; @@ -48,12 +49,18 @@ public class NoXssValidator implements ConstraintValidator { @Override public boolean isValid(Object value, ConstraintValidatorContext constraintValidatorContext) { - if (!(value instanceof String) || ((String) value).isEmpty()) { + String stringValue; + if (value instanceof CharSequence || value instanceof JsonNode) { + stringValue = value.toString(); + } else { + return true; + } + if (stringValue.isEmpty()) { return true; } try { - return xssChecker.scan((String) value, xssPolicy).getNumberOfErrors() == 0; + return xssChecker.scan(stringValue, xssPolicy).getNumberOfErrors() == 0; } catch (ScanException | PolicyException e) { return false; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java index c5c6040deb..21aaf0c653 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/Validator.java @@ -15,16 +15,24 @@ */ package org.thingsboard.server.dao.service; +import org.apache.commons.lang3.StringUtils; +import org.thingsboard.common.util.RegexUtils; import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.UUIDBased; import org.thingsboard.server.common.data.page.PageLink; +import org.thingsboard.server.common.data.query.EntityDataPageLink; +import org.thingsboard.server.common.data.query.EntityKey; +import org.thingsboard.server.common.data.query.EntityKeyType; import org.thingsboard.server.dao.exception.IncorrectParameterException; import java.util.List; import java.util.UUID; +import java.util.regex.Pattern; public class Validator { + public static final Pattern PROPERTY_PATTERN = Pattern.compile("^[\\p{L}0-9_-]+$"); // Unicode letters, numbers, '_' and '-' allowed + /** * This method validate EntityId entity id. If entity id is invalid than throw * IncorrectParameterException exception @@ -122,7 +130,31 @@ public class Validator { throw new IncorrectParameterException("Incorrect page link page size '"+pageLink.getPageSize()+"'. Page size must be greater than zero."); } else if (pageLink.getPage() < 0) { throw new IncorrectParameterException("Incorrect page link page '"+pageLink.getPage()+"'. Page must be positive integer."); + } else if (pageLink.getSortOrder() != null) { + if (!isValidProperty(pageLink.getSortOrder().getProperty())) { + throw new IncorrectParameterException("Invalid page link sort property"); + } } } + public static void validateEntityDataPageLink(EntityDataPageLink pageLink) { + if (pageLink == null) { + throw new IncorrectParameterException("Entity Data Page link must be specified."); + } else if (pageLink.getPageSize() < 1) { + throw new IncorrectParameterException("Incorrect entity data page link page size '" + pageLink.getPageSize() + "'. Page size must be greater than zero."); + } else if (pageLink.getPage() < 0) { + throw new IncorrectParameterException("Incorrect entity data page link page '" + pageLink.getPage() + "'. Page must be positive integer."); + } else if (pageLink.getSortOrder() != null && pageLink.getSortOrder().getKey() != null) { + EntityKey sortKey = pageLink.getSortOrder().getKey(); + if ((sortKey.getType() == EntityKeyType.ENTITY_FIELD || sortKey.getType() == EntityKeyType.ALARM_FIELD) + && !isValidProperty(sortKey.getKey())) { + throw new IncorrectParameterException("Invalid entity data page link sort property"); + } + } + } + + public static boolean isValidProperty(String key) { + return StringUtils.isEmpty(key) || RegexUtils.matches(key, PROPERTY_PATTERN); + } + } diff --git a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java index 4df37ddc00..5a26990fcd 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java +++ b/dao/src/main/java/org/thingsboard/server/dao/service/validator/UserDataValidator.java @@ -19,6 +19,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.Customer; +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.User; @@ -66,6 +67,24 @@ public class UserDataValidator extends DataValidator { } } + @Override + protected User validateUpdate(TenantId tenantId, User user) { + User old = userDao.findById(user.getTenantId(), user.getId().getId()); + if (old == null) { + throw new DataValidationException("Can't update non existing user!"); + } + if (!old.getTenantId().equals(user.getTenantId())) { + throw new DataValidationException("Can't update user tenant id!"); + } + if (!old.getAuthority().equals(user.getAuthority())) { + throw new DataValidationException("Can't update user authority!"); + } + if (!old.getCustomerId().equals(user.getCustomerId())) { + throw new DataValidationException("Can't update user customer id!"); + } + return old; + } + @Override protected void validateDataImpl(TenantId requestTenantId, User user) { if (StringUtils.isEmpty(user.getEmail())) { 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/query/DefaultAlarmQueryRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java index 94eebc7f6b..ce48e9ec95 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java @@ -141,6 +141,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { selectPart.append(" a.originator_id as entity_id "); } EntityDataSortOrder sortOrder = pageLink.getSortOrder(); + String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch()); if (sortOrder != null && sortOrder.getKey().getType().equals(EntityKeyType.ALARM_FIELD)) { String sortOrderKey = sortOrder.getKey().getKey(); sortPart.append(alarmFieldColumnMap.getOrDefault(sortOrderKey, sortOrderKey)) @@ -167,7 +168,11 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } joinPart.append(" as e(id, priority)) e "); if (pageLink.isSearchPropagatedAlarms()) { - joinPart.append("on ea.entity_id = e.id"); + if (textSearchQuery.isEmpty()) { + joinPart.append("on ea.entity_id = e.id"); + } else { + joinPart.append("on a.entity_id = e.id"); + } } else { joinPart.append("on a.originator_id = e.id"); } @@ -231,13 +236,11 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository { } } - String textSearchQuery = buildTextSearchQuery(ctx, query.getAlarmFields(), pageLink.getTextSearch()); - String mainQuery; - if (!textSearchQuery.isEmpty()) { - mainQuery = selectPart.toString() + fromPart.toString() + wherePart.toString(); - mainQuery = String.format("select * from (%s) a %s WHERE %s", mainQuery, joinPart, textSearchQuery); + String mainQuery = String.format("%s%s", selectPart, fromPart); + if (textSearchQuery.isEmpty()) { + mainQuery = String.format("%s%s%s", mainQuery, joinPart, wherePart); } else { - mainQuery = selectPart.toString() + fromPart.toString() + joinPart.toString() + wherePart.toString(); + mainQuery = String.format("select * from (%s%s) a %s WHERE %s", mainQuery, wherePart, joinPart, textSearchQuery); } String countQuery = String.format("select count(*) from (%s) result", mainQuery); long queryTs = System.currentTimeMillis(); 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/sqlts/AbstractChunkedAggregationTimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/AbstractChunkedAggregationTimeseriesDao.java index 22c4661fcd..4875468c09 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; @@ -105,11 +105,6 @@ public abstract class AbstractChunkedAggregationTimeseriesDao extends AbstractSq return Futures.immediateFuture(null); } - @Override - public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return Futures.immediateFuture(null); - } - @Override public ListenableFuture> findAllAsync(TenantId tenantId, EntityId entityId, List queries) { return processFindAllAsync(tenantId, entityId, queries); @@ -143,8 +138,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/insert/sql/SqlPartitioningRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java index 87a62d356f..12295b71cb 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java +++ b/dao/src/main/java/org/thingsboard/server/dao/sqlts/insert/sql/SqlPartitioningRepository.java @@ -16,16 +16,16 @@ package org.thingsboard.server.dao.sqlts.insert.sql; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.exception.ExceptionUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.dao.DataAccessException; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; +import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.thingsboard.server.dao.timeseries.SqlPartition; -import javax.persistence.EntityManager; -import javax.persistence.PersistenceContext; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -36,9 +36,6 @@ import java.util.concurrent.locks.ReentrantLock; @Slf4j public class SqlPartitioningRepository { - @PersistenceContext - private EntityManager entityManager; - @Autowired private JdbcTemplate jdbcTemplate; @@ -50,12 +47,12 @@ public class SqlPartitioningRepository { private final Map> tablesPartitions = new ConcurrentHashMap<>(); private final ReentrantLock partitionCreationLock = new ReentrantLock(); - @Transactional + @Transactional(propagation = Propagation.NOT_SUPPORTED) public void save(SqlPartition partition) { - entityManager.createNativeQuery(partition.getQuery()).executeUpdate(); + jdbcTemplate.execute(partition.getQuery()); } - @Transactional + @Transactional(propagation = Propagation.NOT_SUPPORTED) // executing non-transactionally, so that parent transaction is not aborted on partition save error public void createPartitionIfNotExists(String table, long entityTs, long partitionDurationMs) { long partitionStartTs = calculatePartitionStartTime(entityTs, partitionDurationMs); Map partitions = tablesPartitions.computeIfAbsent(table, t -> new ConcurrentHashMap<>()); @@ -64,19 +61,17 @@ public class SqlPartitioningRepository { partitionCreationLock.lock(); try { if (partitions.containsKey(partitionStartTs)) return; - log.trace("Saving partition: {}", partition); + log.info("Saving partition {}-{} for table {}", partition.getStart(), partition.getEnd(), table); save(partition); log.trace("Adding partition to map: {}", partition); partitions.put(partition.getStart(), partition); - } catch (RuntimeException e) { - log.trace("Error occurred during partition save:", e); - String msg = ExceptionUtils.getRootCauseMessage(e); - if (msg.contains("would overlap partition")) { - log.warn("Couldn't save {} partition for {}, data will be saved to the default partition. SQL error: {}", - partition.getPartitionDate(), table, msg); + } catch (Exception e) { + String error = ExceptionUtils.getRootCauseMessage(e); + if (StringUtils.containsAny(error, "would overlap partition", "already exists")) { partitions.put(partition.getStart(), partition); + log.debug("Couldn't save partition {}-{} for table {}: {}", partition.getStart(), partition.getEnd(), table, error); } else { - throw e; + log.warn("Couldn't save partition {}-{} for table {}: {}", partition.getStart(), partition.getEnd(), table, error); } } finally { partitionCreationLock.unlock(); 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..08089a818e 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 @@ -18,7 +18,6 @@ package org.thingsboard.server.dao.sqlts.timescale; 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.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.PageRequest; @@ -36,7 +35,6 @@ import org.thingsboard.server.common.stats.StatsFactory; import org.thingsboard.server.dao.DaoUtil; import org.thingsboard.server.dao.model.sql.AbstractTsKvEntity; import org.thingsboard.server.dao.model.sqlts.timescale.ts.TimescaleTsKvEntity; -import org.thingsboard.server.dao.model.sqlts.ts.TsKvEntity; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueParams; import org.thingsboard.server.dao.sql.TbSqlBlockingQueueWrapper; import org.thingsboard.server.dao.sqlts.AbstractSqlTimeseriesDao; @@ -52,7 +50,6 @@ import java.util.Comparator; import java.util.List; import java.util.Optional; import java.util.UUID; -import java.util.concurrent.CompletableFuture; import java.util.function.Function; @Component @@ -144,11 +141,6 @@ public class TimescaleTimeseriesDao extends AbstractSqlTimeseriesDao implements }); } - @Override - public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - return service.submit(() -> null); - } - @Override public ListenableFuture findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { @@ -175,9 +167,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/BaseTimeseriesService.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java index c8a5076c65..d366d56eba 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java @@ -46,7 +46,6 @@ import org.thingsboard.server.dao.service.Validator; import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; @@ -235,7 +234,7 @@ public class BaseTimeseriesService implements TimeseriesService { } else { endTs = query.getEndTs(); } - return new BaseReadTsKvQuery(query.getKey(), startTs, endTs, query.getInterval(), query.getLimit(), query.getAggregation(), query.getOrder()); + return new BaseReadTsKvQuery(query, startTs, endTs); }).collect(Collectors.toList()); } @@ -277,7 +276,6 @@ public class BaseTimeseriesService implements TimeseriesService { private void deleteAndRegisterFutures(TenantId tenantId, List> futures, EntityId entityId, DeleteTsKvQuery query) { futures.add(Futures.transform(timeseriesDao.remove(tenantId, entityId, query), v -> null, MoreExecutors.directExecutor())); futures.add(timeseriesLatestDao.removeLatest(tenantId, entityId, query)); - futures.add(Futures.transform(timeseriesDao.removePartition(tenantId, entityId, query), v -> null, MoreExecutors.directExecutor())); } private static void validate(EntityId entityId) { 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 7f8dca5af1..1b17ec1816 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 @@ -28,6 +28,7 @@ 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.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; @@ -59,7 +60,6 @@ import java.time.LocalDateTime; import java.time.ZoneOffset; import java.time.temporal.ChronoUnit; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Optional; @@ -82,17 +82,21 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD protected static final int MIN_AGGREGATION_STEP_MS = 1000; public static final String ASC_ORDER = "ASC"; public static final long SECONDS_IN_DAY = TimeUnit.DAYS.toSeconds(1); - - protected static List FIXED_PARTITION = Arrays.asList(new Long[]{0L}); + protected static final List FIXED_PARTITION = List.of(0L); private CassandraTsPartitionsCache cassandraTsPartitionsCache; @Autowired private Environment environment; + @Getter @Value("${cassandra.query.ts_key_value_partitioning}") private String partitioning; + @Getter + @Value("${cassandra.query.use_ts_key_value_partitioning_on_read:true}") + private boolean useTsKeyValuePartitioningOnRead; + @Value("${cassandra.query.ts_key_value_partitions_max_cache_size:100000}") private long partitionsCacheSize; @@ -111,7 +115,6 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD private PreparedStatement[] fetchStmtsAsc; private PreparedStatement[] fetchStmtsDesc; private PreparedStatement deleteStmt; - private PreparedStatement deletePartitionStmt; private final Lock stmtCreationLock = new ReentrantLock(); private boolean isInstall() { @@ -222,46 +225,6 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD return resultFuture; } - @Override - public ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) { - long minPartition = toPartitionTs(query.getStartTs()); - long maxPartition = toPartitionTs(query.getEndTs()); - if (minPartition == maxPartition) { - return Futures.immediateFuture(null); - } else { - TbResultSetFuture partitionsFuture = fetchPartitions(tenantId, entityId, query.getKey(), minPartition, maxPartition); - - final SimpleListenableFuture resultFuture = new SimpleListenableFuture<>(); - final ListenableFuture> partitionsListFuture = Futures.transformAsync(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor); - - Futures.addCallback(partitionsListFuture, new FutureCallback>() { - @Override - public void onSuccess(@Nullable List partitions) { - int index = 0; - if (minPartition != query.getStartTs()) { - index = 1; - } - List partitionsToDelete = new ArrayList<>(); - for (int i = index; i < partitions.size() - 1; i++) { - partitionsToDelete.add(partitions.get(i)); - } - QueryCursor cursor = new QueryCursor(entityId.getEntityType().name(), entityId.getId(), query, partitionsToDelete); - deletePartitionAsync(tenantId, cursor, resultFuture); - - for (Long partition : partitionsToDelete) { - cassandraTsPartitionsCache.invalidate(new CassandraPartitionCacheKey(entityId, query.getKey(), partition)); - } - } - - @Override - public void onFailure(Throwable t) { - log.error("[{}][{}] Failed to fetch partitions for interval {}-{}", entityId.getEntityType().name(), entityId.getId(), minPartition, maxPartition, t); - } - }, readResultsProcessingExecutor); - return resultFuture; - } - } - @Override public ListenableFuture findAllAsync(TenantId tenantId, EntityId entityId, ReadTsKvQuery query) { if (query.getAggregation() == Aggregation.NONE) { @@ -337,7 +300,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD }, MoreExecutors.directExecutor()); } - private long toPartitionTs(long ts) { + long toPartitionTs(long ts) { LocalDateTime time = LocalDateTime.ofInstant(Instant.ofEpochMilli(ts), ZoneOffset.UTC); return tsFormat.truncatedTo(time).toInstant(ZoneOffset.UTC).toEpochMilli(); } @@ -417,10 +380,37 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD if (isFixedPartitioning()) { //no need to fetch partitions from DB return Futures.immediateFuture(FIXED_PARTITION); } + if (!isUseTsKeyValuePartitioningOnRead()) { + return Futures.immediateFuture(calculatePartitions(minPartition, maxPartition)); + } TbResultSetFuture partitionsFuture = fetchPartitions(tenantId, entityId, query.getKey(), minPartition, maxPartition); return Futures.transformAsync(partitionsFuture, getPartitionsArrayFunction(), readResultsProcessingExecutor); } + List calculatePartitions(long minPartition, long maxPartition) { + if (minPartition == maxPartition) { + return Collections.singletonList(minPartition); + } + List partitions = new ArrayList<>(); + + long currentPartition = minPartition; + LocalDateTime currentPartitionTime = LocalDateTime.ofInstant(Instant.ofEpochMilli(currentPartition), ZoneOffset.UTC); + + while (maxPartition > currentPartition) { + partitions.add(currentPartition); + currentPartitionTime = calculateNextPartition(currentPartitionTime); + currentPartition = currentPartitionTime.toInstant(ZoneOffset.UTC).toEpochMilli(); + } + + partitions.add(maxPartition); + + return partitions; + } + + private LocalDateTime calculateNextPartition(LocalDateTime time) { + return time.plus(1, tsFormat.getTruncateUnit()); + } + private AsyncFunction, List> getFetchChunksAsyncFunction(TenantId tenantId, EntityId entityId, String key, Aggregation aggregation, long startTs, long endTs) { return partitions -> { try { @@ -593,51 +583,6 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD return deleteStmt; } - private void deletePartitionAsync(TenantId tenantId, final QueryCursor cursor, final SimpleListenableFuture resultFuture) { - if (!cursor.hasNextPartition()) { - resultFuture.set(null); - } else { - PreparedStatement proto = getDeletePartitionStmt(); - BoundStatementBuilder stmtBuilder = new BoundStatementBuilder(proto.bind()); - stmtBuilder.setString(0, cursor.getEntityType()); - stmtBuilder.setUuid(1, cursor.getEntityId()); - stmtBuilder.setLong(2, cursor.getNextPartition()); - stmtBuilder.setString(3, cursor.getKey()); - - BoundStatement stmt = stmtBuilder.build(); - - Futures.addCallback(executeAsyncWrite(tenantId, stmt), new FutureCallback() { - @Override - public void onSuccess(@Nullable AsyncResultSet result) { - deletePartitionAsync(tenantId, cursor, resultFuture); - } - - @Override - public void onFailure(Throwable t) { - log.error("[{}][{}] Failed to delete data for query {}-{}", stmt, t); - } - }, readResultsProcessingExecutor); - } - } - - private PreparedStatement getDeletePartitionStmt() { - if (deletePartitionStmt == null) { - stmtCreationLock.lock(); - try { - if (deletePartitionStmt == null) { - deletePartitionStmt = prepare("DELETE FROM " + ModelConstants.TS_KV_PARTITIONS_CF + - " WHERE " + ModelConstants.ENTITY_TYPE_COLUMN + EQUALS_PARAM - + "AND " + ModelConstants.ENTITY_ID_COLUMN + EQUALS_PARAM - + "AND " + ModelConstants.PARTITION_COLUMN + EQUALS_PARAM - + "AND " + ModelConstants.KEY_COLUMN + EQUALS_PARAM); - } - } finally { - stmtCreationLock.unlock(); - } - } - return deletePartitionStmt; - } - private PreparedStatement getSaveStmt(DataType dataType) { if (saveStmts == null) { stmtCreationLock.lock(); @@ -645,14 +590,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD if (saveStmts == null) { var stmts = new PreparedStatement[DataType.values().length]; for (DataType type : DataType.values()) { - stmts[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; } @@ -670,14 +608,7 @@ public class CassandraBaseTimeseriesDao extends AbstractCassandraBaseTimeseriesD if (saveTtlStmts == null) { var stmts = new PreparedStatement[DataType.values().length]; for (DataType type : DataType.values()) { - stmts[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; } @@ -688,6 +619,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/timeseries/TimeseriesDao.java b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java index 5fd26d400a..4878fdd293 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesDao.java @@ -24,7 +24,6 @@ import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.TsKvEntry; import java.util.List; -import java.util.Map; /** * @author Andrew Shvayka @@ -39,7 +38,5 @@ public interface TimeseriesDao { ListenableFuture remove(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); - ListenableFuture removePartition(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query); - void cleanup(long systemTtl); } 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 9e206ef6ae..a40f743448 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; @@ -219,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 0b95809b91..ffded2bd39 100644 --- a/dao/src/main/resources/sql/schema-entities-idx.sql +++ b/dao/src/main/resources/sql/schema-entities-idx.sql @@ -50,6 +50,12 @@ 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_audit_log_id ON audit_log(id); + +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_edge_event_id ON edge_event(id); + 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 262bd9a609..3332034f8e 100644 --- a/dao/src/main/resources/sql/schema-entities.sql +++ b/dao/src/main/resources/sql/schema-entities.sql @@ -720,7 +720,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), @@ -730,7 +730,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/NoXssValidatorTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java index 36eb76edb5..5a4f3af90e 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/NoXssValidatorTest.java @@ -15,23 +15,16 @@ */ package org.thingsboard.server.dao.service; -import org.junit.jupiter.api.BeforeAll; +import com.fasterxml.jackson.databind.node.TextNode; +import org.junit.Test; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.asset.Asset; -import javax.validation.ConstraintValidatorContext; - -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.mockito.Mockito.mock; +import static org.assertj.core.api.Assertions.assertThatThrownBy; public class NoXssValidatorTest { - private static NoXssValidator validator; - - @BeforeAll - public static void beforeAll() { - validator = new NoXssValidator(); - validator.initialize(null); - } @ParameterizedTest @ValueSource(strings = { @@ -44,9 +37,25 @@ public class NoXssValidatorTest { " ", "123 bebe" }) - public void testIsNotValid(String stringWithXss) { - boolean isValid = validator.isValid(stringWithXss, mock(ConstraintValidatorContext.class)); - assertFalse(isValid); + public void givenEntityWithMaliciousPropertyValue_thenReturnValidationError(String maliciousString) { + Asset invalidAsset = new Asset(); + invalidAsset.setName(maliciousString); + + assertThatThrownBy(() -> { + ConstraintValidator.validateFields(invalidAsset); + }).hasMessageContaining("field value is malformed"); + } + + @Test + public void givenEntityWithMaliciousValueInAdditionalInfo_thenReturnValidationError() { + Asset invalidAsset = new Asset(); + String maliciousValue = "qwertyqwerty"; + invalidAsset.setAdditionalInfo(JacksonUtil.newObjectNode() + .set("description", new TextNode(maliciousValue))); + + assertThatThrownBy(() -> { + ConstraintValidator.validateFields(invalidAsset); + }).hasMessageContaining("field value is malformed"); } } diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java index 4e2fd17846..1eea5d3912 100644 --- a/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java +++ b/dao/src/test/java/org/thingsboard/server/dao/service/timeseries/BaseTimeseriesServiceTest.java @@ -35,6 +35,7 @@ import org.thingsboard.server.common.data.kv.DoubleDataEntry; import org.thingsboard.server.common.data.kv.KvEntry; import org.thingsboard.server.common.data.kv.LongDataEntry; import org.thingsboard.server.common.data.kv.ReadTsKvQuery; +import org.thingsboard.server.common.data.kv.ReadTsKvQueryResult; import org.thingsboard.server.common.data.kv.StringDataEntry; import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.objects.TelemetryEntityView; @@ -48,6 +49,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -344,6 +346,36 @@ public abstract class BaseTimeseriesServiceTest extends AbstractServiceTest { Assert.assertEquals(toTsEntry(TS - 3, stringKvEntry), entries.get(2)); } + @Test + public void testFindAllByQueries_verifyQueryId() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS); + saveEntries(deviceId, TS - 2); + saveEntries(deviceId, TS - 10); + + BaseReadTsKvQuery query = new BaseReadTsKvQuery(STRING_KEY, TS - 10, TS + 1, 0, 1000, Aggregation.NONE, "DESC"); + findAndVerifyQueryId(deviceId, query); + } + + @Test + public void testFindAllByQueries_verifyQueryId_forEntityView() throws Exception { + DeviceId deviceId = new DeviceId(Uuids.timeBased()); + saveEntries(deviceId, TS); + saveEntries(deviceId, TS - 2); + saveEntries(deviceId, TS - 12); + + EntityView entityView = saveAndCreateEntityView(deviceId, List.of(LONG_KEY)); + + BaseReadTsKvQuery query = new BaseReadTsKvQuery(LONG_KEY, TS - 10, TS + 1, 0, 1000, Aggregation.NONE, "DESC"); + findAndVerifyQueryId(entityView.getId(), query); + } + + private void findAndVerifyQueryId(EntityId entityId, ReadTsKvQuery query) throws InterruptedException, ExecutionException, TimeoutException { + List results = tsService.findAllByQueries(tenantId, entityId, List.of(query)).get(MAX_TIMEOUT, TimeUnit.SECONDS); + assertThat(results).isNotEmpty(); + assertThat(results).extracting(ReadTsKvQueryResult::getQueryId).containsOnly(query.getId()); + } + @Test public void testDeleteDeviceTsDataWithOverwritingLatest() throws Exception { DeviceId deviceId = new DeviceId(Uuids.timeBased()); diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningDaysAlwaysExistsTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningDaysAlwaysExistsTest.java new file mode 100644 index 0000000000..d5e8350104 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningDaysAlwaysExistsTest.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.dao.timeseries; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.dao.cassandra.CassandraCluster; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; + +import java.text.ParseException; +import java.util.List; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CassandraBaseTimeseriesDao.class) +@TestPropertySource(properties = { + "database.ts.type=cassandra", + "cassandra.query.ts_key_value_partitioning=DAYS", + "cassandra.query.use_ts_key_value_partitioning_on_read=false", + "cassandra.query.ts_key_value_partitions_max_cache_size=100000", + "cassandra.query.ts_key_value_partitions_cache_stats_enabled=true", + "cassandra.query.ts_key_value_partitions_cache_stats_interval=60", + "cassandra.query.ts_key_value_ttl=0", + "cassandra.query.set_null_values_enabled=false", +}) +@Slf4j +public class CassandraBaseTimeseriesDaoPartitioningDaysAlwaysExistsTest { + + @Autowired + CassandraBaseTimeseriesDao tsDao; + + @MockBean(answer = Answers.RETURNS_MOCKS) + @Qualifier("CassandraCluster") + CassandraCluster cassandraCluster; + + @MockBean + CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; + @MockBean + CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; + + @Test + public void testToPartitionsDays() throws ParseException { + assertThat(tsDao.getPartitioning()).isEqualTo("DAYS"); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-02T00:00:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-02T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-03T00:00:01Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-03T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T00:00:00Z").getTime()); + } + + @Test + public void testCalculatePartitionsDays() throws ParseException { + long startTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime()); + long nextTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-12T23:59:59Z").getTime()); + long endTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-15T00:00:00Z").getTime()); + log.info("startTs {}, nextTs {}, endTs {}", startTs, nextTs, endTs); + + assertThat(tsDao.calculatePartitions(0, 0)).isEqualTo(List.of(0L)); + assertThat(tsDao.calculatePartitions(0, 1)).isEqualTo(List.of(0L, 1L)); + + assertThat(tsDao.calculatePartitions(startTs, startTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime())); + assertThat(tsDao.calculatePartitions(startTs, nextTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-11T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-12T00:00:00Z").getTime())); + + assertThat(tsDao.calculatePartitions(startTs, endTs)).hasSize(6).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-11T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-12T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-13T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-14T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-15T00:00:00Z").getTime())); + + long leapStartTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-02-27T00:00:00Z").getTime()); + long leapEndTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-03-01T00:00:00Z").getTime()); + assertThat(tsDao.calculatePartitions(leapStartTs, leapEndTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-02-27T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-02-28T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-02-29T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-03-01T00:00:00Z").getTime())); + + long newYearStartTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-12-30T00:00:00Z").getTime()); + long newYearEndTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2021-01-01T00:00:00Z").getTime()); + assertThat(tsDao.calculatePartitions(newYearStartTs, newYearEndTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-12-30T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-12-31T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2021-01-01T00:00:00Z").getTime())); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningHoursAlwaysExistsTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningHoursAlwaysExistsTest.java new file mode 100644 index 0000000000..d5381653b3 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningHoursAlwaysExistsTest.java @@ -0,0 +1,134 @@ +/** + * 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.timeseries; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.dao.cassandra.CassandraCluster; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; + +import java.text.ParseException; +import java.util.List; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CassandraBaseTimeseriesDao.class) +@TestPropertySource(properties = { + "database.ts.type=cassandra", + "cassandra.query.ts_key_value_partitioning=HOURS", + "cassandra.query.use_ts_key_value_partitioning_on_read=false", + "cassandra.query.ts_key_value_partitions_max_cache_size=100000", + "cassandra.query.ts_key_value_partitions_cache_stats_enabled=true", + "cassandra.query.ts_key_value_partitions_cache_stats_interval=60", + "cassandra.query.ts_key_value_ttl=0", + "cassandra.query.set_null_values_enabled=false", +}) +@Slf4j +public class CassandraBaseTimeseriesDaoPartitioningHoursAlwaysExistsTest { + + @Autowired + CassandraBaseTimeseriesDao tsDao; + + @MockBean(answer = Answers.RETURNS_MOCKS) + @Qualifier("CassandraCluster") + CassandraCluster cassandraCluster; + + @MockBean + CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; + @MockBean + CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; + + @Test + public void testToPartitionsHours() throws ParseException { + assertThat(tsDao.getPartitioning()).isEqualTo("HOURS"); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-02T01:00:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-02T01:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-03T02:00:01Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-03T02:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T23:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T23:00:00Z").getTime()); + } + + @Test + public void testCalculatePartitionsHours() throws ParseException { + long startTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime()); + long nextTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T03:59:59Z").getTime()); + long endTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-11T00:59:00Z").getTime()); + log.info("startTs {}, nextTs {}, endTs {}", startTs, nextTs, endTs); + + assertThat(tsDao.calculatePartitions(0, 0)).isEqualTo(List.of(0L)); + assertThat(tsDao.calculatePartitions(0, 1)).isEqualTo(List.of(0L, 1L)); + + assertThat(tsDao.calculatePartitions(startTs, startTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime())); + assertThat(tsDao.calculatePartitions(startTs, nextTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T01:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T02:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T03:00:00Z").getTime())); + + assertThat(tsDao.calculatePartitions(startTs, endTs)).hasSize(25).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T01:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T02:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T03:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T04:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T05:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T06:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T07:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T08:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T09:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T10:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T11:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T12:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T13:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T14:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T15:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T16:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T17:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T18:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T19:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T20:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T21:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T22:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T23:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-11T00:00:00Z").getTime())); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningIndefiniteAlwaysExistsTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningIndefiniteAlwaysExistsTest.java new file mode 100644 index 0000000000..82f94cde36 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningIndefiniteAlwaysExistsTest.java @@ -0,0 +1,78 @@ +/** + * 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.timeseries; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.dao.cassandra.CassandraCluster; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; + +import java.text.ParseException; +import java.util.List; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CassandraBaseTimeseriesDao.class) +@TestPropertySource(properties = { + "database.ts.type=cassandra", + "cassandra.query.ts_key_value_partitioning=INDEFINITE", + "cassandra.query.use_ts_key_value_partitioning_on_read=false", + "cassandra.query.ts_key_value_partitions_max_cache_size=100000", + "cassandra.query.ts_key_value_partitions_cache_stats_enabled=true", + "cassandra.query.ts_key_value_partitions_cache_stats_interval=60", + "cassandra.query.ts_key_value_ttl=0", + "cassandra.query.set_null_values_enabled=false", +}) +@Slf4j +public class CassandraBaseTimeseriesDaoPartitioningIndefiniteAlwaysExistsTest { + + @Autowired + CassandraBaseTimeseriesDao tsDao; + + @MockBean(answer = Answers.RETURNS_MOCKS) + @Qualifier("CassandraCluster") + CassandraCluster cassandraCluster; + + @MockBean + CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; + @MockBean + CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; + + @Test + public void testToPartitionsIndefinite() throws ParseException { + assertThat(tsDao.getPartitioning()).isEqualTo("INDEFINITE"); + assertThat(tsDao.toPartitionTs(ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime())).isEqualTo(0L); + } + + + @Test + public void testCalculatePartitionsIndefinite() throws ParseException { + //Indefinite partitioning should never call tsDao.calculatePartitions() + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningMinutesAlwaysExistsTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningMinutesAlwaysExistsTest.java new file mode 100644 index 0000000000..ffdf18c42b --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningMinutesAlwaysExistsTest.java @@ -0,0 +1,121 @@ +/** + * 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.timeseries; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.dao.cassandra.CassandraCluster; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; + +import java.text.ParseException; +import java.util.List; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CassandraBaseTimeseriesDao.class) +@TestPropertySource(properties = { + "database.ts.type=cassandra", + "cassandra.query.ts_key_value_partitioning=MINUTES", + "cassandra.query.use_ts_key_value_partitioning_on_read=false", + "cassandra.query.ts_key_value_partitions_max_cache_size=100000", + "cassandra.query.ts_key_value_partitions_cache_stats_enabled=true", + "cassandra.query.ts_key_value_partitions_cache_stats_interval=60", + "cassandra.query.ts_key_value_ttl=0", + "cassandra.query.set_null_values_enabled=false", +}) +@Slf4j +public class CassandraBaseTimeseriesDaoPartitioningMinutesAlwaysExistsTest { + + @Autowired + CassandraBaseTimeseriesDao tsDao; + + @MockBean(answer = Answers.RETURNS_MOCKS) + @Qualifier("CassandraCluster") + CassandraCluster cassandraCluster; + + @MockBean + CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; + @MockBean + CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; + + @Test + public void testToPartitionsMinutes() throws ParseException { + assertThat(tsDao.getPartitioning()).isEqualTo("MINUTES"); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-02T00:01:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-02T00:01:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-03T00:02:01Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-03T00:02:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T23:59:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T23:59:00Z").getTime()); + } + + + @Test + public void testCalculatePartitionsMinutes() throws ParseException { + long startTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime()); + long nextTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:02:59Z").getTime()); + long endTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:10:00Z").getTime()); + log.info("startTs {}, nextTs {}, endTs {}", startTs, nextTs, endTs); + + assertThat(tsDao.calculatePartitions(0, 0)).isEqualTo(List.of(0L)); + assertThat(tsDao.calculatePartitions(0, 1)).isEqualTo(List.of(0L, 1L)); + + assertThat(tsDao.calculatePartitions(startTs, startTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime())); + assertThat(tsDao.calculatePartitions(startTs, nextTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:01:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:02:00Z").getTime())); + + assertThat(tsDao.calculatePartitions(startTs, endTs)).hasSize(11).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:01:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:02:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:03:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:04:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:05:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:06:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:07:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:08:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:09:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-10-10T00:10:00Z").getTime())); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningMonthsAlwaysExistsTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningMonthsAlwaysExistsTest.java new file mode 100644 index 0000000000..8c7d12da86 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningMonthsAlwaysExistsTest.java @@ -0,0 +1,134 @@ +/** + * 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.timeseries; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.dao.cassandra.CassandraCluster; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; + +import java.text.ParseException; +import java.util.List; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CassandraBaseTimeseriesDao.class) +@TestPropertySource(properties = { + "database.ts.type=cassandra", + "cassandra.query.ts_key_value_partitioning=MONTHS", + "cassandra.query.use_ts_key_value_partitioning_on_read=false", + "cassandra.query.ts_key_value_partitions_max_cache_size=100000", + "cassandra.query.ts_key_value_partitions_cache_stats_enabled=true", + "cassandra.query.ts_key_value_partitions_cache_stats_interval=60", + "cassandra.query.ts_key_value_ttl=0", + "cassandra.query.set_null_values_enabled=false", +}) +@Slf4j +public class CassandraBaseTimeseriesDaoPartitioningMonthsAlwaysExistsTest { + + @Autowired + CassandraBaseTimeseriesDao tsDao; + + @MockBean(answer = Answers.RETURNS_MOCKS) + @Qualifier("CassandraCluster") + CassandraCluster cassandraCluster; + + @MockBean + CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; + @MockBean + CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; + + @Test + public void testToPartitionsMonths() throws ParseException { + assertThat(tsDao.getPartitioning()).isEqualTo("MONTHS"); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime())).isEqualTo(1640995200000L).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-01T00:00:00Z").getTime())).isEqualTo(1651363200000L).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-01T00:00:01Z").getTime())).isEqualTo(1651363200000L).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T23:59:59Z").getTime())).isEqualTo(1651363200000L).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T23:59:59Z").getTime())).isEqualTo(1701388800000L).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-01T00:00:00Z").getTime()); + } + + @Test + public void testCalculatePartitionsMonths() throws ParseException { + long startTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-12-12T00:00:00Z").getTime()); + long nextTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-01-31T23:59:59Z").getTime()); + long leapTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-02-29T23:59:59Z").getTime()); + long endTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2021-01-31T23:59:59Z").getTime()); + log.info("startTs {}, nextTs {}, leapTs {}, endTs {}", startTs, nextTs, leapTs, endTs); + + assertThat(tsDao.calculatePartitions(0, 0)).isEqualTo(List.of(0L)); + assertThat(tsDao.calculatePartitions(0, 1)).isEqualTo(List.of(0L, 1L)); + + assertThat(tsDao.calculatePartitions(startTs, startTs)).isEqualTo(List.of(1575158400000L)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-12-01T00:00:00Z").getTime())); + assertThat(tsDao.calculatePartitions(startTs, nextTs)).isEqualTo(List.of(1575158400000L, 1577836800000L)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-12-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-01-01T00:00:00Z").getTime())); + + assertThat(tsDao.calculatePartitions(startTs, leapTs)).isEqualTo(List.of(1575158400000L, 1577836800000L, 1580515200000L)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-12-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-02-01T00:00:00Z").getTime())); + + assertThat(tsDao.calculatePartitions(startTs, endTs)).hasSize(14).isEqualTo(List.of( + 1575158400000L, + 1577836800000L, 1580515200000L, 1583020800000L, + 1585699200000L, 1588291200000L, 1590969600000L, + 1593561600000L, 1596240000000L, 1598918400000L, + 1601510400000L, 1604188800000L, 1606780800000L, + 1609459200000L)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-12-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-02-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-03-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-04-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-05-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-06-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-07-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-08-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-09-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-10-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-11-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-12-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2021-01-01T00:00:00Z").getTime())); + } + +} diff --git a/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningYearsAlwaysExistsTest.java b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningYearsAlwaysExistsTest.java new file mode 100644 index 0000000000..4b2bcb6455 --- /dev/null +++ b/dao/src/test/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesDaoPartitioningYearsAlwaysExistsTest.java @@ -0,0 +1,116 @@ +/** + * 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.timeseries; + +import lombok.extern.slf4j.Slf4j; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Answers; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.test.context.TestPropertySource; +import org.springframework.test.context.junit4.SpringRunner; +import org.thingsboard.server.dao.cassandra.CassandraCluster; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateReadExecutor; +import org.thingsboard.server.dao.nosql.CassandraBufferedRateWriteExecutor; + +import java.text.ParseException; +import java.util.List; + +import static org.apache.commons.lang3.time.DateFormatUtils.ISO_DATETIME_TIME_ZONE_FORMAT; +import static org.assertj.core.api.Assertions.assertThat; + +@RunWith(SpringRunner.class) +@SpringBootTest(classes = CassandraBaseTimeseriesDao.class) +@TestPropertySource(properties = { + "database.ts.type=cassandra", + "cassandra.query.ts_key_value_partitioning=YEARS", + "cassandra.query.use_ts_key_value_partitioning_on_read=false", + "cassandra.query.ts_key_value_partitions_max_cache_size=100000", + "cassandra.query.ts_key_value_partitions_cache_stats_enabled=true", + "cassandra.query.ts_key_value_partitions_cache_stats_interval=60", + "cassandra.query.ts_key_value_ttl=0", + "cassandra.query.set_null_values_enabled=false", +}) +@Slf4j +public class CassandraBaseTimeseriesDaoPartitioningYearsAlwaysExistsTest { + + @Autowired + CassandraBaseTimeseriesDao tsDao; + + @MockBean(answer = Answers.RETURNS_MOCKS) + @Qualifier("CassandraCluster") + CassandraCluster cassandraCluster; + + @MockBean + CassandraBufferedRateReadExecutor cassandraBufferedRateReadExecutor; + @MockBean + CassandraBufferedRateWriteExecutor cassandraBufferedRateWriteExecutor; + + @Test + public void testToPartitionsYears() throws ParseException { + assertThat(tsDao.getPartitioning()).isEqualTo("YEARS"); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-01T00:00:00Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-01T00:00:01Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-05-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime()); + assertThat(tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-12-31T23:59:59Z").getTime())).isEqualTo( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-01-01T00:00:00Z").getTime()); + } + + @Test + public void testCalculatePartitionsYears() throws ParseException { + long startTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-01-01T00:00:00Z").getTime()); + long nextTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2021-10-12T23:59:59Z").getTime()); + long endTs = tsDao.toPartitionTs( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2025-07-15T00:00:00Z").getTime()); + log.info("startTs {}, nextTs {}, endTs {}", startTs, nextTs, endTs); + + assertThat(tsDao.calculatePartitions(0, 0)).isEqualTo(List.of(0L)); + assertThat(tsDao.calculatePartitions(0, 1)).isEqualTo(List.of(0L, 1L)); + + assertThat(tsDao.calculatePartitions(startTs, startTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-01-01T00:00:00Z").getTime())); + assertThat(tsDao.calculatePartitions(startTs, nextTs)).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2021-01-01T00:00:00Z").getTime())); + + assertThat(tsDao.calculatePartitions(startTs, endTs)).hasSize(7).isEqualTo(List.of( + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2019-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2020-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2021-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2022-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2023-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2024-01-01T00:00:00Z").getTime(), + ISO_DATETIME_TIME_ZONE_FORMAT.parse("2025-01-01T00:00:00Z").getTime())); + } + +} diff --git a/docker/compose-utils.sh b/docker/compose-utils.sh index f45339eb92..c1963cc045 100755 --- a/docker/compose-utils.sh +++ b/docker/compose-utils.sh @@ -195,3 +195,35 @@ function checkFolders() { done < <(echo "$PERMISSION_LIST") return $EXIT_CODE } + +function composeVersion() { + #Checking whether "set -e" shell option should be restored after Compose version check + FLAG_SET=false + if [[ $SHELLOPTS =~ errexit ]]; then + set +e + FLAG_SET=true + fi + + #Checking Compose V1 availablity + docker-compose version >/dev/null 2>&1 + if [ $? -eq 0 ]; then status_v1=true; else status_v1=false; fi + + #Checking Compose V2 availablity + docker compose version >/dev/null 2>&1 + if [ $? -eq 0 ]; then status_v2=true; else status_v2=false; fi + + COMPOSE_VERSION="" + + if $status_v2 ; then + COMPOSE_VERSION="V2" + elif $status_v1 ; then + COMPOSE_VERSION="V1" + else + echo "Docker Compose plugin is not detected. Please check your environment." >&2 + exit 1 + fi + + echo $COMPOSE_VERSION + + if $FLAG_SET ; then set -e; fi +} diff --git a/docker/docker-compose.aws-sqs.yml b/docker/docker-compose.aws-sqs.yml index 331902851f..d6a542a340 100644 --- a/docker/docker-compose.aws-sqs.yml +++ b/docker/docker-compose.aws-sqs.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: tb-js-executor: diff --git a/docker/docker-compose.cassandra.volumes.yml b/docker/docker-compose.cassandra.volumes.yml index 37880b44fa..6efdd849e0 100644 --- a/docker/docker-compose.cassandra.volumes.yml +++ b/docker/docker-compose.cassandra.volumes.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: cassandra: @@ -23,5 +23,5 @@ services: volumes: cassandra-volume: - external: true - name: ${CASSANDRA_DATA_VOLUME} + external: + name: ${CASSANDRA_DATA_VOLUME} diff --git a/docker/docker-compose.confluent.yml b/docker/docker-compose.confluent.yml index 077acdca98..e7eee83c3b 100644 --- a/docker/docker-compose.confluent.yml +++ b/docker/docker-compose.confluent.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: tb-js-executor: diff --git a/docker/docker-compose.hybrid.yml b/docker/docker-compose.hybrid.yml index 12ea4cd099..2c84b41805 100644 --- a/docker/docker-compose.hybrid.yml +++ b/docker/docker-compose.hybrid.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: postgres: diff --git a/docker/docker-compose.kafka.yml b/docker/docker-compose.kafka.yml index e6fa0c489f..99b06d21fd 100644 --- a/docker/docker-compose.kafka.yml +++ b/docker/docker-compose.kafka.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: kafka: diff --git a/docker/docker-compose.postgres.volumes.yml b/docker/docker-compose.postgres.volumes.yml index caf78f23d7..8a57c2b74d 100644 --- a/docker/docker-compose.postgres.volumes.yml +++ b/docker/docker-compose.postgres.volumes.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: postgres: @@ -23,5 +23,5 @@ services: volumes: postgres-db-volume: - external: true - name: ${POSTGRES_DATA_VOLUME} + external: + name: ${POSTGRES_DATA_VOLUME} diff --git a/docker/docker-compose.postgres.yml b/docker/docker-compose.postgres.yml index 8fe8e6f53d..f9c27c4743 100644 --- a/docker/docker-compose.postgres.yml +++ b/docker/docker-compose.postgres.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: postgres: diff --git a/docker/docker-compose.prometheus-grafana.yml b/docker/docker-compose.prometheus-grafana.yml index c31b25190e..af560fec89 100644 --- a/docker/docker-compose.prometheus-grafana.yml +++ b/docker/docker-compose.prometheus-grafana.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' volumes: prometheus_data: {} diff --git a/docker/docker-compose.pubsub.yml b/docker/docker-compose.pubsub.yml index 7c122d0835..8e046b24d3 100644 --- a/docker/docker-compose.pubsub.yml +++ b/docker/docker-compose.pubsub.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: tb-js-executor: diff --git a/docker/docker-compose.rabbitmq.yml b/docker/docker-compose.rabbitmq.yml index 1f1cf1554c..fc3b8a7b1e 100644 --- a/docker/docker-compose.rabbitmq.yml +++ b/docker/docker-compose.rabbitmq.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: tb-js-executor: diff --git a/docker/docker-compose.redis-cluster.volumes.yml b/docker/docker-compose.redis-cluster.volumes.yml index 2cf319bd21..445b79099b 100644 --- a/docker/docker-compose.redis-cluster.volumes.yml +++ b/docker/docker-compose.redis-cluster.volumes.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: # Redis cluster @@ -39,20 +39,20 @@ services: volumes: redis-cluster-data-0: - external: true - name: ${REDIS_CLUSTER_DATA_VOLUME_0} + external: + name: ${REDIS_CLUSTER_DATA_VOLUME_0} redis-cluster-data-1: - external: true - name: ${REDIS_CLUSTER_DATA_VOLUME_1} + external: + name: ${REDIS_CLUSTER_DATA_VOLUME_1} redis-cluster-data-2: - external: true - name: ${REDIS_CLUSTER_DATA_VOLUME_2} + external: + name: ${REDIS_CLUSTER_DATA_VOLUME_2} redis-cluster-data-3: - external: true - name: ${REDIS_CLUSTER_DATA_VOLUME_3} + external: + name: ${REDIS_CLUSTER_DATA_VOLUME_3} redis-cluster-data-4: - external: true - name: ${REDIS_CLUSTER_DATA_VOLUME_4} + external: + name: ${REDIS_CLUSTER_DATA_VOLUME_4} redis-cluster-data-5: - external: true - name: ${REDIS_CLUSTER_DATA_VOLUME_5} + external: + name: ${REDIS_CLUSTER_DATA_VOLUME_5} diff --git a/docker/docker-compose.redis-cluster.yml b/docker/docker-compose.redis-cluster.yml index 9763b5d79a..766a22bfdd 100644 --- a/docker/docker-compose.redis-cluster.yml +++ b/docker/docker-compose.redis-cluster.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: # Redis cluster diff --git a/docker/docker-compose.redis.volumes.yml b/docker/docker-compose.redis.volumes.yml index 090aa441fe..fb6fee20f4 100644 --- a/docker/docker-compose.redis.volumes.yml +++ b/docker/docker-compose.redis.volumes.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: redis: @@ -23,5 +23,5 @@ services: volumes: redis-data: - external: true - name: ${REDIS_DATA_VOLUME} + external: + name: ${REDIS_DATA_VOLUME} diff --git a/docker/docker-compose.redis.yml b/docker/docker-compose.redis.yml index e53a974134..c1ec7c18a9 100644 --- a/docker/docker-compose.redis.yml +++ b/docker/docker-compose.redis.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: # Redis standalone diff --git a/docker/docker-compose.service-bus.yml b/docker/docker-compose.service-bus.yml index 6e39de0baa..ff099de444 100644 --- a/docker/docker-compose.service-bus.yml +++ b/docker/docker-compose.service-bus.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: tb-js-executor: diff --git a/docker/docker-compose.volumes.yml b/docker/docker-compose.volumes.yml index 58269473e4..8a30d9b566 100644 --- a/docker/docker-compose.volumes.yml +++ b/docker/docker-compose.volumes.yml @@ -14,7 +14,7 @@ # limitations under the License. # -version: '2.2' +version: '3.0' services: tb-core1: @@ -59,23 +59,23 @@ services: volumes: tb-log-volume: - external: true - name: ${TB_LOG_VOLUME} + external: + name: ${TB_LOG_VOLUME} tb-coap-transport-log-volume: - external: true - name: ${TB_COAP_TRANSPORT_LOG_VOLUME} + external: + name: ${TB_COAP_TRANSPORT_LOG_VOLUME} tb-lwm2m-transport-log-volume: - external: true - name: ${TB_LWM2M_TRANSPORT_LOG_VOLUME} + external: + name: ${TB_LWM2M_TRANSPORT_LOG_VOLUME} tb-http-transport-log-volume: - external: true - name: ${TB_HTTP_TRANSPORT_LOG_VOLUME} + external: + name: ${TB_HTTP_TRANSPORT_LOG_VOLUME} tb-mqtt-transport-log-volume: - external: true - name: ${TB_MQTT_TRANSPORT_LOG_VOLUME} + external: + name: ${TB_MQTT_TRANSPORT_LOG_VOLUME} tb-snmp-transport-log-volume: - external: true - name: ${TB_SNMP_TRANSPORT_LOG_VOLUME} + external: + name: ${TB_SNMP_TRANSPORT_LOG_VOLUME} tb-vc-executor-log-volume: - external: true - name: ${TB_VC_EXECUTOR_LOG_VOLUME} + external: + name: ${TB_VC_EXECUTOR_LOG_VOLUME} diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 6ec410fcde..38b2b9b0f3 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -15,7 +15,7 @@ # -version: '2.2' +version: '3.0' services: zookeeper: @@ -30,7 +30,8 @@ services: tb-js-executor: restart: always image: "${DOCKER_REPO}/${JS_EXECUTOR_DOCKER_NAME}:${TB_VERSION}" - scale: 10 + deploy: + replicas: 10 env_file: - tb-js-executor.env tb-core1: diff --git a/docker/docker-install-tb.sh b/docker/docker-install-tb.sh index 628b16e6ee..01eefeb4a8 100755 --- a/docker/docker-install-tb.sh +++ b/docker/docker-install-tb.sh @@ -41,6 +41,8 @@ set -e source compose-utils.sh +COMPOSE_VERSION=$(composeVersion) || exit $? + ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? @@ -52,14 +54,39 @@ ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? checkFolders --create || exit $? if [ ! -z "${ADDITIONAL_STARTUP_SERVICES// }" ]; then - docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ - up -d $ADDITIONAL_STARTUP_SERVICES + + COMPOSE_ARGS="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ + up -d ${ADDITIONAL_STARTUP_SERVICES}" + + case $COMPOSE_VERSION in + V2) + docker compose $COMPOSE_ARGS + ;; + V1) + docker-compose $COMPOSE_ARGS + ;; + *) + # unknown option + ;; + esac fi -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ - run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} \ - tb-core1 +COMPOSE_ARGS="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ + run --no-deps --rm -e INSTALL_TB=true -e LOAD_DEMO=${loadDemo} \ + tb-core1" + +case $COMPOSE_VERSION in + V2) + docker compose $COMPOSE_ARGS + ;; + V1) + docker-compose $COMPOSE_ARGS + ;; + *) + # unknown option + ;; +esac diff --git a/docker/docker-remove-services.sh b/docker/docker-remove-services.sh index 769150c1f3..1e2a55f976 100755 --- a/docker/docker-remove-services.sh +++ b/docker/docker-remove-services.sh @@ -19,6 +19,8 @@ set -e source compose-utils.sh +COMPOSE_VERSION=$(composeVersion) || exit $? + ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? @@ -27,6 +29,18 @@ ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \ - down -v +COMPOSE_ARGS="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} \ + down -v" + +case $COMPOSE_VERSION in + V2) + docker compose $COMPOSE_ARGS + ;; + V1) + docker-compose $COMPOSE_ARGS + ;; + *) + # unknown option + ;; +esac diff --git a/docker/docker-start-services.sh b/docker/docker-start-services.sh index 1a06946308..3603382b4d 100755 --- a/docker/docker-start-services.sh +++ b/docker/docker-start-services.sh @@ -19,6 +19,8 @@ set -e source compose-utils.sh +COMPOSE_VERSION=$(composeVersion) || exit $? + ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? @@ -29,6 +31,18 @@ ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? checkFolders --create || exit $? -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \ - up -d +COMPOSE_ARGS="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} \ + up -d" + +case $COMPOSE_VERSION in + V2) + docker compose $COMPOSE_ARGS + ;; + V1) + docker-compose --compatibility $COMPOSE_ARGS + ;; + *) + # unknown option + ;; +esac diff --git a/docker/docker-stop-services.sh b/docker/docker-stop-services.sh index 5b09aea204..4edd755d43 100755 --- a/docker/docker-stop-services.sh +++ b/docker/docker-stop-services.sh @@ -19,6 +19,8 @@ set -e source compose-utils.sh +COMPOSE_VERSION=$(composeVersion) || exit $? + ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? @@ -27,6 +29,18 @@ ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? ADDITIONAL_COMPOSE_MONITORING_ARGS=$(additionalComposeMonitoringArgs) || exit $? -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS $ADDITIONAL_COMPOSE_MONITORING_ARGS \ - stop +COMPOSE_ARGS="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} ${ADDITIONAL_COMPOSE_MONITORING_ARGS} \ + stop" + +case $COMPOSE_VERSION in + V2) + docker compose $COMPOSE_ARGS + ;; + V1) + docker-compose $COMPOSE_ARGS + ;; + *) + # unknown option + ;; +esac diff --git a/docker/docker-update-service.sh b/docker/docker-update-service.sh index 027280635d..fe2e2b3ce2 100755 --- a/docker/docker-update-service.sh +++ b/docker/docker-update-service.sh @@ -19,15 +19,32 @@ set -e source compose-utils.sh +COMPOSE_VERSION=$(composeVersion) || exit $? + ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? ADDITIONAL_CACHE_ARGS=$(additionalComposeCacheArgs) || exit $? -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ - pull $@ -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ - up -d --no-deps --build $@ +COMPOSE_ARGS_PULL="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ + pull" + +COMPOSE_ARGS_BUILD="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ + up -d --no-deps --build" + +case $COMPOSE_VERSION in + V2) + docker compose $COMPOSE_ARGS_PULL $@ + docker compose $COMPOSE_ARGS_BUILD $@ + ;; + V1) + docker-compose $COMPOSE_ARGS_PULL $@ + docker-compose $COMPOSE_ARGS_BUILD $@ + ;; + *) + # unknown option + ;; +esac diff --git a/docker/docker-upgrade-tb.sh b/docker/docker-upgrade-tb.sh index 87dff9da15..18ce476d62 100755 --- a/docker/docker-upgrade-tb.sh +++ b/docker/docker-upgrade-tb.sh @@ -40,6 +40,8 @@ set -e source compose-utils.sh +COMPOSE_VERSION=$(composeVersion) || exit $? + ADDITIONAL_COMPOSE_QUEUE_ARGS=$(additionalComposeQueueArgs) || exit $? ADDITIONAL_COMPOSE_ARGS=$(additionalComposeArgs) || exit $? @@ -50,16 +52,32 @@ ADDITIONAL_STARTUP_SERVICES=$(additionalStartupServices) || exit $? checkFolders --create || exit $? -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ - pull \ - tb-core1 +COMPOSE_ARGS_PULL="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ + pull \ + tb-core1" + +COMPOSE_ARGS_UP="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ + up -d ${ADDITIONAL_STARTUP_SERVICES}" -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ - up -d $ADDITIONAL_STARTUP_SERVICES +COMPOSE_ARGS_RUN="\ + -f docker-compose.yml ${ADDITIONAL_CACHE_ARGS} ${ADDITIONAL_COMPOSE_ARGS} ${ADDITIONAL_COMPOSE_QUEUE_ARGS} \ + run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} \ + tb-core1" -docker-compose \ - -f docker-compose.yml $ADDITIONAL_CACHE_ARGS $ADDITIONAL_COMPOSE_ARGS $ADDITIONAL_COMPOSE_QUEUE_ARGS \ - run --no-deps --rm -e UPGRADE_TB=true -e FROM_VERSION=${fromVersion} \ - tb-core1 +case $COMPOSE_VERSION in + V2) + docker compose $COMPOSE_ARGS_PULL + docker compose $COMPOSE_ARGS_UP + docker compose $COMPOSE_ARGS_RUN + ;; + V1) + docker-compose $COMPOSE_ARGS_PULL + docker-compose $COMPOSE_ARGS_UP + docker-compose $COMPOSE_ARGS_RUN + ;; + *) + # unknown option + ;; +esac diff --git a/docker/tb-js-executor.env b/docker/tb-js-executor.env index e080906549..1938449d53 100644 --- a/docker/tb-js-executor.env +++ b/docker/tb-js-executor.env @@ -3,4 +3,5 @@ LOGGER_LEVEL=info LOG_FOLDER=logs LOGGER_FILENAME=tb-js-executor-%DATE%.log DOCKER_MODE=true -SCRIPT_BODY_TRACE_FREQUENCY=1000 \ No newline at end of file +SCRIPT_BODY_TRACE_FREQUENCY=1000 +NODE_OPTIONS="--max-old-space-size=200" diff --git a/lombok.config b/lombok.config index d904701090..1b8f891cd9 100644 --- a/lombok.config +++ b/lombok.config @@ -1,2 +1,3 @@ config.stopbubbling = true lombok.anyconstructor.addconstructorproperties = true +lombok.copyableAnnotations += org.springframework.context.annotation.Lazy diff --git a/msa/black-box-tests/README.md b/msa/black-box-tests/README.md index a45badf44a..a60e7405a8 100644 --- a/msa/black-box-tests/README.md +++ b/msa/black-box-tests/README.md @@ -30,5 +30,9 @@ As result, in REPOSITORY column, next images should be present: mvn clean install -DblackBoxTests.skip=false -DblackBoxTests.hybridMode=true +To run the black box tests with using local env run tests in the [msa/black-box-tests](../black-box-tests) directory with runLocal property: + + mvn clean install -DblackBoxTests.skip=false -DrunLocal=true + diff --git a/msa/black-box-tests/pom.xml b/msa/black-box-tests/pom.xml index a1c745e163..74c97fc87d 100644 --- a/msa/black-box-tests/pom.xml +++ b/msa/black-box-tests/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa @@ -57,11 +57,6 @@ httpclient test - - io.takari.junit - takari-cpsuite - test - org.springframework.boot spring-boot-starter-test @@ -72,11 +67,35 @@ junit-vintage-engine test + + org.testng + testng + test + + + org.assertj + assertj-core + test + + + io.rest-assured + rest-assured + test + + + org.hamcrest + hamcrest-all + test + org.awaitility awaitility test + + org.eclipse.californium + californium-core + ch.qos.logback logback-classic @@ -145,6 +164,12 @@ snmp docker-info + + org.thingsboard.common + message + ${project.version} + test + @@ -152,11 +177,18 @@ org.apache.maven.plugins maven-surefire-plugin - - **/*TestSuite.java - + + src/test/resources/testNG.xml + ${blackBoxTests.skip} + + + org.apache.maven.surefire + surefire-testng + ${surefire.version} + + diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java index 07529baead..d259d46b43 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/AbstractContainerTest.java @@ -15,115 +15,69 @@ */ package org.thingsboard.server.msa; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.google.common.collect.ImmutableMap; import com.google.gson.JsonArray; import com.google.gson.JsonObject; -import com.google.gson.JsonParser; import lombok.extern.slf4j.Slf4j; -import org.apache.http.config.Registry; -import org.apache.http.config.RegistryBuilder; -import org.apache.http.conn.socket.ConnectionSocketFactory; -import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.TrustStrategy; -import org.apache.http.impl.client.CloseableHttpClient; -import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.ssl.SSLContextBuilder; import org.apache.http.ssl.SSLContexts; -import org.junit.BeforeClass; -import org.junit.Rule; -import org.junit.rules.TestRule; -import org.junit.rules.TestWatcher; -import org.junit.runner.Description; -import org.springframework.http.client.HttpComponentsClientHttpRequestFactory; -import org.thingsboard.rest.client.RestClient; -import org.thingsboard.server.common.data.Device; +import org.testng.annotations.AfterSuite; +import org.testng.annotations.BeforeSuite; +import org.testng.annotations.Listeners; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.StringUtils; +import org.thingsboard.server.common.data.device.profile.AllowCreateNewDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DeviceProfileData; +import org.thingsboard.server.common.data.device.profile.DeviceProfileProvisionConfiguration; +import org.thingsboard.server.common.data.device.profile.DisabledDeviceProfileProvisionConfiguration; import org.thingsboard.server.common.data.id.DeviceId; -import org.thingsboard.server.msa.mapper.WsTelemetryResponse; -import javax.net.ssl.SSLContext; import java.net.URI; -import java.util.List; import java.util.Map; import java.util.Random; + @Slf4j +@Listeners(TestListener.class) public abstract class AbstractContainerTest { - protected static final String HTTPS_URL = "https://localhost"; - protected static final String WSS_URL = "wss://localhost"; - protected static String TB_TOKEN; - protected static RestClient restClient; + protected final static String TEST_PROVISION_DEVICE_KEY = "test_provision_key"; + protected final static String TEST_PROVISION_DEVICE_SECRET = "test_provision_secret"; protected static long timeoutMultiplier = 1; - protected ObjectMapper mapper = new ObjectMapper(); - protected JsonParser jsonParser = new JsonParser(); - - @BeforeClass - public static void before() throws Exception { - restClient = new RestClient(HTTPS_URL); - restClient.getRestTemplate().setRequestFactory(getRequestFactoryForSelfSignedCert()); + private static final ContainerTestSuite containerTestSuite = ContainerTestSuite.getInstance(); + protected static TestRestClient testRestClient; + @BeforeSuite + public void beforeSuite() { + if ("false".equals(System.getProperty("runLocal", "false"))) { + containerTestSuite.start(); + } + testRestClient = new TestRestClient(TestProperties.getBaseUrl()); if (!"kafka".equals(System.getProperty("blackBoxTests.queue", "kafka"))) { timeoutMultiplier = 10; } } - @Rule - public TestRule watcher = new TestWatcher() { - protected void starting(Description description) { - log.info("================================================="); - log.info("STARTING TEST: {}" , description.getMethodName()); - log.info("================================================="); - } - - /** - * Invoked when a test succeeds - */ - protected void succeeded(Description description) { - log.info("================================================="); - log.info("SUCCEEDED TEST: {}" , description.getMethodName()); - log.info("================================================="); - } - - /** - * Invoked when a test fails - */ - protected void failed(Throwable e, Description description) { - log.info("================================================="); - log.info("FAILED TEST: {}" , description.getMethodName(), e); - log.info("================================================="); + @AfterSuite + public void afterSuite() { + if (containerTestSuite.isActive()) { + containerTestSuite.stop(); } - }; - - protected Device createGatewayDevice() throws JsonProcessingException { - String isGateway = "{\"gateway\":true}"; - ObjectMapper objectMapper = new ObjectMapper(); - JsonNode additionalInfo = objectMapper.readTree(isGateway); - Device gatewayDeviceTemplate = new Device(); - gatewayDeviceTemplate.setName("mqtt_gateway"); - gatewayDeviceTemplate.setType("gateway"); - gatewayDeviceTemplate.setAdditionalInfo(additionalInfo); - return restClient.saveDevice(gatewayDeviceTemplate); - } - - protected Device createDevice(String name) { - Device device = new Device(); - device.setName(name + StringUtils.randomAlphanumeric(7)); - device.setType("DEFAULT"); - return restClient.saveDevice(device); } protected WsClient subscribeToWebSocket(DeviceId deviceId, String scope, CmdsType property) throws Exception { - WsClient wsClient = new WsClient(new URI(WSS_URL + "/api/ws/plugins/telemetry?token=" + restClient.getToken()), timeoutMultiplier); - SSLContextBuilder builder = SSLContexts.custom(); - builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); - wsClient.setSocketFactory(builder.build().getSocketFactory()); + String webSocketUrl = TestProperties.getWebSocketUrl(); + WsClient wsClient = new WsClient(new URI(webSocketUrl + "/api/ws/plugins/telemetry?token=" + testRestClient.getToken()), timeoutMultiplier); + if (webSocketUrl.matches("^(wss)://.*$")) { + SSLContextBuilder builder = SSLContexts.custom(); + builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); + wsClient.setSocketFactory(builder.build().getSocketFactory()); + } wsClient.connectBlocking(); JsonObject cmdsObject = new JsonObject(); @@ -150,16 +104,6 @@ public abstract class AbstractContainerTest { .build(); } - protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, Long expectedTs, String expectedValue) { - List list = wsTelemetryResponse.getDataValuesByKey(key); - return expectedTs.equals(list.get(0)) && expectedValue.equals(list.get(1)); - } - - protected boolean verify(WsTelemetryResponse wsTelemetryResponse, String key, String expectedValue) { - List list = wsTelemetryResponse.getDataValuesByKey(key); - return expectedValue.equals(list.get(1)); - } - protected JsonObject createGatewayConnectPayload(String deviceName){ JsonObject payload = new JsonObject(); payload.addProperty("device", deviceName); @@ -216,20 +160,28 @@ public abstract class AbstractContainerTest { } } - private static HttpComponentsClientHttpRequestFactory getRequestFactoryForSelfSignedCert() throws Exception { - SSLContextBuilder builder = SSLContexts.custom(); - builder.loadTrustMaterial(null, (TrustStrategy) (chain, authType) -> true); - SSLContext sslContext = builder.build(); - SSLConnectionSocketFactory sslSelfSigned = new SSLConnectionSocketFactory(sslContext, (s, sslSession) -> true); - - Registry socketFactoryRegistry = RegistryBuilder - .create() - .register("https", sslSelfSigned) - .build(); - - PoolingHttpClientConnectionManager cm = new PoolingHttpClientConnectionManager(socketFactoryRegistry); - CloseableHttpClient httpClient = HttpClients.custom().setConnectionManager(cm).build(); - return new HttpComponentsClientHttpRequestFactory(httpClient); + protected DeviceProfile updateDeviceProfileWithProvisioningStrategy(DeviceProfile deviceProfile, DeviceProfileProvisionType provisionType) { + DeviceProfileProvisionConfiguration provisionConfiguration; + String testProvisionDeviceKey = TEST_PROVISION_DEVICE_KEY; + deviceProfile.setProvisionType(provisionType); + switch(provisionType) { + case ALLOW_CREATE_NEW_DEVICES: + provisionConfiguration = new AllowCreateNewDevicesDeviceProfileProvisionConfiguration(TEST_PROVISION_DEVICE_SECRET); + break; + case CHECK_PRE_PROVISIONED_DEVICES: + provisionConfiguration = new CheckPreProvisionedDevicesDeviceProfileProvisionConfiguration(TEST_PROVISION_DEVICE_SECRET); + break; + default: + case DISABLED: + testProvisionDeviceKey = null; + provisionConfiguration = new DisabledDeviceProfileProvisionConfiguration(null); + break; + } + DeviceProfileData deviceProfileData = deviceProfile.getProfileData(); + deviceProfileData.setProvisionConfiguration(provisionConfiguration); + deviceProfile.setProfileData(deviceProfileData); + deviceProfile.setProvisionDeviceKey(testProvisionDeviceKey); + return testRestClient.postDeviceProfile(deviceProfile); } } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java index 47acbde16e..2ff7088fa0 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ContainerTestSuite.java @@ -17,9 +17,6 @@ package org.thingsboard.server.msa; import lombok.extern.slf4j.Slf4j; import org.apache.commons.io.FileUtils; -import org.junit.ClassRule; -import org.junit.extensions.cpsuite.ClasspathSuite; -import org.junit.runner.RunWith; import org.testcontainers.containers.DockerComposeContainer; import org.testcontainers.containers.wait.strategy.Wait; import org.thingsboard.server.common.data.StringUtils; @@ -41,10 +38,8 @@ import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.Assert.fail; +import static org.testng.Assert.fail; -@RunWith(ClasspathSuite.class) -@ClasspathSuite.ClassnameFilters({"org.thingsboard.server.msa.*Test"}) @Slf4j public class ContainerTestSuite { final static boolean IS_REDIS_CLUSTER = Boolean.parseBoolean(System.getProperty("blackBoxTests.redisCluster")); @@ -57,107 +52,133 @@ public class ContainerTestSuite { private static final String TB_JS_EXECUTOR_LOG_REGEXP = ".*template started.*"; private static final Duration CONTAINER_STARTUP_TIMEOUT = Duration.ofSeconds(400); - private static DockerComposeContainer testContainer; - - @ClassRule - public static ThingsBoardDbInstaller installTb = new ThingsBoardDbInstaller(); - - @ClassRule - public static DockerComposeContainer getTestContainer() { - if (testContainer == null) { - log.info("System property of blackBoxTests.redisCluster is {}", IS_REDIS_CLUSTER); - log.info("System property of blackBoxTests.hybridMode is {}", IS_HYBRID_MODE); - boolean skipTailChildContainers = Boolean.valueOf(System.getProperty("blackBoxTests.skipTailChildContainers")); - try { - final String targetDir = FileUtils.getTempDirectoryPath() + "/" + "ContainerTestSuite-" + UUID.randomUUID() + "/"; - log.info("targetDir {}", targetDir); - FileUtils.copyDirectory(new File(SOURCE_DIR), new File(targetDir)); - replaceInFile(targetDir + "docker-compose.yml", " container_name: \"${LOAD_BALANCER_NAME}\"", "", "container_name"); - - FileUtils.copyDirectory(new File("src/test/resources"), new File(targetDir)); - - class DockerComposeContainerImpl> extends DockerComposeContainer { - public DockerComposeContainerImpl(List composeFiles) { - super(composeFiles); - } - - @Override - public void stop() { - super.stop(); - tryDeleteDir(targetDir); - } - } + private DockerComposeContainer testContainer; + private ThingsBoardDbInstaller installTb; + private boolean isActive; + + private static ContainerTestSuite containerTestSuite; + + public boolean isActive() { + return isActive; + } + + public void setActive(boolean active) { + isActive = active; + } + + private ContainerTestSuite() { + } + + public static ContainerTestSuite getInstance() { + if (containerTestSuite == null) { + containerTestSuite = new ContainerTestSuite(); + } + return containerTestSuite; + } + + public void start() { + installTb = new ThingsBoardDbInstaller(); + installTb.createVolumes(); + log.info("System property of blackBoxTests.redisCluster is {}", IS_REDIS_CLUSTER); + log.info("System property of blackBoxTests.hybridMode is {}", IS_HYBRID_MODE); + boolean skipTailChildContainers = Boolean.valueOf(System.getProperty("blackBoxTests.skipTailChildContainers")); + try { + final String targetDir = FileUtils.getTempDirectoryPath() + "/" + "ContainerTestSuite-" + UUID.randomUUID() + "/"; + log.info("targetDir {}", targetDir); + FileUtils.copyDirectory(new File(SOURCE_DIR), new File(targetDir)); + replaceInFile(targetDir + "docker-compose.yml", " container_name: \"${LOAD_BALANCER_NAME}\"", "", "container_name"); - List composeFiles = new ArrayList<>(Arrays.asList( - new File(targetDir + "docker-compose.yml"), - new File(targetDir + "docker-compose.volumes.yml"), - new File(targetDir + (IS_HYBRID_MODE ? "docker-compose.hybrid.yml" : "docker-compose.postgres.yml")), - new File(targetDir + "docker-compose.postgres.volumes.yml"), - new File(targetDir + "docker-compose." + QUEUE_TYPE + ".yml"), - new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.yml" : "docker-compose.redis.yml")), - new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.volumes.yml" : "docker-compose.redis.volumes.yml")) - )); - - Map queueEnv = new HashMap<>(); - queueEnv.put("TB_QUEUE_TYPE", QUEUE_TYPE); - switch (QUEUE_TYPE) { - case "kafka": - composeFiles.add(new File(targetDir + "docker-compose.kafka.yml")); - break; - case "aws-sqs": - replaceInFile(targetDir, "queue-aws-sqs.env", - Map.of("YOUR_KEY", getSysProp("blackBoxTests.awsKey"), - "YOUR_SECRET", getSysProp("blackBoxTests.awsSecret"), - "YOUR_REGION", getSysProp("blackBoxTests.awsRegion"))); - break; - case "rabbitmq": - composeFiles.add(new File(targetDir + "docker-compose.rabbitmq-server.yml")); - replaceInFile(targetDir, "queue-rabbitmq.env", - Map.of("localhost", "rabbitmq")); - break; - case "service-bus": - replaceInFile(targetDir, "queue-service-bus.env", - Map.of("YOUR_NAMESPACE_NAME", getSysProp("blackBoxTests.serviceBusNamespace"), - "YOUR_SAS_KEY_NAME", getSysProp("blackBoxTests.serviceBusSASPolicy"))); - replaceInFile(targetDir, "queue-service-bus.env", - Map.of("YOUR_SAS_KEY", getSysProp("blackBoxTests.serviceBusPrimaryKey"))); - break; - case "pubsub": - replaceInFile(targetDir, "queue-pubsub.env", - Map.of("YOUR_PROJECT_ID", getSysProp("blackBoxTests.pubSubProjectId"), - "YOUR_SERVICE_ACCOUNT", getSysProp("blackBoxTests.pubSubServiceAccount"))); - break; - default: - throw new RuntimeException("Unsupported queue type: " + QUEUE_TYPE); + FileUtils.copyDirectory(new File("src/test/resources"), new File(targetDir)); + + class DockerComposeContainerImpl> extends DockerComposeContainer { + public DockerComposeContainerImpl(List composeFiles) { + super(composeFiles); } - if (IS_HYBRID_MODE) { - composeFiles.add(new File(targetDir + "docker-compose.cassandra.volumes.yml")); + @Override + public void stop() { + super.stop(); + tryDeleteDir(targetDir); } + } - testContainer = new DockerComposeContainerImpl<>(composeFiles) - .withPull(false) - .withLocalCompose(true) - .withTailChildContainers(!skipTailChildContainers) - .withEnv(installTb.getEnv()) - .withEnv(queueEnv) - .withEnv("LOAD_BALANCER_NAME", "") - .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-core1", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-core2", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-http-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-http-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-mqtt-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-mqtt-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-vc-executor1", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-vc-executor2", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) - .waitingFor("tb-js-executor", Wait.forLogMessage(TB_JS_EXECUTOR_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)); - } catch (Exception e) { - log.error("Failed to create test container", e); - fail("Failed to create test container"); + List composeFiles = new ArrayList<>(Arrays.asList( + new File(targetDir + "docker-compose.yml"), + new File(targetDir + "docker-compose.volumes.yml"), + new File(targetDir + (IS_HYBRID_MODE ? "docker-compose.hybrid.yml" : "docker-compose.postgres.yml")), + new File(targetDir + "docker-compose.postgres.volumes.yml"), + new File(targetDir + "docker-compose." + QUEUE_TYPE + ".yml"), + new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.yml" : "docker-compose.redis.yml")), + new File(targetDir + (IS_REDIS_CLUSTER ? "docker-compose.redis-cluster.volumes.yml" : "docker-compose.redis.volumes.yml")) + )); + + Map queueEnv = new HashMap<>(); + queueEnv.put("TB_QUEUE_TYPE", QUEUE_TYPE); + switch (QUEUE_TYPE) { + case "kafka": + composeFiles.add(new File(targetDir + "docker-compose.kafka.yml")); + break; + case "aws-sqs": + replaceInFile(targetDir, "queue-aws-sqs.env", + Map.of("YOUR_KEY", getSysProp("blackBoxTests.awsKey"), + "YOUR_SECRET", getSysProp("blackBoxTests.awsSecret"), + "YOUR_REGION", getSysProp("blackBoxTests.awsRegion"))); + break; + case "rabbitmq": + composeFiles.add(new File(targetDir + "docker-compose.rabbitmq-server.yml")); + replaceInFile(targetDir, "queue-rabbitmq.env", + Map.of("localhost", "rabbitmq")); + break; + case "service-bus": + replaceInFile(targetDir, "queue-service-bus.env", + Map.of("YOUR_NAMESPACE_NAME", getSysProp("blackBoxTests.serviceBusNamespace"), + "YOUR_SAS_KEY_NAME", getSysProp("blackBoxTests.serviceBusSASPolicy"))); + replaceInFile(targetDir, "queue-service-bus.env", + Map.of("YOUR_SAS_KEY", getSysProp("blackBoxTests.serviceBusPrimaryKey"))); + break; + case "pubsub": + replaceInFile(targetDir, "queue-pubsub.env", + Map.of("YOUR_PROJECT_ID", getSysProp("blackBoxTests.pubSubProjectId"), + "YOUR_SERVICE_ACCOUNT", getSysProp("blackBoxTests.pubSubServiceAccount"))); + break; + default: + throw new RuntimeException("Unsupported queue type: " + QUEUE_TYPE); } + + if (IS_HYBRID_MODE) { + composeFiles.add(new File(targetDir + "docker-compose.cassandra.volumes.yml")); + } + + testContainer = new DockerComposeContainerImpl<>(composeFiles) + .withPull(false) + .withLocalCompose(true) + .withTailChildContainers(!skipTailChildContainers) + .withEnv(installTb.getEnv()) + .withEnv(queueEnv) + .withEnv("LOAD_BALANCER_NAME", "") + .withExposedService("haproxy", 80, Wait.forHttp("/swagger-ui.html").withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-core1", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-core2", Wait.forLogMessage(TB_CORE_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-http-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-http-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-mqtt-transport1", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-mqtt-transport2", Wait.forLogMessage(TRANSPORTS_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-vc-executor1", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-vc-executor2", Wait.forLogMessage(TB_VC_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)) + .waitingFor("tb-js-executor", Wait.forLogMessage(TB_JS_EXECUTOR_LOG_REGEXP, 1).withStartupTimeout(CONTAINER_STARTUP_TIMEOUT)); + testContainer.start(); + setActive(true); + } catch (Exception e) { + log.error("Failed to create test container", e); + fail("Failed to create test container"); + } + } + public void stop() { + if (isActive) { + testContainer.stop(); + installTb.savaLogsAndRemoveVolumes(); + setActive(false); } - return testContainer; } private static void replaceInFile(String targetDir, String fileName, Map replacements) throws IOException { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java new file mode 100644 index 0000000000..e00fdeebfd --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClient.java @@ -0,0 +1,121 @@ +/** + * 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.msa; + +import java.io.IOException; +import org.eclipse.californium.core.CoapClient; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapObserveRelation; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; +import org.eclipse.californium.core.coap.MediaTypeRegistry; +import org.eclipse.californium.core.coap.Request; +import org.eclipse.californium.elements.exception.ConnectorException; +import org.thingsboard.server.common.msg.session.FeatureType; + +public class TestCoapClient { + + private static final String COAP_BASE_URL = "coap://localhost:5683/api/v1/"; + private static final long CLIENT_REQUEST_TIMEOUT = 60000L; + + private final CoapClient client; + + public TestCoapClient(){ + this.client = createClient(); + } + + public TestCoapClient(String accessToken, FeatureType featureType) { + this.client = createClient(getFeatureTokenUrl(accessToken, featureType)); + } + + public TestCoapClient(String featureTokenUrl) { + this.client = createClient(featureTokenUrl); + } + + public void connectToCoap(String accessToken) { + setURI(accessToken, null); + } + + public void connectToCoap(String accessToken, FeatureType featureType) { + setURI(accessToken, featureType); + } + + public void disconnect() { + if (client != null) { + client.shutdown(); + } + } + + public CoapResponse postMethod(String requestBody) throws ConnectorException, IOException { + return this.postMethod(requestBody.getBytes()); + } + + public CoapResponse postMethod(byte[] requestBodyBytes) throws ConnectorException, IOException { + return client.setTimeout(CLIENT_REQUEST_TIMEOUT).post(requestBodyBytes, MediaTypeRegistry.APPLICATION_JSON); + } + + public void postMethod(CoapHandler handler, String payload, int format) { + client.post(handler, payload, format); + } + + public void postMethod(CoapHandler handler, byte[] payload, int format) { + client.post(handler, payload, format); + } + + public CoapResponse getMethod() throws ConnectorException, IOException { + return client.setTimeout(CLIENT_REQUEST_TIMEOUT).get(); + } + + public CoapObserveRelation getObserveRelation(TestCoapClientCallback callback){ + Request request = Request.newGet().setObserve(); + request.setType(CoAP.Type.CON); + return client.observe(request, callback); + } + + public void setURI(String featureTokenUrl) { + if (client == null) { + throw new RuntimeException("Failed to connect! CoapClient is not initialized!"); + } + client.setURI(featureTokenUrl); + } + + public void setURI(String accessToken, FeatureType featureType) { + if (featureType == null){ + featureType = FeatureType.ATTRIBUTES; + } + setURI(getFeatureTokenUrl(accessToken, featureType)); + } + + private CoapClient createClient() { + return new CoapClient(); + } + + private CoapClient createClient(String featureTokenUrl) { + return new CoapClient(featureTokenUrl); + } + + public static String getFeatureTokenUrl(FeatureType featureType) { + return COAP_BASE_URL + featureType.name().toLowerCase(); + } + + public static String getFeatureTokenUrl(String token, FeatureType featureType) { + return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase(); + } + + public static String getFeatureTokenUrl(String token, FeatureType featureType, int requestId) { + return COAP_BASE_URL + token + "/" + featureType.name().toLowerCase() + "/" + requestId; + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java new file mode 100644 index 0000000000..b31f9c2905 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestCoapClientCallback.java @@ -0,0 +1,68 @@ +/** + * 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.msa; + +import lombok.Data; +import lombok.extern.slf4j.Slf4j; +import org.eclipse.californium.core.CoapHandler; +import org.eclipse.californium.core.CoapResponse; +import org.eclipse.californium.core.coap.CoAP; + +import java.util.concurrent.CountDownLatch; + +@Slf4j +@Data +public class TestCoapClientCallback implements CoapHandler { + + protected final CountDownLatch latch; + protected Integer observe; + protected byte[] payloadBytes; + protected CoAP.ResponseCode responseCode; + + public TestCoapClientCallback() { + this.latch = new CountDownLatch(1); + } + + public TestCoapClientCallback(int subscribeCount) { + this.latch = new CountDownLatch(subscribeCount); + } + + public Integer getObserve() { + return observe; + } + + public byte[] getPayloadBytes() { + return payloadBytes; + } + + public CoAP.ResponseCode getResponseCode() { + return responseCode; + } + + @Override + public void onLoad(CoapResponse response) { + observe = response.getOptions().getObserve(); + payloadBytes = response.getPayload(); + responseCode = response.getCode(); + latch.countDown(); + } + + @Override + public void onError() { + log.warn("Command Response Ack Error, No connect"); + } + +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestListener.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestListener.java new file mode 100644 index 0000000000..51bc75c86a --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestListener.java @@ -0,0 +1,53 @@ +/** + * 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.msa; + +import lombok.extern.slf4j.Slf4j; +import org.testng.ITestContext; +import org.testng.ITestResult; +import org.testng.TestListenerAdapter; + +import static org.testng.internal.Utils.log; + +@Slf4j +public class TestListener extends TestListenerAdapter { + + @Override + public void onTestStart(ITestResult result) { + super.onTestStart(result); + log.info("===>>> Test started: " + result.getName()); + } + + /** + * Invoked when a test succeeds + */ + @Override + public void onTestSuccess(ITestResult result) { + super.onTestSuccess(result); + if (result != null) { + log.info("<<<=== Test completed successfully: " + result.getName()); + } + } + + /** + * Invoked when a test fails + */ + @Override + public void onTestFailure(ITestResult result) { + super.onTestFailure(result); + log.info("<<<=== Test failed: " + result.getName()); + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.java new file mode 100644 index 0000000000..020dbf8b93 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestProperties.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.server.msa; + +import lombok.extern.slf4j.Slf4j; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Properties; + +@Slf4j +public class TestProperties { + + private static final String HTTPS_URL = "https://localhost"; + + private static final String WSS_URL = "wss://localhost"; + + private static final ContainerTestSuite instance = ContainerTestSuite.getInstance(); + + private static Properties properties; + + public static String getBaseUrl() { + if (instance.isActive()) { + return HTTPS_URL; + } + return getProperties().getProperty("tb.baseUrl"); + } + + public static String getWebSocketUrl() { + if (instance.isActive()) { + return WSS_URL; + } + return getProperties().getProperty("tb.wsUrl"); + } + + private static Properties getProperties() { + if (properties == null) { + try (InputStream input = TestProperties.class.getClassLoader().getResourceAsStream("config.properties")) { + properties = new Properties(); + properties.load(input); + } catch (IOException ex) { + log.error("Exception while reading test properties " + ex.getMessage()); + } + } + return properties; + } + +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java new file mode 100644 index 0000000000..138c74e9b6 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/TestRestClient.java @@ -0,0 +1,299 @@ +/** + * 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.msa; + +import com.fasterxml.jackson.databind.JsonNode; +import io.restassured.RestAssured; +import io.restassured.common.mapper.TypeRef; +import io.restassured.config.HeaderConfig; +import io.restassured.config.RestAssuredConfig; +import io.restassured.filter.log.RequestLoggingFilter; +import io.restassured.filter.log.ResponseLoggingFilter; +import io.restassured.http.ContentType; +import io.restassured.path.json.JsonPath; +import io.restassured.response.ValidatableResponse; +import io.restassured.specification.RequestSpecification; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.DeviceProfileId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.RuleChainId; +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.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.security.DeviceCredentials; + +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import static io.restassured.RestAssured.given; +import static java.net.HttpURLConnection.HTTP_NOT_FOUND; +import static java.net.HttpURLConnection.HTTP_OK; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.core.AnyOf.anyOf; +import static org.thingsboard.server.common.data.StringUtils.isEmpty; + +public class TestRestClient { + private static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; + private static final String CONTENT_TYPE_HEADER = "Content-Type"; + private final RequestSpecification requestSpec; + private String token; + private String refreshToken; + + public TestRestClient(String url) { + RestAssured.filters(new RequestLoggingFilter(), new ResponseLoggingFilter()); + + requestSpec = given().baseUri(url) + .contentType(ContentType.JSON) + .config(RestAssuredConfig.config() + .headerConfig(HeaderConfig.headerConfig() + .overwriteHeadersWithName(JWT_TOKEN_HEADER_PARAM, CONTENT_TYPE_HEADER))); + + if (url.matches("^(https)://.*$")) { + requestSpec.relaxedHTTPSValidation(); + } + } + + public void login(String username, String password) { + Map loginRequest = new HashMap<>(); + loginRequest.put("username", username); + loginRequest.put("password", password); + + JsonPath jsonPath = given().spec(requestSpec).body(loginRequest) + .post( "/api/auth/login") + .getBody().jsonPath(); + token = jsonPath.get("token"); + refreshToken = jsonPath.get("refreshToken"); + requestSpec.header(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); + } + + public Device postDevice(String accessToken, Device device) { + return given().spec(requestSpec).body(device) + .pathParams("accessToken", accessToken) + .post("/api/device?accessToken={accessToken}") + .then() + .statusCode(HTTP_OK) + .extract() + .as(Device.class); + } + + public Device getDeviceByName(String deviceName) { + return given().spec(requestSpec).pathParam("deviceName", deviceName) + .get("/api/tenant/devices?deviceName={deviceName}") + .then() + .statusCode(HTTP_OK) + .extract() + .as(Device.class); + } + + public ValidatableResponse getDeviceById(DeviceId deviceId, int statusCode) { + return given().spec(requestSpec) + .pathParams("deviceId", deviceId.getId()) + .get("/api/device/{deviceId}") + .then() + .statusCode(statusCode); + } + public Device getDeviceById(DeviceId deviceId) { + return getDeviceById(deviceId, HTTP_OK) + .extract() + .as(Device.class); + } + public DeviceCredentials getDeviceCredentialsByDeviceId(DeviceId deviceId) { + return given().spec(requestSpec).get("/api/device/{deviceId}/credentials", deviceId.getId()) + .then() + .assertThat() + .statusCode(HTTP_OK) + .extract() + .as(DeviceCredentials.class); + } + + public ValidatableResponse postTelemetry(String credentialsId, JsonNode telemetry) { + return given().spec(requestSpec).body(telemetry) + .post("/api/v1/{credentialsId}/telemetry", credentialsId) + .then() + .statusCode(HTTP_OK); + } + + public ValidatableResponse deleteDevice(DeviceId deviceId) { + return given().spec(requestSpec) + .delete("/api/device/{deviceId}", deviceId.getId()) + .then() + .statusCode(HTTP_OK); + } + public ValidatableResponse deleteDeviceIfExists(DeviceId deviceId) { + return given().spec(requestSpec) + .delete("/api/device/{deviceId}", deviceId.getId()) + .then() + .statusCode(anyOf(is(HTTP_OK),is(HTTP_NOT_FOUND))); + } + + public ValidatableResponse postTelemetryAttribute(String entityType, DeviceId deviceId, String scope, JsonNode attribute) { + return given().spec(requestSpec).body(attribute) + .post("/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", entityType, deviceId.getId(), scope) + .then() + .statusCode(HTTP_OK); + } + + public ValidatableResponse postAttribute(String accessToken, JsonNode attribute) { + return given().spec(requestSpec).body(attribute) + .post("/api/v1/{accessToken}/attributes/", accessToken) + .then() + .statusCode(HTTP_OK); + } + + public JsonNode getAttributes(String accessToken, String clientKeys, String sharedKeys) { + return given().spec(requestSpec) + .queryParam("clientKeys", clientKeys) + .queryParam("sharedKeys", sharedKeys) + .get("/api/v1/{accessToken}/attributes", accessToken) + .then() + .statusCode(HTTP_OK) + .extract() + .as(JsonNode.class); + } + + public JsonPath postProvisionRequest(String provisionRequest) { + return given().spec(requestSpec) + .body(provisionRequest) + .post("/api/v1/provision") + .getBody() + .jsonPath(); + } + + public PageData getRuleChains(PageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return given().spec(requestSpec).queryParams(params) + .get("/api/ruleChains") + .then() + .statusCode(HTTP_OK) + .extract() + .as(new TypeRef>() {}); + } + + public RuleChain postRootRuleChain(RuleChain ruleChain) { + return given().spec(requestSpec) + .body(ruleChain) + .post("/api/ruleChain") + .then() + .statusCode(HTTP_OK) + .extract() + .as(RuleChain.class); + } + + public RuleChainMetaData postRuleChainMetadata(RuleChainMetaData ruleChainMetaData) { + return given().spec(requestSpec) + .body(ruleChainMetaData) + .post("/api/ruleChain/metadata") + .then() + .statusCode(HTTP_OK) + .extract() + .as(RuleChainMetaData.class); + } + + public void setRootRuleChain(RuleChainId ruleChainId) { + given().spec(requestSpec) + .post("/api/ruleChain/{ruleChainId}/root", ruleChainId.getId()) + .then() + .statusCode(HTTP_OK); + } + + public void deleteRuleChain(RuleChainId ruleChainId) { + given().spec(requestSpec) + .delete("/api/ruleChain/{ruleChainId}", ruleChainId.getId()) + .then() + .statusCode(HTTP_OK); + } + + private String getUrlParams(PageLink pageLink) { + String urlParams = "pageSize={pageSize}&page={page}"; + if (!isEmpty(pageLink.getTextSearch())) { + urlParams += "&textSearch={textSearch}"; + } + if (pageLink.getSortOrder() != null) { + urlParams += "&sortProperty={sortProperty}&sortOrder={sortOrder}"; + } + return urlParams; + } + + private void addPageLinkToParam(Map params, PageLink pageLink) { + params.put("pageSize", String.valueOf(pageLink.getPageSize())); + params.put("page", String.valueOf(pageLink.getPage())); + if (!isEmpty(pageLink.getTextSearch())) { + params.put("textSearch", pageLink.getTextSearch()); + } + if (pageLink.getSortOrder() != null) { + params.put("sortProperty", pageLink.getSortOrder().getProperty()); + params.put("sortOrder", pageLink.getSortOrder().getDirection().name()); + } + } + + public List findRelationByFrom(EntityId fromId, RelationTypeGroup relationTypeGroup) { + Map params = new HashMap<>(); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); + + return given().spec(requestSpec) + .pathParams(params) + .get("/api/relations?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}") + .then() + .statusCode(HTTP_OK) + .extract() + .as(new TypeRef>() {}); + } + + public JsonNode postServerSideRpc(DeviceId deviceId, JsonNode serverRpcPayload) { + return given().spec(requestSpec) + .body(serverRpcPayload) + .post("/api/rpc/twoway/{deviceId}", deviceId.getId()) + .then() + .statusCode(HTTP_OK) + .extract() + .as(JsonNode.class); + } + + public DeviceProfile getDeviceProfileById(DeviceProfileId deviceProfileId) { + return given().spec(requestSpec).get("/api/deviceProfile/{deviceProfileId}", deviceProfileId.getId()) + .then() + .assertThat() + .statusCode(HTTP_OK) + .extract() + .as(DeviceProfile.class); + } + + public DeviceProfile postDeviceProfile(DeviceProfile deviceProfile) { + return given().spec(requestSpec).body(deviceProfile) + .post("/api/deviceProfile") + .then() + .statusCode(HTTP_OK) + .extract() + .as(DeviceProfile.class); + } + + public String getToken() { + return token; + } + + public String getRefreshToken() { + return refreshToken; + } +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java index 979fbb187d..ed606cd468 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/ThingsBoardDbInstaller.java @@ -16,7 +16,6 @@ package org.thingsboard.server.msa; import lombok.extern.slf4j.Slf4j; -import org.junit.rules.ExternalResource; import org.testcontainers.utility.Base58; import org.thingsboard.server.common.data.StringUtils; @@ -30,7 +29,7 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; @Slf4j -public class ThingsBoardDbInstaller extends ExternalResource { +public class ThingsBoardDbInstaller { final static boolean IS_REDIS_CLUSTER = Boolean.parseBoolean(System.getProperty("blackBoxTests.redisCluster")); final static boolean IS_HYBRID_MODE = Boolean.parseBoolean(System.getProperty("blackBoxTests.hybridMode")); @@ -129,8 +128,7 @@ public class ThingsBoardDbInstaller extends ExternalResource { return env; } - @Override - protected void before() throws Throwable { + public void createVolumes() { try { dockerCompose.withCommand("volume create " + postgresDataVolume); @@ -192,8 +190,7 @@ public class ThingsBoardDbInstaller extends ExternalResource { } } - @Override - protected void after() { + public void savaLogsAndRemoveVolumes() { copyLogs(tbLogVolume, "./target/tb-logs/"); copyLogs(tbCoapTransportLogVolume, "./target/tb-coap-transport-logs/"); copyLogs(tbLwm2mTransportLogVolume, "./target/tb-lwm2m-transport-logs/"); diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java new file mode 100644 index 0000000000..fe3443dd0e --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/CoapClientTest.java @@ -0,0 +1,120 @@ +/** + * 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.msa.connectivity; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; +import com.google.gson.JsonObject; +import io.restassured.path.json.JsonPath; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.msg.session.FeatureType; +import org.thingsboard.server.msa.AbstractContainerTest; +import org.thingsboard.server.msa.TestCoapClient; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype; + +public class CoapClientTest extends AbstractContainerTest { + private TestCoapClient client; + + private Device device; + @BeforeMethod + public void setUp() throws Exception { + testRestClient.login("tenant@thingsboard.org", "tenant"); + device = testRestClient.postDevice("", defaultDevicePrototype("http_")); + } + + @AfterMethod + public void tearDown() { + testRestClient.deleteDeviceIfExists(device.getId()); + } + + @Test + public void provisionRequestForDeviceWithPreProvisionedStrategy() throws Exception { + + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); + + DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); + + JsonNode provisionResponse = JacksonUtil.fromBytes(createCoapClientAndPublish(device.getName())); + + assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name()); + assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId()); + assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS"); + + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); + } + + @Test + public void provisionRequestForDeviceWithAllowToCreateNewDevicesStrategy() throws Exception { + + String testDeviceName = "test_provision_device"; + + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + + JsonNode provisionResponse = JacksonUtil.fromBytes(createCoapClientAndPublish(testDeviceName)); + + testRestClient.deleteDeviceIfExists(device.getId()); + device = testRestClient.getDeviceByName(testDeviceName); + + DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); + + assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name()); + assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId()); + assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS"); + + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); + } + + @Test + public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception { + + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + + JsonNode response = JacksonUtil.fromBytes(createCoapClientAndPublish(null)); + + assertThat(response.get("status").asText()).isEqualTo("NOT_FOUND"); + } + + private byte[] createCoapClientAndPublish(String deviceName) throws Exception { + String provisionRequestMsg = createTestProvisionMessage(deviceName); + client = new TestCoapClient(TestCoapClient.getFeatureTokenUrl(FeatureType.PROVISION)); + return client.postMethod(provisionRequestMsg.getBytes()).getPayload(); + } + + private String createTestProvisionMessage(String deviceName) { + ObjectNode provisionRequest = JacksonUtil.newObjectNode(); + provisionRequest.put("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.put("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + if (deviceName != null) { + provisionRequest.put("deviceName", deviceName); + } + return provisionRequest.toString(); + } + +} diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java index f81d03b394..adaefa887a 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/HttpClientTest.java @@ -16,109 +16,156 @@ package org.thingsboard.server.msa.connectivity; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.Sets; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.http.ResponseEntity; +import com.google.gson.JsonObject; +import io.restassured.path.json.JsonPath; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.DeviceProfile; +import org.thingsboard.server.common.data.DeviceProfileProvisionType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.msa.AbstractContainerTest; import org.thingsboard.server.msa.WsClient; import org.thingsboard.server.msa.mapper.WsTelemetryResponse; - -import java.util.Optional; +import java.util.Arrays; import java.util.concurrent.TimeUnit; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; +import static org.assertj.core.api.Assertions.assertThat; import static org.thingsboard.server.common.data.DataConstants.DEVICE; import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; +import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype; public class HttpClientTest extends AbstractContainerTest { + private Device device; + @BeforeMethod + public void setUp() throws Exception { + testRestClient.login("tenant@thingsboard.org", "tenant"); + device = testRestClient.postDevice("", defaultDevicePrototype("http_")); + } + + @AfterMethod + public void tearDown() { + testRestClient.deleteDeviceIfExists(device.getId()); + } @Test public void telemetryUpload() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - - Device device = createDevice("http_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); - ResponseEntity deviceTelemetryResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/v1/{credentialsId}/telemetry", - mapper.readTree(createPayload().toString()), - ResponseEntity.class, - deviceCredentials.getCredentialsId()); - Assert.assertTrue(deviceTelemetryResponse.getStatusCode().is2xxSuccessful()); + testRestClient.postTelemetry(deviceCredentials.getCredentialsId(), mapper.readTree(createPayload().toString())); + WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); wsClient.closeBlocking(); - Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), - actualLatestTelemetry.getLatestValues().keySet()); - - Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); - Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); - Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); - Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnlyOnceElementsOf(Arrays.asList("booleanKey", "stringKey", "doubleKey", "longKey")); - restClient.deleteDevice(device.getId()); + assertThat(actualLatestTelemetry.getDataValuesByKey("booleanKey").get(1)).isEqualTo(Boolean.TRUE.toString()); + assertThat(actualLatestTelemetry.getDataValuesByKey("stringKey").get(1)).isEqualTo("value1"); + assertThat(actualLatestTelemetry.getDataValuesByKey("doubleKey").get(1)).isEqualTo(Double.toString(42.0)); + assertThat(actualLatestTelemetry.getDataValuesByKey("longKey").get(1)).isEqualTo(Long.toString(73)); } @Test public void getAttributes() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - TB_TOKEN = restClient.getToken(); + String accessToken = testRestClient.getDeviceCredentialsByDeviceId(device.getId()).getCredentialsId(); + assertThat(accessToken).isNotNull(); - Device device = createDevice("test"); - String accessToken = restClient.getDeviceCredentialsByDeviceId(device.getId()).get().getCredentialsId(); - assertNotNull(accessToken); + JsonNode sharedAttribute = mapper.readTree(createPayload().toString()); + testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute); - ResponseEntity deviceSharedAttributes = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/" + DEVICE + "/" + device.getId().toString() + "/attributes/" + SHARED_SCOPE, mapper.readTree(createPayload().toString()), - ResponseEntity.class, - accessToken); + JsonNode clientAttribute = mapper.readTree(createPayload().toString()); + testRestClient.postAttribute(accessToken, clientAttribute); - Assert.assertTrue(deviceSharedAttributes.getStatusCode().is2xxSuccessful()); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); - ResponseEntity deviceClientsAttributes = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/v1/" + accessToken + "/attributes/", mapper.readTree(createPayload().toString()), - ResponseEntity.class, - accessToken); + JsonNode attributes = testRestClient.getAttributes(accessToken, null, null); + assertThat(attributes.get("shared")).isEqualTo(sharedAttribute); + assertThat(attributes.get("client")).isEqualTo(clientAttribute); - Assert.assertTrue(deviceClientsAttributes.getStatusCode().is2xxSuccessful()); + JsonNode attributes2 = testRestClient.getAttributes(accessToken, null, "stringKey"); + assertThat(attributes2.get("shared").get("stringKey")).isEqualTo(sharedAttribute.get("stringKey")); + assertThat(attributes2.has("client")).isFalse(); - TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); + JsonNode attributes3 = testRestClient.getAttributes(accessToken, "longKey,stringKey", null); - @SuppressWarnings("deprecation") - Optional allOptional = restClient.getAttributes(accessToken, null, null); - assertTrue(allOptional.isPresent()); + assertThat(attributes3.has("shared")).isFalse(); + assertThat(attributes3.get("client").get("longKey")).isEqualTo(clientAttribute.get("longKey")); + assertThat(attributes3.get("client").get("stringKey")).isEqualTo(clientAttribute.get("stringKey")); + } + + @Test + public void provisionRequestForDeviceWithPreProvisionedStrategy() throws Exception { + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); - JsonNode all = allOptional.get(); - assertEquals(2, all.size()); - assertEquals(mapper.readTree(createPayload().toString()), all.get("shared")); - assertEquals(mapper.readTree(createPayload().toString()), all.get("client")); + DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); - @SuppressWarnings("deprecation") - Optional sharedOptional = restClient.getAttributes(accessToken, null, "stringKey"); - assertTrue(sharedOptional.isPresent()); + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + provisionRequest.addProperty("deviceName", device.getName()); - JsonNode shared = sharedOptional.get(); - assertEquals(shared.get("shared").get("stringKey"), mapper.readTree(createPayload().get("stringKey").toString())); - assertFalse(shared.has("client")); + JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString()); - @SuppressWarnings("deprecation") - Optional clientOptional = restClient.getAttributes(accessToken, "longKey,stringKey", null); - assertTrue(clientOptional.isPresent()); + String credentialsType = provisionResponse.get("credentialsType"); + String credentialsValue = provisionResponse.get("credentialsValue"); + String status = provisionResponse.get("status"); - JsonNode client = clientOptional.get(); - assertFalse(client.has("shared")); - assertEquals(mapper.readTree(createPayload().get("longKey").toString()), client.get("client").get("longKey")); - assertEquals(client.get("client").get("stringKey"), mapper.readTree(createPayload().get("stringKey").toString())); + assertThat(credentialsType).isEqualTo(expectedDeviceCredentials.getCredentialsType().name()); + assertThat(credentialsValue).isEqualTo(expectedDeviceCredentials.getCredentialsId()); + assertThat(status).isEqualTo("SUCCESS"); - restClient.deleteDevice(device.getId()); + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); } + + @Test + public void provisionRequestForDeviceWithAllowToCreateNewDevicesStrategy() throws Exception { + + String testDeviceName = "test_provision_device"; + + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + provisionRequest.addProperty("deviceName", testDeviceName); + + JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString()); + + String credentialsType = provisionResponse.get("credentialsType"); + String credentialsValue = provisionResponse.get("credentialsValue"); + String status = provisionResponse.get("status"); + + testRestClient.deleteDeviceIfExists(device.getId()); + device = testRestClient.getDeviceByName(testDeviceName); + + DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); + + assertThat(credentialsType).isEqualTo(expectedDeviceCredentials.getCredentialsType().name()); + assertThat(credentialsValue).isEqualTo(expectedDeviceCredentials.getCredentialsId()); + assertThat(status).isEqualTo("SUCCESS"); + + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); + } + + @Test + public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception { + + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + + JsonPath provisionResponse = testRestClient.postProvisionRequest(provisionRequest.toString()); + + String status = provisionResponse.get("status"); + + assertThat(status).isEqualTo("NOT_FOUND"); + } + } diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java index ce36f08a64..347598a9e7 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttClientTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.msa.connectivity; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -26,19 +25,21 @@ import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.junit.Assert; -import org.junit.Test; -import org.springframework.core.ParameterizedTypeReference; -import org.springframework.http.HttpMethod; -import org.springframework.http.ResponseEntity; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttHandler; +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.DeviceProfileProvisionType; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.RuleChainId; import org.thingsboard.server.common.data.page.PageData; +import org.thingsboard.server.common.data.page.PageLink; import org.thingsboard.server.common.data.rule.NodeConnectionInfo; import org.thingsboard.server.common.data.rule.RuleChain; import org.thingsboard.server.common.data.rule.RuleChainMetaData; @@ -61,14 +62,29 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThat; +import static org.testng.Assert.fail; +import static org.thingsboard.server.common.data.DataConstants.DEVICE; +import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; +import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultDevicePrototype; + @Slf4j public class MqttClientTest extends AbstractContainerTest { + private Device device; + @BeforeMethod + public void setUp() throws Exception { + testRestClient.login("tenant@thingsboard.org", "tenant"); + device = testRestClient.postDevice("", defaultDevicePrototype("http_")); + } + + @AfterMethod + public void tearDown() { + testRestClient.deleteDeviceIfExists(device.getId()); + } @Test public void telemetryUpload() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); MqttClient mqttClient = getMqttClient(deviceCredentials, null); @@ -77,25 +93,19 @@ public class MqttClientTest extends AbstractContainerTest { log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(4, actualLatestTelemetry.getData().size()); - Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), - actualLatestTelemetry.getLatestValues().keySet()); - - Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); - Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); - Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); - Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); + assertThat(actualLatestTelemetry.getData()).hasSize(4); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnlyOnceElementsOf(Arrays.asList("booleanKey", "stringKey", "doubleKey", "longKey")); - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + assertThat(actualLatestTelemetry.getDataValuesByKey("booleanKey").get(1)).isEqualTo(Boolean.TRUE.toString()); + assertThat(actualLatestTelemetry.getDataValuesByKey("stringKey").get(1)).isEqualTo("value1"); + assertThat(actualLatestTelemetry.getDataValuesByKey("doubleKey").get(1)).isEqualTo(Double.toString(42.0)); + assertThat(actualLatestTelemetry.getDataValuesByKey("longKey").get(1)).isEqualTo(Long.toString(73)); } @Test public void telemetryUploadWithTs() throws Exception { long ts = 1451649600512L; - - restClient.login("tenant@thingsboard.org", "tenant"); - Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); WsClient wsClient = subscribeToWebSocket(device.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); MqttClient mqttClient = getMqttClient(deviceCredentials, null); @@ -104,22 +114,18 @@ public class MqttClientTest extends AbstractContainerTest { log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(4, actualLatestTelemetry.getData().size()); - Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues()); - - Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString())); - Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1")); - Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0))); - Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73))); + assertThat(actualLatestTelemetry.getData()).hasSize(4); + assertThat(getExpectedLatestValues(ts)).isEqualTo(actualLatestTelemetry.getLatestValues()); - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + assertThat(actualLatestTelemetry.getDataValuesByKey("booleanKey").get(1)).isEqualTo(Boolean.TRUE.toString()); + assertThat(actualLatestTelemetry.getDataValuesByKey("stringKey").get(1)).isEqualTo("value1"); + assertThat(actualLatestTelemetry.getDataValuesByKey("doubleKey").get(1)).isEqualTo(Double.toString(42.0)); + assertThat(actualLatestTelemetry.getDataValuesByKey("longKey").get(1)).isEqualTo(Long.toString(73)); } @Test public void publishAttributeUpdateToServer() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); MqttMessageListener listener = new MqttMessageListener(); @@ -134,23 +140,18 @@ public class MqttClientTest extends AbstractContainerTest { log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(4, actualLatestTelemetry.getData().size()); - Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"), - actualLatestTelemetry.getLatestValues().keySet()); + assertThat(actualLatestTelemetry.getData()).hasSize(4); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnlyOnceElementsOf(Arrays.asList("attr1", "attr2", "attr3", "attr4")); - Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1")); - Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString())); - Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0))); - Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73))); - - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr1").get(1)).isEqualTo("value1"); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr2").get(1)).isEqualTo(Boolean.TRUE.toString()); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr3").get(1)).isEqualTo(Double.toString(42.0)); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr4").get(1)).isEqualTo(Long.toString(73)); } @Test public void requestAttributeValuesFromServer() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); WsClient wsClient = subscribeToWebSocket(device.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); MqttMessageListener listener = new MqttMessageListener(); @@ -166,21 +167,16 @@ public class MqttClientTest extends AbstractContainerTest { log.info("Received ws telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(1, actualLatestTelemetry.getData().size()); - Assert.assertEquals(Sets.newHashSet("clientAttr"), - actualLatestTelemetry.getLatestValues().keySet()); - - Assert.assertTrue(verify(actualLatestTelemetry, "clientAttr", clientAttributeValue)); + assertThat(actualLatestTelemetry.getData()).hasSize(1); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnly("clientAttr"); + assertThat(actualLatestTelemetry.getDataValuesByKey("clientAttr").get(1)).isEqualTo(clientAttributeValue); // Add a new shared attribute JsonObject sharedAttributes = new JsonObject(); String sharedAttributeValue = StringUtils.randomAlphanumeric(8); sharedAttributes.addProperty("sharedAttr", sharedAttributeValue); - ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", - mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, - device.getId()); - Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); + JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString()); + testRestClient.postTelemetryAttribute(DataConstants.DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute); // Subscribe to attributes response mqttClient.on("v1/devices/me/attributes/response/+", listener, MqttQoS.AT_LEAST_ONCE).get(); @@ -197,20 +193,16 @@ public class MqttClientTest extends AbstractContainerTest { AttributesResponse attributes = mapper.readValue(Objects.requireNonNull(event).getMessage(), AttributesResponse.class); log.info("Received telemetry: {}", attributes); - Assert.assertEquals(1, attributes.getClient().size()); - Assert.assertEquals(clientAttributeValue, attributes.getClient().get("clientAttr")); + assertThat(attributes.getClient()).hasSize(1); + assertThat(attributes.getClient().get("clientAttr")).isEqualTo(clientAttributeValue); - Assert.assertEquals(1, attributes.getShared().size()); - Assert.assertEquals(sharedAttributeValue, attributes.getShared().get("sharedAttr")); - - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + assertThat(attributes.getShared()).hasSize(1); + assertThat(attributes.getShared().get("sharedAttr")).isEqualTo(sharedAttributeValue); } @Test public void subscribeToAttributeUpdatesFromServer() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); @@ -225,38 +217,28 @@ public class MqttClientTest extends AbstractContainerTest { JsonObject sharedAttributes = new JsonObject(); String sharedAttributeValue = StringUtils.randomAlphanumeric(8); sharedAttributes.addProperty(sharedAttributeName, sharedAttributeValue); - ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", - mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, - device.getId()); - Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); + JsonNode sharedAttribute = mapper.readTree(sharedAttributes.toString()); + + testRestClient.postTelemetryAttribute(DataConstants.DEVICE, device.getId(), SHARED_SCOPE, sharedAttribute); MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); - Assert.assertEquals(sharedAttributeValue, - mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); + assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()) + .isEqualTo(sharedAttributeValue); // Update the shared attribute value JsonObject updatedSharedAttributes = new JsonObject(); String updatedSharedAttributeValue = StringUtils.randomAlphanumeric(8); updatedSharedAttributes.addProperty(sharedAttributeName, updatedSharedAttributeValue); - ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", - mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class, - device.getId()); - Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful()); + testRestClient.postTelemetryAttribute(DEVICE, device.getId(), SHARED_SCOPE, mapper.readTree(updatedSharedAttributes.toString())); event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); - Assert.assertEquals(updatedSharedAttributeValue, - mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()); - - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get(sharedAttributeName).asText()) + .isEqualTo(updatedSharedAttributeValue); } @Test public void serverSideRpc() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); @@ -270,21 +252,18 @@ public class MqttClientTest extends AbstractContainerTest { serverRpcPayload.addProperty("method", "getValue"); serverRpcPayload.addProperty("params", true); ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(getClass().getSimpleName()))); - ListenableFuture future = service.submit(() -> { + ListenableFuture future = service.submit(() -> { try { - return restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/rpc/twoway/{deviceId}", - mapper.readTree(serverRpcPayload.toString()), String.class, - device.getId()); + return testRestClient.postServerSideRpc(device.getId(), mapper.readTree(serverRpcPayload.toString())); } catch (IOException e) { - return ResponseEntity.badRequest().build(); + return null; } }); // Wait for RPC call from the server and send the response MqttEvent requestFromServer = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); - Assert.assertEquals("{\"method\":\"getValue\",\"params\":true}", Objects.requireNonNull(requestFromServer).getMessage()); + assertThat(Objects.requireNonNull(requestFromServer).getMessage()).isEqualTo("{\"method\":\"getValue\",\"params\":true}"); Integer requestId = Integer.valueOf(Objects.requireNonNull(requestFromServer).getTopic().substring("v1/devices/me/rpc/request/".length())); JsonObject clientResponse = new JsonObject(); @@ -292,19 +271,14 @@ public class MqttClientTest extends AbstractContainerTest { // Send a response to the server's RPC request mqttClient.publish("v1/devices/me/rpc/response/" + requestId, Unpooled.wrappedBuffer(clientResponse.toString().getBytes())).get(); - ResponseEntity serverResponse = future.get(5 * timeoutMultiplier, TimeUnit.SECONDS); + JsonNode serverResponse = future.get(5 * timeoutMultiplier, TimeUnit.SECONDS); service.shutdownNow(); - Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful()); - Assert.assertEquals(clientResponse.toString(), serverResponse.getBody()); - - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + assertThat(serverResponse).isEqualTo(mapper.readTree(clientResponse.toString())); } @Test public void clientSideRpc() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - Device device = createDevice("mqtt_"); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); @@ -328,46 +302,131 @@ public class MqttClientTest extends AbstractContainerTest { TimeUnit.SECONDS.sleep(1 * timeoutMultiplier); MqttEvent responseFromServer = listener.getEvents().poll(1 * timeoutMultiplier, TimeUnit.SECONDS); Integer responseId = Integer.valueOf(Objects.requireNonNull(responseFromServer).getTopic().substring("v1/devices/me/rpc/response/".length())); - Assert.assertEquals(requestId, responseId); - Assert.assertEquals("requestReceived", mapper.readTree(responseFromServer.getMessage()).get("response").asText()); + assertThat(responseId).isEqualTo(requestId); + assertThat(mapper.readTree(responseFromServer.getMessage()).get("response").asText()).isEqualTo("requestReceived"); // Make the default rule chain a root again - ResponseEntity rootRuleChainResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", - null, - RuleChain.class, - defaultRuleChainId); - Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); + testRestClient.setRootRuleChain(defaultRuleChainId); // Delete the created rule chain - restClient.getRestTemplate().delete(HTTPS_URL + "/api/ruleChain/{ruleChainId}", ruleChainId); - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + device.getId()); + testRestClient.deleteRuleChain(ruleChainId); } @Test public void deviceDeletedClosingSession() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - String deviceForDeletingTestName = "Device for deleting notification test"; - Device device = createDevice(deviceForDeletingTestName); - DeviceCredentials deviceCredentials = restClient.getDeviceCredentialsByDeviceId(device.getId()).get(); + DeviceCredentials deviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); MqttMessageListener listener = new MqttMessageListener(); MqttClient mqttClient = getMqttClient(deviceCredentials, listener); - restClient.deleteDevice(device.getId()); + testRestClient.deleteDeviceIfExists(device.getId()); + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); + assertThat(mqttClient.isConnected()).isFalse(); + } + + @Test + public void provisionRequestForDeviceWithPreProvisionedStrategy() throws Exception { + + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.CHECK_PRE_PROVISIONED_DEVICES); + + DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient("provision", listener); + + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + provisionRequest.addProperty("deviceName", device.getName()); + + mqttClient.publish("/provision/request", Unpooled.wrappedBuffer(provisionRequest.toString().getBytes())).get(); + + //Wait for response + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); + + MqttEvent provisionResponseMsg = listener.getEvents().poll(timeoutMultiplier, TimeUnit.SECONDS); + + assertThat(provisionResponseMsg).isNotNull(); + + JsonNode provisionResponse = mapper.readTree(provisionResponseMsg.getMessage()); + + assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name()); + assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId()); + assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS"); + + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); + } + + @Test + public void provisionRequestForDeviceWithAllowToCreateNewDevicesStrategy() throws Exception { + + String testDeviceName = "test_provision_device"; + + DeviceProfile deviceProfile = testRestClient.getDeviceProfileById(device.getDeviceProfileId()); + + deviceProfile = updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.ALLOW_CREATE_NEW_DEVICES); + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient("provision", listener); + + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + provisionRequest.addProperty("deviceName", testDeviceName); + + mqttClient.publish("/provision/request", Unpooled.wrappedBuffer(provisionRequest.toString().getBytes())).get(); + + //Wait for response TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); - Assert.assertFalse(mqttClient.isConnected()); + + MqttEvent provisionResponseMsg = listener.getEvents().poll(timeoutMultiplier, TimeUnit.SECONDS); + + assertThat(provisionResponseMsg).isNotNull(); + + JsonNode provisionResponse = mapper.readTree(provisionResponseMsg.getMessage()); + + testRestClient.deleteDeviceIfExists(device.getId()); + device = testRestClient.getDeviceByName(testDeviceName); + + DeviceCredentials expectedDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(device.getId()); + + assertThat(provisionResponse.get("credentialsType").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsType().name()); + assertThat(provisionResponse.get("credentialsValue").asText()).isEqualTo(expectedDeviceCredentials.getCredentialsId()); + assertThat(provisionResponse.get("status").asText()).isEqualTo("SUCCESS"); + + updateDeviceProfileWithProvisioningStrategy(deviceProfile, DeviceProfileProvisionType.DISABLED); + } + + @Test + public void provisionRequestForDeviceWithDisabledProvisioningStrategy() throws Exception { + + MqttMessageListener listener = new MqttMessageListener(); + MqttClient mqttClient = getMqttClient("provision", listener); + + JsonObject provisionRequest = new JsonObject(); + provisionRequest.addProperty("provisionDeviceKey", TEST_PROVISION_DEVICE_KEY); + provisionRequest.addProperty("provisionDeviceSecret", TEST_PROVISION_DEVICE_SECRET); + + mqttClient.publish("/provision/request", Unpooled.wrappedBuffer(provisionRequest.toString().getBytes())).get(); + + //Wait for response + TimeUnit.SECONDS.sleep(3 * timeoutMultiplier); + + MqttEvent provisionResponseMsg = listener.getEvents().poll(timeoutMultiplier, TimeUnit.SECONDS); + + assertThat(provisionResponseMsg).isNotNull(); + + JsonNode provisionResponse = mapper.readTree(provisionResponseMsg.getMessage()); + + assertThat(provisionResponse.get("status").asText()).isEqualTo("NOT_FOUND"); } private RuleChainId createRootRuleChainForRpcResponse() throws Exception { RuleChain newRuleChain = new RuleChain(); newRuleChain.setName("testRuleChain"); - ResponseEntity ruleChainResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/ruleChain", - newRuleChain, - RuleChain.class); - Assert.assertTrue(ruleChainResponse.getStatusCode().is2xxSuccessful()); - RuleChain ruleChain = ruleChainResponse.getBody(); + + RuleChain ruleChain = testRestClient.postRootRuleChain(newRuleChain); JsonNode configuration = mapper.readTree(this.getClass().getClassLoader().getResourceAsStream("RpcResponseRuleChainMetadata.json")); RuleChainMetaData ruleChainMetaData = new RuleChainMetaData(); @@ -376,45 +435,34 @@ public class MqttClientTest extends AbstractContainerTest { ruleChainMetaData.setNodes(Arrays.asList(mapper.treeToValue(configuration.get("nodes"), RuleNode[].class))); ruleChainMetaData.setConnections(Arrays.asList(mapper.treeToValue(configuration.get("connections"), NodeConnectionInfo[].class))); - ResponseEntity ruleChainMetadataResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/ruleChain/metadata", - ruleChainMetaData, - RuleChainMetaData.class); - Assert.assertTrue(ruleChainMetadataResponse.getStatusCode().is2xxSuccessful()); + testRestClient.postRuleChainMetadata(ruleChainMetaData); // Set a new rule chain as root - ResponseEntity rootRuleChainResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/ruleChain/{ruleChainId}/root", - null, - RuleChain.class, - ruleChain.getId()); - Assert.assertTrue(rootRuleChainResponse.getStatusCode().is2xxSuccessful()); - + testRestClient.setRootRuleChain(ruleChain.getId()); return ruleChain.getId(); } private RuleChainId getDefaultRuleChainId() { - ResponseEntity> ruleChains = restClient.getRestTemplate().exchange( - HTTPS_URL + "/api/ruleChains?pageSize=40&page=0&textSearch=", - HttpMethod.GET, - null, - new ParameterizedTypeReference>() { - }); - - Optional defaultRuleChain = ruleChains.getBody().getData() + PageData ruleChains = testRestClient.getRuleChains(new PageLink(40, 0)); + + Optional defaultRuleChain = ruleChains.getData() .stream() .filter(RuleChain::isRoot) .findFirst(); if (!defaultRuleChain.isPresent()) { - Assert.fail("Root rule chain wasn't found"); + fail("Root rule chain wasn't found"); } return defaultRuleChain.get().getId(); } private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException { + return getMqttClient(deviceCredentials.getCredentialsId(), listener); + } + + private MqttClient getMqttClient(String username, MqttMessageListener listener) throws InterruptedException, ExecutionException { MqttClientConfig clientConfig = new MqttClientConfig(); clientConfig.setClientId("MQTT client from test"); - clientConfig.setUsername(deviceCredentials.getCredentialsId()); + clientConfig.setUsername(username); MqttClient mqttClient = MqttClient.create(clientConfig, listener); mqttClient.connect("localhost", 1883).get(); return mqttClient; diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java index 25a8946431..84b2dd7253 100644 --- a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/connectivity/MqttGatewayClientTest.java @@ -16,7 +16,6 @@ package org.thingsboard.server.msa.connectivity; import com.fasterxml.jackson.databind.JsonNode; -import com.google.common.collect.Sets; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListeningExecutorService; import com.google.common.util.concurrent.MoreExecutors; @@ -28,15 +27,17 @@ import io.netty.buffer.Unpooled; import io.netty.handler.codec.mqtt.MqttQoS; import lombok.Data; import lombok.extern.slf4j.Slf4j; -import org.junit.After; -import org.junit.Assert; -import org.junit.Before; -import org.junit.Test; -import org.springframework.http.ResponseEntity; +import org.springframework.http.HttpStatus; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.testng.annotations.AfterMethod; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; +import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.ThingsBoardThreadFactory; import org.thingsboard.mqtt.MqttClient; import org.thingsboard.mqtt.MqttClientConfig; import org.thingsboard.mqtt.MqttHandler; +import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.StringUtils; import org.thingsboard.server.common.data.id.DeviceId; @@ -50,9 +51,9 @@ import org.thingsboard.server.msa.mapper.WsTelemetryResponse; import java.io.IOException; import java.nio.charset.StandardCharsets; +import java.util.Arrays; import java.util.List; import java.util.Objects; -import java.util.Optional; import java.util.Random; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; @@ -60,28 +61,34 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; +import static org.assertj.core.api.Assertions.assertThat; +import static org.thingsboard.server.common.data.DataConstants.DEVICE; +import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; +import static org.thingsboard.server.msa.prototypes.DevicePrototypes.defaultGatewayPrototype; + @Slf4j public class MqttGatewayClientTest extends AbstractContainerTest { - Device gatewayDevice; - MqttClient mqttClient; - Device createdDevice; - MqttMessageListener listener; + private Device gatewayDevice; + private MqttClient mqttClient; + private Device createdDevice; + private MqttMessageListener listener; + private JsonParser jsonParser = new JsonParser(); - @Before + @BeforeMethod public void createGateway() throws Exception { - restClient.login("tenant@thingsboard.org", "tenant"); - this.gatewayDevice = createGatewayDevice(); - Optional gatewayDeviceCredentials = restClient.getDeviceCredentialsByDeviceId(gatewayDevice.getId()); - Assert.assertTrue(gatewayDeviceCredentials.isPresent()); + testRestClient.login("tenant@thingsboard.org", "tenant"); + gatewayDevice = testRestClient.postDevice("", defaultGatewayPrototype()); + DeviceCredentials gatewayDeviceCredentials = testRestClient.getDeviceCredentialsByDeviceId(gatewayDevice.getId()); + this.listener = new MqttMessageListener(); - this.mqttClient = getMqttClient(gatewayDeviceCredentials.get(), listener); + this.mqttClient = getMqttClient(gatewayDeviceCredentials, listener); this.createdDevice = createDeviceThroughGateway(mqttClient, gatewayDevice); } - @After - public void removeGateway() throws Exception { - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.gatewayDevice.getId()); - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.createdDevice.getId()); + @AfterMethod + public void removeGateway() { + testRestClient.deleteDeviceIfExists(this.gatewayDevice.getId()); + testRestClient.deleteDeviceIfExists(this.createdDevice.getId()); this.listener = null; this.mqttClient = null; this.createdDevice = null; @@ -95,40 +102,38 @@ public class MqttGatewayClientTest extends AbstractContainerTest { log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(4, actualLatestTelemetry.getData().size()); - Assert.assertEquals(Sets.newHashSet("booleanKey", "stringKey", "doubleKey", "longKey"), - actualLatestTelemetry.getLatestValues().keySet()); + assertThat(actualLatestTelemetry.getData()).hasSize(4); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnlyOnceElementsOf(Arrays.asList("booleanKey", "stringKey", "doubleKey", "longKey")); - Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", Boolean.TRUE.toString())); - Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", "value1")); - Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", Double.toString(42.0))); - Assert.assertTrue(verify(actualLatestTelemetry, "longKey", Long.toString(73))); + assertThat(actualLatestTelemetry.getDataValuesByKey("booleanKey").get(1)).isEqualTo(Boolean.TRUE.toString()); + assertThat(actualLatestTelemetry.getDataValuesByKey("stringKey").get(1)).isEqualTo("value1"); + assertThat(actualLatestTelemetry.getDataValuesByKey("doubleKey").get(1)).isEqualTo(Double.toString(42.0)); + assertThat(actualLatestTelemetry.getDataValuesByKey("longKey").get(1)).isEqualTo(Long.toString(73)); } @Test public void telemetryUploadWithTs() throws Exception { long ts = 1451649600512L; - restClient.login("tenant@thingsboard.org", "tenant"); WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "LATEST_TELEMETRY", CmdsType.TS_SUB_CMDS); mqttClient.publish("v1/gateway/telemetry", Unpooled.wrappedBuffer(createGatewayPayload(createdDevice.getName(), ts).toString().getBytes())).get(); WsTelemetryResponse actualLatestTelemetry = wsClient.getLastMessage(); log.info("Received telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(4, actualLatestTelemetry.getData().size()); - Assert.assertEquals(getExpectedLatestValues(ts), actualLatestTelemetry.getLatestValues()); + assertThat(actualLatestTelemetry.getData()).hasSize(4); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnlyOnceElementsOf(Arrays.asList("booleanKey", "stringKey", "doubleKey", "longKey")); - Assert.assertTrue(verify(actualLatestTelemetry, "booleanKey", ts, Boolean.TRUE.toString())); - Assert.assertTrue(verify(actualLatestTelemetry, "stringKey", ts, "value1")); - Assert.assertTrue(verify(actualLatestTelemetry, "doubleKey", ts, Double.toString(42.0))); - Assert.assertTrue(verify(actualLatestTelemetry, "longKey", ts, Long.toString(73))); + assertThat(actualLatestTelemetry.getDataValuesByKey("booleanKey").get(1)).isEqualTo(Boolean.TRUE.toString()); + assertThat(actualLatestTelemetry.getDataValuesByKey("stringKey").get(1)).isEqualTo("value1"); + assertThat(actualLatestTelemetry.getDataValuesByKey("doubleKey").get(1)).isEqualTo(Double.toString(42.0)); + assertThat(actualLatestTelemetry.getDataValuesByKey("longKey").get(1)).isEqualTo(Long.toString(73)); } @Test public void publishAttributeUpdateToServer() throws Exception { - Optional createdDeviceCredentials = restClient.getDeviceCredentialsByDeviceId(createdDevice.getId()); - Assert.assertTrue(createdDeviceCredentials.isPresent()); + testRestClient.getDeviceCredentialsByDeviceId(createdDevice.getId()); + WsClient wsClient = subscribeToWebSocket(createdDevice.getId(), "CLIENT_SCOPE", CmdsType.ATTR_SUB_CMDS); JsonObject clientAttributes = new JsonObject(); clientAttributes.addProperty("attr1", "value1"); @@ -142,20 +147,18 @@ public class MqttGatewayClientTest extends AbstractContainerTest { log.info("Received attributes: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(4, actualLatestTelemetry.getData().size()); - Assert.assertEquals(Sets.newHashSet("attr1", "attr2", "attr3", "attr4"), - actualLatestTelemetry.getLatestValues().keySet()); + assertThat(actualLatestTelemetry.getData()).hasSize(4); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnlyOnceElementsOf(Arrays.asList("attr1", "attr2", "attr3", "attr4")); - Assert.assertTrue(verify(actualLatestTelemetry, "attr1", "value1")); - Assert.assertTrue(verify(actualLatestTelemetry, "attr2", Boolean.TRUE.toString())); - Assert.assertTrue(verify(actualLatestTelemetry, "attr3", Double.toString(42.0))); - Assert.assertTrue(verify(actualLatestTelemetry, "attr4", Long.toString(73))); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr1").get(1)).isEqualTo("value1"); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr2").get(1)).isEqualTo(Boolean.TRUE.toString()); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr3").get(1)).isEqualTo(Double.toString(42.0)); + assertThat(actualLatestTelemetry.getDataValuesByKey("attr4").get(1)).isEqualTo(Long.toString(73)); } @Test public void responseDataOnAttributesRequestCheck() throws Exception { - Optional createdDeviceCredentials = restClient.getDeviceCredentialsByDeviceId(createdDevice.getId()); - Assert.assertTrue(createdDeviceCredentials.isPresent()); + testRestClient.getDeviceCredentialsByDeviceId(createdDevice.getId()); JsonObject sharedAttributes = new JsonObject(); sharedAttributes.addProperty("attr1", "value1"); sharedAttributes.addProperty("attr2", true); @@ -163,11 +166,8 @@ public class MqttGatewayClientTest extends AbstractContainerTest { sharedAttributes.addProperty("attr4", 73); mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get(); - ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", - mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, - createdDevice.getId()); - Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); + + testRestClient.postTelemetryAttribute(DataConstants.DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString())); var event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); JsonObject requestData = new JsonObject(); @@ -181,8 +181,8 @@ public class MqttGatewayClientTest extends AbstractContainerTest { event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); JsonObject responseData = jsonParser.parse(Objects.requireNonNull(event).getMessage()).getAsJsonObject(); - Assert.assertTrue(responseData.has("value")); - Assert.assertEquals(sharedAttributes.get("attr1").getAsString(), responseData.get("value").getAsString()); + assertThat(responseData.has("value")).isTrue(); + assertThat(responseData.get("value").getAsString()).isEqualTo(sharedAttributes.get("attr1").getAsString()); requestData = new JsonObject(); requestData.addProperty("id", 1); @@ -198,9 +198,9 @@ public class MqttGatewayClientTest extends AbstractContainerTest { event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); responseData = jsonParser.parse(Objects.requireNonNull(event).getMessage()).getAsJsonObject(); - Assert.assertTrue(responseData.has("values")); - Assert.assertEquals(sharedAttributes.get("attr1").getAsString(), responseData.get("values").getAsJsonObject().get("attr1").getAsString()); - Assert.assertEquals(sharedAttributes.get("attr2").getAsString(), responseData.get("values").getAsJsonObject().get("attr2").getAsString()); + assertThat(responseData.has("values")).isTrue(); + assertThat(responseData.get("values").getAsJsonObject().get("attr1").getAsString()).isEqualTo(sharedAttributes.get("attr1").getAsString()); + assertThat(responseData.get("values").getAsJsonObject().get("attr2").getAsString()).isEqualTo(sharedAttributes.get("attr2").getAsString()); requestData = new JsonObject(); requestData.addProperty("id", 1); @@ -216,9 +216,9 @@ public class MqttGatewayClientTest extends AbstractContainerTest { event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); responseData = jsonParser.parse(Objects.requireNonNull(event).getMessage()).getAsJsonObject(); - Assert.assertTrue(responseData.has("values")); - Assert.assertEquals(sharedAttributes.get("attr1").getAsString(), responseData.get("values").getAsJsonObject().get("attr1").getAsString()); - Assert.assertEquals(1, responseData.get("values").getAsJsonObject().entrySet().size()); + assertThat(responseData.has("values")).isTrue(); + assertThat(responseData.get("values").getAsJsonObject().get("attr1").getAsString()).isEqualTo(sharedAttributes.get("attr1").getAsString()); + assertThat(responseData.get("values").getAsJsonObject().entrySet()).hasSize(1); } @Test @@ -237,11 +237,9 @@ public class MqttGatewayClientTest extends AbstractContainerTest { log.info("Received ws telemetry: {}", actualLatestTelemetry); wsClient.closeBlocking(); - Assert.assertEquals(1, actualLatestTelemetry.getData().size()); - Assert.assertEquals(Sets.newHashSet("clientAttr"), - actualLatestTelemetry.getLatestValues().keySet()); - - Assert.assertTrue(verify(actualLatestTelemetry, "clientAttr", clientAttributeValue)); + assertThat(actualLatestTelemetry.getData()).hasSize(1); + assertThat(actualLatestTelemetry.getLatestValues().keySet()).containsOnly("clientAttr"); + assertThat(actualLatestTelemetry.getDataValuesByKey("clientAttr").get(1)).isEqualTo(clientAttributeValue); // Add a new shared attribute JsonObject sharedAttributes = new JsonObject(); @@ -251,16 +249,12 @@ public class MqttGatewayClientTest extends AbstractContainerTest { // Subscribe for attribute update event mqttClient.on("v1/gateway/attributes", listener, MqttQoS.AT_LEAST_ONCE).get(); - ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", - mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, - createdDevice.getId()); - Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); + testRestClient.postTelemetryAttribute(DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString())); MqttEvent sharedAttributeEvent = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); // Catch attribute update event - Assert.assertNotNull(sharedAttributeEvent); - Assert.assertEquals("v1/gateway/attributes", sharedAttributeEvent.getTopic()); + assertThat(sharedAttributeEvent).isNotNull(); + assertThat(sharedAttributeEvent.getTopic()).isEqualTo("v1/gateway/attributes"); // Subscribe to attributes response mqttClient.on("v1/gateway/attributes/response", listener, MqttQoS.AT_LEAST_ONCE).get(); @@ -288,15 +282,11 @@ public class MqttGatewayClientTest extends AbstractContainerTest { gatewaySharedAttributeValue.addProperty("device", createdDevice.getName()); gatewaySharedAttributeValue.add("data", sharedAttributes); - ResponseEntity sharedAttributesResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", - mapper.readTree(sharedAttributes.toString()), ResponseEntity.class, - createdDevice.getId()); - Assert.assertTrue(sharedAttributesResponse.getStatusCode().is2xxSuccessful()); + testRestClient.postTelemetryAttribute(DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(sharedAttributes.toString())); MqttEvent event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); - Assert.assertEquals(sharedAttributeValue, - mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()); + assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()) + .isEqualTo(sharedAttributeValue); // Update the shared attribute value JsonObject updatedSharedAttributes = new JsonObject(); @@ -307,15 +297,10 @@ public class MqttGatewayClientTest extends AbstractContainerTest { gatewayUpdatedSharedAttributeValue.addProperty("device", createdDevice.getName()); gatewayUpdatedSharedAttributeValue.add("data", updatedSharedAttributes); - ResponseEntity updatedSharedAttributesResponse = restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/plugins/telemetry/DEVICE/{deviceId}/SHARED_SCOPE", - mapper.readTree(updatedSharedAttributes.toString()), ResponseEntity.class, - createdDevice.getId()); - Assert.assertTrue(updatedSharedAttributesResponse.getStatusCode().is2xxSuccessful()); - + testRestClient.postTelemetryAttribute(DEVICE, createdDevice.getId(), SHARED_SCOPE, mapper.readTree(updatedSharedAttributes.toString())); event = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); - Assert.assertEquals(updatedSharedAttributeValue, - mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()); + assertThat(mapper.readValue(Objects.requireNonNull(event).getMessage(), JsonNode.class).get("data").get(sharedAttributeName).asText()) + .isEqualTo(updatedSharedAttributeValue); } @Test @@ -331,14 +316,11 @@ public class MqttGatewayClientTest extends AbstractContainerTest { serverRpcPayload.addProperty("method", "getValue"); serverRpcPayload.addProperty("params", true); ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newSingleThreadExecutor(ThingsBoardThreadFactory.forName(getClass().getSimpleName()))); - ListenableFuture future = service.submit(() -> { + ListenableFuture future = service.submit(() -> { try { - return restClient.getRestTemplate() - .postForEntity(HTTPS_URL + "/api/rpc/twoway/{deviceId}", - mapper.readTree(serverRpcPayload.toString()), String.class, - createdDevice.getId()); + return testRestClient.postServerSideRpc(createdDevice.getId(), mapper.readTree(serverRpcPayload.toString())); } catch (IOException e) { - return ResponseEntity.badRequest().build(); + return null; } }); @@ -346,19 +328,13 @@ public class MqttGatewayClientTest extends AbstractContainerTest { MqttEvent requestFromServer = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); service.shutdownNow(); - Assert.assertNotNull(requestFromServer); - Assert.assertNotNull(requestFromServer.getMessage()); - - JsonObject requestFromServerJson = new JsonParser().parse(requestFromServer.getMessage()).getAsJsonObject(); - - Assert.assertEquals(createdDevice.getName(), requestFromServerJson.get("device").getAsString()); - - JsonObject requestFromServerData = requestFromServerJson.get("data").getAsJsonObject(); - - Assert.assertEquals("getValue", requestFromServerData.get("method").getAsString()); - Assert.assertTrue(requestFromServerData.get("params").getAsBoolean()); - - int requestId = requestFromServerData.get("id").getAsInt(); + assertThat(requestFromServer).isNotNull(); + assertThat(requestFromServer.getMessage()).isNotNull(); + JsonNode requestFromServerJson = JacksonUtil.toJsonNode(requestFromServer.getMessage()); + assertThat(requestFromServerJson.get("device").asText()).isEqualTo(createdDevice.getName()); + assertThat(requestFromServerJson.get("data").get("method").asText()).isEqualTo("getValue"); + assertThat(requestFromServerJson.get("data").get("params").asText()).isEqualTo("true"); + int requestId = requestFromServerJson.get("data").get("id").asInt(); JsonObject clientResponse = new JsonObject(); clientResponse.addProperty("response", "someResponse"); @@ -369,16 +345,15 @@ public class MqttGatewayClientTest extends AbstractContainerTest { // Send a response to the server's RPC request mqttClient.publish(gatewayRpcTopic, Unpooled.wrappedBuffer(gatewayResponse.toString().getBytes())).get(); - ResponseEntity serverResponse = future.get(5 * timeoutMultiplier, TimeUnit.SECONDS); - Assert.assertTrue(serverResponse.getStatusCode().is2xxSuccessful()); - Assert.assertEquals(clientResponse.toString(), serverResponse.getBody()); + JsonNode serverResponse = future.get(5 * timeoutMultiplier, TimeUnit.SECONDS); + + assertThat(serverResponse).isEqualTo(mapper.readTree(clientResponse.toString())); } @Test public void deviceCreationAfterDeleted() throws Exception { - restClient.getRestTemplate().delete(HTTPS_URL + "/api/device/" + this.createdDevice.getId()); - Optional deletedDevice = restClient.getDeviceById(this.createdDevice.getId()); - Assert.assertTrue(deletedDevice.isEmpty()); + testRestClient.deleteDevice(this.createdDevice.getId()); + testRestClient.getDeviceById(this.createdDevice.getId(), HttpStatus.NOT_FOUND.value()); this.createdDevice = createDeviceThroughGateway(mqttClient, gatewayDevice); } @@ -397,13 +372,13 @@ public class MqttGatewayClientTest extends AbstractContainerTest { log.info(gatewayAttributesRequest.toString()); mqttClient.publish("v1/gateway/attributes/request", Unpooled.wrappedBuffer(gatewayAttributesRequest.toString().getBytes())).get(); MqttEvent clientAttributeEvent = listener.getEvents().poll(10 * timeoutMultiplier, TimeUnit.SECONDS); - Assert.assertNotNull(clientAttributeEvent); + assertThat(clientAttributeEvent).isNotNull(); JsonObject responseMessage = new JsonParser().parse(Objects.requireNonNull(clientAttributeEvent).getMessage()).getAsJsonObject(); - Assert.assertEquals(messageId, responseMessage.get("id").getAsInt()); - Assert.assertEquals(createdDevice.getName(), responseMessage.get("device").getAsString()); - Assert.assertEquals(3, responseMessage.entrySet().size()); - Assert.assertEquals(expectedValue, responseMessage.get("value").getAsString()); + assertThat(responseMessage.get("id").getAsInt()).isEqualTo(messageId); + assertThat(responseMessage.get("device").getAsString()).isEqualTo(createdDevice.getName()); + assertThat(responseMessage.entrySet()).hasSize(3); + assertThat(responseMessage.get("value").getAsString()).isEqualTo(expectedValue); } private Device createDeviceThroughGateway(MqttClient mqttClient, Device gatewayDevice) throws Exception { @@ -411,24 +386,19 @@ public class MqttGatewayClientTest extends AbstractContainerTest { TimeUnit.SECONDS.sleep(30); } - String deviceName = "mqtt_device"; + String deviceName = "mqtt_device" + RandomStringUtils.randomAlphabetic(5); mqttClient.publish("v1/gateway/connect", Unpooled.wrappedBuffer(createGatewayConnectPayload(deviceName).toString().getBytes()), MqttQoS.AT_LEAST_ONCE).get(); if (timeoutMultiplier > 1) { TimeUnit.SECONDS.sleep(30); } - List relations = restClient.findByFrom(gatewayDevice.getId(), RelationTypeGroup.COMMON); - - Assert.assertEquals(1, relations.size()); + List relations = testRestClient.findRelationByFrom(gatewayDevice.getId(), RelationTypeGroup.COMMON); + assertThat(relations).hasSize(1); EntityId createdEntityId = relations.get(0).getTo(); DeviceId createdDeviceId = new DeviceId(createdEntityId.getId()); - Optional createdDevice = restClient.getDeviceById(createdDeviceId); - - Assert.assertTrue(createdDevice.isPresent()); - - return createdDevice.get(); + return testRestClient.getDeviceById(createdDeviceId); } private MqttClient getMqttClient(DeviceCredentials deviceCredentials, MqttMessageListener listener) throws InterruptedException, ExecutionException { diff --git a/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/prototypes/DevicePrototypes.java b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/prototypes/DevicePrototypes.java new file mode 100644 index 0000000000..7db35460a9 --- /dev/null +++ b/msa/black-box-tests/src/test/java/org/thingsboard/server/msa/prototypes/DevicePrototypes.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.msa.prototypes; + +import com.fasterxml.jackson.databind.JsonNode; +import org.testcontainers.shaded.org.apache.commons.lang3.RandomStringUtils; +import org.thingsboard.common.util.JacksonUtil; +import org.thingsboard.server.common.data.Device; + +public class DevicePrototypes { + public static Device defaultDevicePrototype(String name){ + Device device = new Device(); + device.setName(name + RandomStringUtils.randomAlphanumeric(7)); + device.setType("DEFAULT"); + return device; + } + + public static Device defaultGatewayPrototype() { + String isGateway = "{\"gateway\":true}"; + JsonNode additionalInfo = JacksonUtil.toJsonNode(isGateway); + Device gatewayDeviceTemplate = new Device(); + gatewayDeviceTemplate.setName("mqtt_gateway_" + RandomStringUtils.randomAlphanumeric(5)); + gatewayDeviceTemplate.setType("gateway"); + gatewayDeviceTemplate.setAdditionalInfo(additionalInfo); + return gatewayDeviceTemplate; + } +} diff --git a/msa/black-box-tests/src/test/resources/config.properties b/msa/black-box-tests/src/test/resources/config.properties new file mode 100644 index 0000000000..419c73185d --- /dev/null +++ b/msa/black-box-tests/src/test/resources/config.properties @@ -0,0 +1,2 @@ +tb.baseUrl=http://localhost:8080 +tb.wsUrl=ws://localhost:8080 diff --git a/msa/black-box-tests/src/test/resources/testNG.xml b/msa/black-box-tests/src/test/resources/testNG.xml new file mode 100644 index 0000000000..45e93f76f1 --- /dev/null +++ b/msa/black-box-tests/src/test/resources/testNG.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/msa/js-executor/api/jsExecutor.models.ts b/msa/js-executor/api/jsExecutor.models.ts index 7a6b53cd8a..17407f4d50 100644 --- a/msa/js-executor/api/jsExecutor.models.ts +++ b/msa/js-executor/api/jsExecutor.models.ts @@ -56,7 +56,7 @@ export interface JsCompileResponse extends TbMessage { export interface JsInvokeResponse { success: boolean; - result: string; + result?: string; errorCode?: number; errorDetails?: string; } diff --git a/msa/js-executor/api/jsInvokeMessageProcessor.ts b/msa/js-executor/api/jsInvokeMessageProcessor.ts index 668cd61f50..52a337c74a 100644 --- a/msa/js-executor/api/jsInvokeMessageProcessor.ts +++ b/msa/js-executor/api/jsInvokeMessageProcessor.ts @@ -39,6 +39,7 @@ const TIMEOUT_ERROR = 2; const NOT_FOUND_ERROR = 3; const statFrequency = Number(config.get('script.stat_print_frequency')); +const memoryUsageTraceFrequency = Number(config.get('script.memory_usage_trace_frequency')); const scriptBodyTraceFrequency = Number(config.get('script.script_body_trace_frequency')); const useSandbox = config.get('script.use_sandbox') === 'true'; const maxActiveScripts = Number(config.get('script.max_active_scripts')); @@ -167,11 +168,15 @@ export class JsInvokeMessageProcessor { if (this.executedScriptsCounter % scriptBodyTraceFrequency == 0) { this.logger.info('[%s] Executing script body: [%s]', scriptId, invokeRequest.scriptBody); } + if (this.executedScriptsCounter % memoryUsageTraceFrequency == 0) { + this.logger.info('Current memory usage: [%s]', process.memoryUsage()); + } + this.getOrCompileScript(scriptId, invokeRequest.scriptBody).then( (script) => { this.executor.executeScript(script, invokeRequest.args, invokeRequest.timeout).then( - (result) => { - if (result.length <= maxResultSize) { + (result: string | undefined) => { + if (!result || result.length <= maxResultSize) { const invokeResponse = JsInvokeMessageProcessor.createInvokeResponse(result, true); this.logger.debug('[%s] Sending success invoke response, scriptId: [%s]', requestId, scriptId); this.sendResponse(requestId, responseTopic, headers, scriptId, undefined, invokeResponse); @@ -323,7 +328,7 @@ export class JsInvokeMessageProcessor { } } - private static createInvokeResponse(result: string, success: boolean, errorCode?: number, err?: any): JsInvokeResponse { + private static createInvokeResponse(result: string | undefined, success: boolean, errorCode?: number, err?: any): JsInvokeResponse { return { errorCode: errorCode, success: success, diff --git a/msa/js-executor/config/custom-environment-variables.yml b/msa/js-executor/config/custom-environment-variables.yml index b9c24c8d8d..2ebea4ccc1 100644 --- a/msa/js-executor/config/custom-environment-variables.yml +++ b/msa/js-executor/config/custom-environment-variables.yml @@ -75,6 +75,7 @@ logger: script: use_sandbox: "SCRIPT_USE_SANDBOX" + memory_usage_trace_frequency: "MEMORY_USAGE_TRACE_FREQUENCY" stat_print_frequency: "SCRIPT_STAT_PRINT_FREQUENCY" script_body_trace_frequency: "SCRIPT_BODY_TRACE_FREQUENCY" max_active_scripts: "MAX_ACTIVE_SCRIPTS" diff --git a/msa/js-executor/config/default.yml b/msa/js-executor/config/default.yml index 64829ef792..96f3401da5 100644 --- a/msa/js-executor/config/default.yml +++ b/msa/js-executor/config/default.yml @@ -64,6 +64,7 @@ logger: script: use_sandbox: "true" + memory_usage_trace_frequency: "1000" script_body_trace_frequency: "10000" stat_print_frequency: "10000" max_active_scripts: "1000" diff --git a/msa/js-executor/docker/start-js-executor.sh b/msa/js-executor/docker/start-js-executor.sh index 575f93c389..d30b62c145 100755 --- a/msa/js-executor/docker/start-js-executor.sh +++ b/msa/js-executor/docker/start-js-executor.sh @@ -27,4 +27,4 @@ source "${CONF_FOLDER}/${configfile}" cd ${pkg.installFolder} # This will forward this PID 1 to the node.js and forward SIGTERM for graceful shutdown as well -exec node server.js +exec node --no-compilation-cache server.js diff --git a/msa/js-executor/package.json b/msa/js-executor/package.json index dea0eb29c1..e094dcc4e5 100644 --- a/msa/js-executor/package.json +++ b/msa/js-executor/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-js-executor", "private": true, - "version": "3.4.2", + "version": "3.4.3", "description": "ThingsBoard JavaScript Executor Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/js-executor/pom.xml b/msa/js-executor/pom.xml index 95a4e0c724..860eb3891a 100644 --- a/msa/js-executor/pom.xml +++ b/msa/js-executor/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa 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/msa/pom.xml b/msa/pom.xml index e527566683..9e628b0f70 100644 --- a/msa/pom.xml +++ b/msa/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard msa diff --git a/msa/tb-node/pom.xml b/msa/tb-node/pom.xml index 965d0ab05a..7119da74d8 100644 --- a/msa/tb-node/pom.xml +++ b/msa/tb-node/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/tb/pom.xml b/msa/tb/pom.xml index f204a6822f..46a2a632be 100644 --- a/msa/tb/pom.xml +++ b/msa/tb/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/coap/pom.xml b/msa/transport/coap/pom.xml index 3b5a91dd13..82d7e02da6 100644 --- a/msa/transport/coap/pom.xml +++ b/msa/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/http/pom.xml b/msa/transport/http/pom.xml index 6fbb13f32f..a596c5000f 100644 --- a/msa/transport/http/pom.xml +++ b/msa/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/lwm2m/pom.xml b/msa/transport/lwm2m/pom.xml index fa8d99e8b5..0a43e7deb5 100644 --- a/msa/transport/lwm2m/pom.xml +++ b/msa/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/mqtt/pom.xml b/msa/transport/mqtt/pom.xml index c63fd43668..2fe358fe5d 100644 --- a/msa/transport/mqtt/pom.xml +++ b/msa/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard.msa - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.msa.transport diff --git a/msa/transport/pom.xml b/msa/transport/pom.xml index 38f08cffaf..3339d93577 100644 --- a/msa/transport/pom.xml +++ b/msa/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/transport/snmp/pom.xml b/msa/transport/snmp/pom.xml index 8edd6dfeaa..1a352f47fb 100644 --- a/msa/transport/snmp/pom.xml +++ b/msa/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard.msa transport - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT org.thingsboard.msa.transport diff --git a/msa/vc-executor-docker/pom.xml b/msa/vc-executor-docker/pom.xml index 38c6979c83..05884931d3 100644 --- a/msa/vc-executor-docker/pom.xml +++ b/msa/vc-executor-docker/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/vc-executor/pom.xml b/msa/vc-executor/pom.xml index 2a4d07011e..31c96c583c 100644 --- a/msa/vc-executor/pom.xml +++ b/msa/vc-executor/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa diff --git a/msa/web-ui/package.json b/msa/web-ui/package.json index 4529d51510..035b8a3072 100644 --- a/msa/web-ui/package.json +++ b/msa/web-ui/package.json @@ -1,7 +1,7 @@ { "name": "thingsboard-web-ui", "private": true, - "version": "3.4.2", + "version": "3.4.3", "description": "ThingsBoard Web UI Microservice", "main": "server.ts", "bin": "server.js", diff --git a/msa/web-ui/pom.xml b/msa/web-ui/pom.xml index 872170798f..5a0a444687 100644 --- a/msa/web-ui/pom.xml +++ b/msa/web-ui/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT msa org.thingsboard.msa diff --git a/netty-mqtt/pom.xml b/netty-mqtt/pom.xml index 5b4c8506c6..99d05fc583 100644 --- a/netty-mqtt/pom.xml +++ b/netty-mqtt/pom.xml @@ -19,11 +19,11 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard netty-mqtt - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT jar Netty MQTT Client diff --git a/packaging/java/scripts/install/logback.xml b/packaging/java/scripts/install/logback.xml index 0047956c93..9233ab4d0b 100644 --- a/packaging/java/scripts/install/logback.xml +++ b/packaging/java/scripts/install/logback.xml @@ -56,6 +56,10 @@ + + + + diff --git a/pom.xml b/pom.xml index 60e8783bca..f1c3adbad8 100755 --- a/pom.xml +++ b/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT pom Thingsboard @@ -77,7 +77,7 @@ 3.5.5 3.21.9 1.42.1 - 2.4.23TB + 1.0.3 1.18.18 1.2.4 4.1.75.Final @@ -134,6 +134,10 @@ 1.3.0 1.2.7 + 7.6.1 + 3.23.1 + 5.2.0 + 1.3 1.17.3 1.12 3.0.0 @@ -1571,8 +1575,8 @@ org.thingsboard - mvel2 - ${mvel.version} + tbel + ${tbel.version} org.springframework @@ -1634,6 +1638,30 @@ + + org.testng + testng + ${testng.version} + test + + + org.assertj + assertj-core + ${assertj.version} + test + + + io.rest-assured + rest-assured + ${rest-assured.version} + test + + + org.hamcrest + hamcrest-all + ${hamcrest.version} + test + org.awaitility awaitility diff --git a/rest-client/pom.xml b/rest-client/pom.xml index 4b9efe6177..24f8453786 100644 --- a/rest-client/pom.xml +++ b/rest-client/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard rest-client diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java index 21a18534ed..ec1ead3e6b 100644 --- a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -136,6 +136,8 @@ import org.thingsboard.server.common.data.rule.RuleChainMetaData; import org.thingsboard.server.common.data.rule.RuleChainType; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.security.model.JwtPair; +import org.thingsboard.server.common.data.security.model.JwtSettings; import org.thingsboard.server.common.data.security.model.SecuritySettings; import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; import org.thingsboard.server.common.data.sms.config.TestSmsRequest; @@ -286,6 +288,23 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { return restTemplate.postForEntity(baseURL + "/api/admin/securitySettings", securitySettings, SecuritySettings.class).getBody(); } + public Optional getJwtSettings() { + try { + ResponseEntity jwtSettings = restTemplate.getForEntity(baseURL + "/api/admin/jwtSettings", JwtSettings.class); + return Optional.ofNullable(jwtSettings.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public JwtPair saveJwtSettings(JwtSettings jwtSettings) { + return restTemplate.postForEntity(baseURL + "/api/admin/jwtSettings", jwtSettings, JwtPair.class).getBody(); + } + public Optional getRepositorySettings() { try { ResponseEntity repositorySettings = restTemplate.getForEntity(baseURL + "/api/admin/repositorySettings", RepositorySettings.class); @@ -2032,10 +2051,15 @@ public class RestClient implements ClientHttpRequestInterceptor, Closeable { } public PageData getRuleChains(PageLink pageLink) { + return getRuleChains(RuleChainType.CORE, pageLink); + } + + public PageData getRuleChains(RuleChainType ruleChainType, PageLink pageLink) { Map params = new HashMap<>(); + params.put("type", ruleChainType.name()); addPageLinkToParam(params, pageLink); return restTemplate.exchange( - baseURL + "/api/ruleChains?" + getUrlParams(pageLink), + baseURL + "/api/ruleChains?type={type}&" + getUrlParams(pageLink), HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 438c2d3157..126885cd22 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard rule-engine diff --git a/rule-engine/rule-engine-api/pom.xml b/rule-engine/rule-engine-api/pom.xml index 99170b585c..665158a44f 100644 --- a/rule-engine/rule-engine-api/pom.xml +++ b/rule-engine/rule-engine-api/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT rule-engine org.thingsboard.rule-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/RuleEngineTelemetryService.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java index dbee45c230..7d47195d96 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineTelemetryService.java @@ -64,6 +64,8 @@ public interface RuleEngineTelemetryService { void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, FutureCallback callback); + void deleteAndNotify(TenantId tenantId, EntityId entityId, String scope, List keys, boolean notifyDevice, FutureCallback callback); + void deleteLatest(TenantId tenantId, EntityId entityId, List keys, FutureCallback callback); void deleteAllLatest(TenantId tenantId, EntityId entityId, FutureCallback> callback); 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 ede2f716e5..e23d850ead 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 @@ -62,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; @@ -197,6 +199,8 @@ public interface TbContext { * */ + void checkTenantEntity(EntityId entityId); + boolean isLocalEntity(EntityId entityId); RuleNodeId getSelfId(); @@ -316,4 +320,9 @@ public interface TbContext { TenantProfile getTenantProfile(); + WidgetsBundleService getWidgetBundleService(); + + WidgetTypeService getWidgetTypeService(); + + RuleEngineApiUsageStateService getRuleEngineApiUsageStateService(); } diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java index e5e5a6a938..7da2695703 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/msg/DeviceAttributesEventNotificationMsg.java @@ -25,6 +25,7 @@ import org.thingsboard.server.common.data.kv.AttributeKvEntry; import org.thingsboard.server.common.msg.MsgType; import org.thingsboard.server.common.msg.ToDeviceActorNotificationMsg; +import java.util.HashSet; import java.util.List; import java.util.Set; @@ -52,8 +53,10 @@ public class DeviceAttributesEventNotificationMsg implements ToDeviceActorNotifi return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, null, scope, values, false); } - public static DeviceAttributesEventNotificationMsg onDelete(TenantId tenantId, DeviceId deviceId, Set keys) { - return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keys, null, null, true); + public static DeviceAttributesEventNotificationMsg onDelete(TenantId tenantId, DeviceId deviceId, String scope, List keys) { + Set keysToNotify = new HashSet<>(); + keys.forEach(key -> keysToNotify.add(new AttributeKey(scope, key))); + return new DeviceAttributesEventNotificationMsg(tenantId, deviceId, keysToNotify, null, null, true); } @Override diff --git a/rule-engine/rule-engine-components/pom.xml b/rule-engine/rule-engine-components/pom.xml index 996063823b..d32d8163a6 100644 --- a/rule-engine/rule-engine-components/pom.xml +++ b/rule-engine/rule-engine-components/pom.xml @@ -22,7 +22,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT rule-engine org.thingsboard.rule-engine 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 27080da2e8..82544417e3 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 @@ -48,7 +48,7 @@ public abstract class TbAbstractAlarmNode 20;"); - configuration.setMvelScript("return msg.temperature > 20;"); + configuration.setTbelScript("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 9fddb5ca8f..339f873532 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 @@ -56,7 +56,7 @@ public class TbJsSwitchNode implements TbNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbJsSwitchNodeConfiguration.class); this.scriptEngine = ctx.createScriptEngine(config.getScriptLang(), - ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript()); + ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript()); } @Override 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 d0640af982..70e00b866c 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 @@ -15,13 +15,10 @@ */ 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 { @@ -33,7 +30,7 @@ public class TbJsSwitchNodeConfiguration implements NodeConfiguration> headers, BiConsumer consumer) { + if (headers == null) { + return; + } + headers.forEach((key, values) -> { + if (values != null && !values.isEmpty()) { + if (values.size() == 1) { + consumer.accept(key, values.get(0)); + } else { + consumer.accept(key, JacksonUtil.toString(values)); + } + } + }); + } + private TbMsg processFailureResponse(TbContext ctx, TbMsg origMsg, ResponseEntity response) { TbMsgMetaData metaData = origMsg.getMetaData(); metaData.putValue(STATUS, response.getStatusCode().name()); metaData.putValue(STATUS_CODE, response.getStatusCode().value() + ""); metaData.putValue(STATUS_REASON, response.getStatusCode().getReasonPhrase()); metaData.putValue(ERROR_BODY, response.getBody()); + headersToMetaData(response.getHeaders(), metaData::putValue); return ctx.transformMsg(origMsg, origMsg.getType(), origMsg.getOriginator(), metaData, origMsg.getData()); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java index ce9b5050f0..405a73944e 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rpc/TbSendRPCReplyNode.java @@ -15,15 +15,27 @@ */ package org.thingsboard.rule.engine.rpc; +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; import lombok.extern.slf4j.Slf4j; -import org.thingsboard.server.common.data.StringUtils; +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.DataConstants; +import org.thingsboard.server.common.data.EdgeUtils; import org.thingsboard.server.common.data.EntityType; +import org.thingsboard.server.common.data.StringUtils; +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.DeviceId; +import org.thingsboard.server.common.data.id.EdgeId; import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.msg.TbMsg; @@ -65,9 +77,46 @@ public class TbSendRPCReplyNode implements TbNode { } else if (StringUtils.isEmpty(msg.getData())) { ctx.tellFailure(msg, new RuntimeException("Request body is empty!")); } else { - ctx.getRpcService().sendRpcReplyToDevice(serviceIdStr, UUID.fromString(sessionIdStr), Integer.parseInt(requestIdStr), msg.getData()); - ctx.tellSuccess(msg); + if (StringUtils.isNotBlank(msg.getMetaData().getValue(DataConstants.EDGE_ID))) { + saveRpcResponseToEdgeQueue(ctx, msg, serviceIdStr, sessionIdStr, requestIdStr); + } else { + ctx.getRpcService().sendRpcReplyToDevice(serviceIdStr, UUID.fromString(sessionIdStr), Integer.parseInt(requestIdStr), msg.getData()); + ctx.tellSuccess(msg); + } } } + private void saveRpcResponseToEdgeQueue(TbContext ctx, TbMsg msg, String serviceIdStr, String sessionIdStr, String requestIdStr) { + EdgeId edgeId; + DeviceId deviceId; + try { + edgeId = new EdgeId(UUID.fromString(msg.getMetaData().getValue(DataConstants.EDGE_ID))); + deviceId = new DeviceId(UUID.fromString(msg.getMetaData().getValue(DataConstants.DEVICE_ID))); + } catch (Exception e) { + String errMsg = String.format("[%s] Failed to parse edgeId or deviceId from metadata %s!", ctx.getTenantId(), msg.getMetaData()); + ctx.tellFailure(msg, new RuntimeException(errMsg)); + return; + } + + ObjectNode body = JacksonUtil.OBJECT_MAPPER.createObjectNode(); + body.put("serviceId", serviceIdStr); + body.put("sessionId", sessionIdStr); + body.put("requestId", requestIdStr); + body.put("response", msg.getData()); + EdgeEvent edgeEvent = EdgeUtils.constructEdgeEvent(ctx.getTenantId(), edgeId, EdgeEventType.DEVICE, + EdgeEventActionType.RPC_CALL, deviceId, JacksonUtil.OBJECT_MAPPER.valueToTree(body)); + ListenableFuture 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/telemetry/AttributesUpdateNodeCallback.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java index f8ee4a29b1..6044cdab9c 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/AttributesUpdateNodeCallback.java @@ -24,8 +24,8 @@ import java.util.List; public class AttributesUpdateNodeCallback extends TelemetryNodeCallback { - private String scope; - private List attributes; + private final String scope; + private final List attributes; public AttributesUpdateNodeCallback(TbContext ctx, TbMsg msg, String scope, List attributes) { super(ctx, msg); diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java index b810774832..771f2a0d6d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNode.java @@ -33,6 +33,10 @@ import org.thingsboard.server.common.transport.adaptor.JsonConverter; import java.util.ArrayList; import java.util.List; +import static org.thingsboard.server.common.data.DataConstants.CLIENT_SCOPE; +import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; +import static org.thingsboard.server.common.data.DataConstants.SCOPE; + @Slf4j @RuleNode( type = ComponentType.ACTION, @@ -40,8 +44,9 @@ import java.util.List; configClazz = TbMsgAttributesNodeConfiguration.class, nodeDescription = "Saves attributes data", nodeDetails = "Saves entity attributes based on configurable scope parameter. Expects messages with 'POST_ATTRIBUTES_REQUEST' message type. " + - "If upsert(update/insert) operation is completed successfully, rule node will send the \"Attributes Updated\" " + - "event to the root chain of the message originator and send the incoming message via Success chain, otherwise, Failure chain is used.", + "If upsert(update/insert) operation is completed successfully rule node will send the incoming message via Success chain, otherwise, Failure chain is used. " + + "Additionally if checkbox Send attributes updated notification is set to true, rule node will put the \"Attributes Updated\" " + + "event for SHARED_SCOPE and SERVER_SCOPE attributes updates to the corresponding rule engine queue.", uiResources = {"static/rulenode/rulenode-core-config.js"}, configDirective = "tbActionNodeAttributesConfig", icon = "file_upload" @@ -65,20 +70,38 @@ public class TbMsgAttributesNode implements TbNode { return; } String src = msg.getData(); - List attributes = new ArrayList<>(JsonConverter.convertToAttributes(new JsonParser().parse(src))); + List attributes = new ArrayList<>(JsonConverter.convertToAttributes(JsonParser.parseString(src))); if (attributes.isEmpty()) { ctx.tellSuccess(msg); return; } - String notifyDeviceStr = msg.getMetaData().getValue("notifyDevice"); + String scope = getScope(msg.getMetaData().getValue(SCOPE)); + boolean sendAttributesUpdateNotification = checkSendNotification(scope); ctx.getTelemetryService().saveAndNotify( ctx.getTenantId(), msg.getOriginator(), - config.getScope(), + scope, attributes, - config.getNotifyDevice() || StringUtils.isEmpty(notifyDeviceStr) || Boolean.parseBoolean(notifyDeviceStr), - new AttributesUpdateNodeCallback(ctx, msg, config.getScope(), attributes) + checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY)), + sendAttributesUpdateNotification ? + new AttributesUpdateNodeCallback(ctx, msg, scope, attributes) : + new TelemetryNodeCallback(ctx, msg) ); } + private boolean checkSendNotification(String scope) { + return config.isSendAttributesUpdatedNotification() && !CLIENT_SCOPE.equals(scope); + } + + private boolean checkNotifyDevice(String notifyDeviceMdValue) { + return config.getNotifyDevice() || StringUtils.isEmpty(notifyDeviceMdValue) || Boolean.parseBoolean(notifyDeviceMdValue); + } + + private String getScope(String mdScopeValue) { + if (StringUtils.isNotEmpty(mdScopeValue)) { + return mdScopeValue; + } + return config.getScope(); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java index 1586048b81..93b8d63b47 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgAttributesNodeConfiguration.java @@ -25,12 +25,14 @@ public class TbMsgAttributesNodeConfiguration implements NodeConfiguration keys; @Override public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { - this.config = TbNodeUtils.convert(configuration, TbMsgDeleteAttributesConfiguration.class); - this.scope = config.getScope(); + this.config = TbNodeUtils.convert(configuration, TbMsgDeleteAttributesNodeConfiguration.class); this.keys = config.getKeys(); } @@ -67,7 +69,29 @@ public class TbMsgDeleteAttributes implements TbNode { if (keysToDelete.isEmpty()) { ctx.tellSuccess(msg); } else { - ctx.getTelemetryService().deleteAndNotify(ctx.getTenantId(), msg.getOriginator(), scope, keysToDelete, new AttributesDeleteNodeCallback(ctx, msg, scope, keysToDelete)); + String scope = getScope(msg.getMetaData().getValue(SCOPE)); + ctx.getTelemetryService().deleteAndNotify( + ctx.getTenantId(), + msg.getOriginator(), + scope, + keysToDelete, + checkNotifyDevice(msg.getMetaData().getValue(NOTIFY_DEVICE_METADATA_KEY), scope), + config.isSendAttributesDeletedNotification() ? + new AttributesDeleteNodeCallback(ctx, msg, scope, keysToDelete) : + new TelemetryNodeCallback(ctx, msg) + ); + } + } + + private String getScope(String mdScopeValue) { + if (StringUtils.isNotEmpty(mdScopeValue)) { + return mdScopeValue; } + return config.getScope(); } + + private boolean checkNotifyDevice(String notifyDeviceMdValue, String scope) { + return SHARED_SCOPE.equals(scope) && (config.isNotifyDevice() || Boolean.parseBoolean(notifyDeviceMdValue)); + } + } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeConfiguration.java similarity index 68% rename from rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesConfiguration.java rename to rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeConfiguration.java index 03bef2a4e6..02e084c5a2 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeConfiguration.java @@ -23,16 +23,20 @@ import java.util.Collections; import java.util.List; @Data -public class TbMsgDeleteAttributesConfiguration implements NodeConfiguration { +public class TbMsgDeleteAttributesNodeConfiguration implements NodeConfiguration { private String scope; private List keys; + private boolean sendAttributesDeletedNotification; + private boolean notifyDevice; @Override - public TbMsgDeleteAttributesConfiguration defaultConfiguration() { - TbMsgDeleteAttributesConfiguration configuration = new TbMsgDeleteAttributesConfiguration(); + public TbMsgDeleteAttributesNodeConfiguration defaultConfiguration() { + TbMsgDeleteAttributesNodeConfiguration configuration = new TbMsgDeleteAttributesNodeConfiguration(); configuration.setScope(DataConstants.SERVER_SCOPE); configuration.setKeys(Collections.emptyList()); + configuration.setSendAttributesDeletedNotification(false); + configuration.setNotifyDevice(false); return configuration; } } 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 8a801cbb7f..52fa68dba8 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 @@ -127,6 +127,7 @@ public class TbChangeOriginatorNode extends TbAbstractTransformNode { log.error("EntityNamePattern not specified for type [{}]", conf.getEntityType()); throw new IllegalArgumentException("Wrong config for [{}] in TbChangeOriginatorNode!" + ENTITY_SOURCE); } + EntitiesByNameAndTypeLoader.checkEntityType(EntityType.valueOf(conf.getEntityType())); } } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java index ff5b8a1ece..e79618d2ef 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/transform/TbJsonPathNode.java @@ -70,7 +70,7 @@ public class TbJsonPathNode implements TbNode { public void onMsg(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException, TbNodeException { if (!TbJsonPathNodeConfiguration.DEFAULT_JSON_PATH.equals(this.jsonPathValue)) { try { - JsonNode jsonPathData = jsonPath.read(msg.getData(), this.configurationJsonPath); + Object jsonPathData = jsonPath.read(msg.getData(), this.configurationJsonPath); ctx.tellSuccess(TbMsg.transformMsg(msg, msg.getType(), msg.getOriginator(), msg.getMetaData(), JacksonUtil.toString(jsonPathData))); } catch (PathNotFoundException e) { ctx.tellFailure(msg, e); 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 fd33bb4554..4611538e4d 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,6 @@ 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; @@ -53,7 +52,7 @@ public class TbTransformMsgNode extends TbAbstractTransformNode { public void init(TbContext ctx, TbNodeConfiguration configuration) throws TbNodeException { this.config = TbNodeUtils.convert(configuration, TbTransformMsgNodeConfiguration.class); scriptEngine = ctx.createScriptEngine(config.getScriptLang(), - ScriptLanguage.MVEL.equals(config.getScriptLang()) ? config.getMvelScript() : config.getJsScript()); + ScriptLanguage.TBEL.equals(config.getScriptLang()) ? config.getTbelScript() : config.getJsScript()); setConfig(config); } 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 8779fa2541..5b5d206a76 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 @@ -24,14 +24,14 @@ public class TbTransformMsgNodeConfiguration extends TbTransformNodeConfiguratio private ScriptLanguage scriptLang; private String jsScript; - private String mvelScript; + private String tbelScript; @Override public TbTransformMsgNodeConfiguration defaultConfiguration() { TbTransformMsgNodeConfiguration configuration = new TbTransformMsgNodeConfiguration(); - configuration.setScriptLang(ScriptLanguage.MVEL); + configuration.setScriptLang(ScriptLanguage.TBEL); configuration.setJsScript("return {msg: msg, metadata: metadata, msgType: msgType};"); - configuration.setMvelScript("return {msg: msg, metadata: metadata, msgType: msgType};"); + configuration.setTbelScript("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/EntitiesByNameAndTypeLoader.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java index d70865f197..21f2cf5822 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/util/EntitiesByNameAndTypeLoader.java @@ -16,75 +16,52 @@ package org.thingsboard.rule.engine.util; import org.thingsboard.rule.engine.api.TbContext; -import org.thingsboard.server.common.data.Customer; -import org.thingsboard.server.common.data.DashboardInfo; -import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.EntityType; -import org.thingsboard.server.common.data.EntityView; -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.SearchTextBasedWithAdditionalInfo; import org.thingsboard.server.common.data.id.EntityId; -import java.util.Optional; +import java.util.List; public class EntitiesByNameAndTypeLoader { + private static final List AVAILABLE_ENTITY_TYPES = List.of( + EntityType.DEVICE, + EntityType.ASSET, + EntityType.ENTITY_VIEW, + EntityType.EDGE, + EntityType.USER); + public static EntityId findEntityId(TbContext ctx, EntityType entityType, String entityName) { - EntityId targetEntityId = null; + SearchTextBasedWithAdditionalInfo targetEntity; switch (entityType) { case DEVICE: - Device device = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName); - if (device != null) { - targetEntityId = device.getId(); - } + targetEntity = ctx.getDeviceService().findDeviceByTenantIdAndName(ctx.getTenantId(), entityName); break; case ASSET: - Asset asset = ctx.getAssetService().findAssetByTenantIdAndName(ctx.getTenantId(), entityName); - if (asset != null) { - targetEntityId = asset.getId(); - } - break; - case CUSTOMER: - Optional customerOptional = ctx.getCustomerService().findCustomerByTenantIdAndTitle(ctx.getTenantId(), entityName); - if (customerOptional.isPresent()) { - targetEntityId = customerOptional.get().getId(); - } - break; - case TENANT: - targetEntityId = ctx.getTenantId(); + targetEntity = ctx.getAssetService().findAssetByTenantIdAndName(ctx.getTenantId(), entityName); break; case ENTITY_VIEW: - EntityView entityView = ctx.getEntityViewService().findEntityViewByTenantIdAndName(ctx.getTenantId(), entityName); - if (entityView != null) { - targetEntityId = entityView.getId(); - } + targetEntity = ctx.getEntityViewService().findEntityViewByTenantIdAndName(ctx.getTenantId(), entityName); break; case EDGE: - Edge edge = ctx.getEdgeService().findEdgeByTenantIdAndName(ctx.getTenantId(), entityName); - if (edge != null) { - targetEntityId = edge.getId(); - } - break; - case DASHBOARD: - DashboardInfo dashboardInfo = ctx.getDashboardService().findFirstDashboardInfoByTenantIdAndName(ctx.getTenantId(), entityName); - if (dashboardInfo != null) { - targetEntityId = dashboardInfo.getId(); - } + targetEntity = ctx.getEdgeService().findEdgeByTenantIdAndName(ctx.getTenantId(), entityName); break; case USER: - User user = ctx.getUserService().findUserByEmail(ctx.getTenantId(), entityName); - if (user != null) { - targetEntityId = user.getId(); - } + targetEntity = ctx.getUserService().findUserByTenantIdAndEmail(ctx.getTenantId(), entityName); break; default: throw new IllegalStateException("Unexpected entity type " + entityType.name()); } - if (targetEntityId == null) { + if (targetEntity == null) { throw new IllegalStateException("Failed to found " + entityType.name() + " entity by name: '" + entityName + "'!"); } - return targetEntityId; + return targetEntity.getId(); + } + + public static void checkEntityType(EntityType entityType) { + if (!AVAILABLE_ENTITY_TYPES.contains(entityType)) { + throw new IllegalStateException("Unexpected entity type " + entityType.name()); + } } } 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/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 2ee538bdfd..86a6ad51cb 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/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 +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,E,V,P,R,O,w,B,H,U,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){E=t},function(t){V=t},function(t){P=t},function(t){R=t},function(t){O=t},function(t){w=t,B=t.CommonModule},function(t){H=t},function(t){U=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 Et{constructor(t){this.sanitizer=t}transform(t){return this.sanitizer.bypassSecurityTrustHtml(t)}}t("SafeHtmlPipe",Et),Et.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Et,deps:[{token:H.DomSanitizer}],target:e.ɵɵFactoryTarget.Pipe}),Et.ɵpipe=e.ɵɵngDeclarePipe({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Et,name:"safeHtml"}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Et,decorators:[{type:r,args:[{name:"safeHtml"}]}],ctorParameters:function(){return[{type:H.DomSanitizer}]}});class Vt 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",Vt),Vt.ɵfac=e.ɵɵngDeclareFactory({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Vt,deps:[{token:k.Store},{token:M.FormBuilder}],target:e.ɵɵFactoryTarget.Component}),Vt.ɵcmp=e.ɵɵngDeclareComponent({minVersion:"12.0.0",version:"12.2.16",type:Vt,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:E.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Vt,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.attributeScopeMap=m,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,[]],sendAttributesUpdatedNotification:[!!t&&t.sendAttributesUpdatedNotification,[]]}),this.attributesConfigForm.get("scope").valueChanges.subscribe((t=>{t!==m.SHARED_SCOPE&&this.attributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1}),t===m.CLIENT_SCOPE&&this.attributesConfigForm.get("sendAttributesUpdatedNotification").patchValue(!1,{emitEvent:!1})}))}}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 \n {{ \'tb.rulenode.send-attributes-updated-notification\' | translate }}\n \n
tb.rulenode.send-attributes-updated-notification-hint
\n
\n
\n',components:[{type:E.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:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.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:E.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 Bt=new Map([[wt.CIRCLE,"tb.rulenode.perimeter-circle"],[wt.POLYGON,"tb.rulenode.perimeter-polygon"]]);var Ht;!function(t){t.MILLISECONDS="MILLISECONDS",t.SECONDS="SECONDS",t.MINUTES="MINUTES",t.HOURS="HOURS",t.DAYS="DAYS"}(Ht||(Ht={}));const Ut=new Map([[Ht.MILLISECONDS,"tb.rulenode.time-unit-milliseconds"],[Ht.SECONDS,"tb.rulenode.time-unit-seconds"],[Ht.MINUTES,"tb.rulenode.time-unit-minutes"],[Ht.HOURS,"tb.rulenode.time-unit-hours"],[Ht.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:E.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:U.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:E.MatSuffix,selector:"[matSuffix]"}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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.tbelEnabled=Q(this.store).tbelEnabled,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,[]],alarmDetailsBuildTbel:[t?t.alarmDetailsBuildTbel:null,[]],alarmType:[t?t.alarmType:null,[q.required]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.clearAlarmConfigForm.get("scriptLang").value;e!==d.TBEL||this.tbelEnabled||(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("alarmDetailsBuildTbel").setValidators(e===d.TBEL?[q.required]:[]),this.clearAlarmConfigForm.get("alarmDetailsBuildTbel").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":"alarmDetailsBuildTbel",o=t===d.JS?"rulenode/clear_alarm_node_script_fn":"rulenode/tbel/clear_alarm_node_script_fn",r=this.clearAlarmConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(r,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,o,t).subscribe((t=>{t&&this.clearAlarmConfigForm.get(e).setValue(t)}))}onValidate(){this.clearAlarmConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.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:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{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.tbelEnabled=Q(this.store).tbelEnabled,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,[]],alarmDetailsBuildTbel:[t?t.alarmDetailsBuildTbel: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.TBEL||this.tbelEnabled||(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("alarmDetailsBuildTbel").setValidators(a&&r===d.TBEL?[q.required]:[]),this.createAlarmConfigForm.get("alarmDetailsBuildJs").updateValueAndValidity({emitEvent:t}),this.createAlarmConfigForm.get("alarmDetailsBuildTbel").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":"alarmDetailsBuildTbel",o=t===d.JS?"rulenode/create_alarm_node_script_fn":"rulenode/tbel/create_alarm_node_script_fn",r=this.createAlarmConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(r,"json",this.translate.instant("tb.rulenode.details"),"Details",["msg","metadata","msgType"],this.ruleNodeId,o,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.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:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],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:V.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:E.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: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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),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}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{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:E.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:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:lt.EntityTypeSelectComponent,selector:"tb-entity-type-select",inputs:["allowedEntityTypes","useAliasEntityTypes","filterAllowedEntityTypes","showLabel","required","disabled"]},{type:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),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:V.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:E.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: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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),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:V.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.tbelEnabled=Q(this.store).tbelEnabled,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,[]],tbelScript:[t?t.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.generatorConfigForm.get("scriptLang").value;e!==d.TBEL||this.tbelEnabled||(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("tbelScript").setValidators(e===d.TBEL?[q.required]:[]),this.generatorConfigForm.get("tbelScript").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":"tbelScript",o=t===d.JS?"rulenode/generator_node_script_fn":"rulenode/tbel/generator_node_script_fn",r=this.generatorConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(r,"generate",this.translate.instant("tb.rulenode.generator"),"Generate",["prevMsg","prevMetadata","prevMsgType"],this.ruleNodeId,o,t).subscribe((t=>{t&&this.generatorConfigForm.get(e).setValue(t)}))}onValidate(){this.generatorConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.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:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],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:E.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:E.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:E.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}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{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=Bt,this.rangeUnits=Object.keys(Kt),this.rangeUnitTranslationMap=jt,this.timeUnits=Object.keys(Ht),this.timeUnitsTranslationMap=Ut}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:E.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:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.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:E.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:E.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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:pt.MatTooltip,selector:"[matTooltip]",exportAs:["matTooltip"]}],pipes:{translate:R.TranslatePipe,async:w.AsyncPipe,safeHtml:Et}}),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:E.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: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:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),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.tbelEnabled=Q(this.store).tbelEnabled,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,[]],tbelScript:[t?t.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.logConfigForm.get("scriptLang").value;e!==d.TBEL||this.tbelEnabled||(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("tbelScript").setValidators(e===d.TBEL?[q.required]:[]),this.logConfigForm.get("tbelScript").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":"tbelScript",o=t===d.JS?"rulenode/log_node_script_fn":"rulenode/tbel/log_node_script_fn",r=this.logConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(r,"string",this.translate.instant("tb.rulenode.to-string"),"ToString",["msg","metadata","msgType"],this.ruleNodeId,o,t).subscribe((t=>{t&&this.logConfigForm.get(e).setValue(t)}))}onValidate(){this.logConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.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:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],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}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{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:E.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: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:E.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:E.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:E.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:E.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),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:E.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:E.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:E.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:V.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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:E.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: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:E.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:E.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: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:E.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:E.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:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:_.TogglePasswordComponent,selector:"tb-toggle-password"},{type:V.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:E.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:E.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:E.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:E.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:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),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 Ee 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",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-reply-config",usesInheritance:!0,ngImport:e,template:'
\n \n tb.rulenode.request-id-metadata-attribute\n \n \n
\n',components:[{type:E.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:E.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:Ee,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 Ve 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",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-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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ve,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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:V.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:E.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: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:E.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:E.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:E.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:E.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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 Be 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",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-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:E.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: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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Be,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 He 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",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-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:E.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:He,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 Ue 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",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-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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.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:Et}}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ue,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.attributeScopeMap=m,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]],sendAttributesDeletedNotification:[!!t&&t.sendAttributesDeletedNotification,[]],notifyDevice:[!!t&&t.notifyDevice,[]]}),this.deleteAttributesConfigForm.get("scope").valueChanges.subscribe((t=>{t!==m.SHARED_SCOPE&&this.deleteAttributesConfigForm.get("notifyDevice").patchValue(!1,{emitEvent:!1})}))}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 {{ \'tb.rulenode.send-attributes-deleted-notification\' | translate }}\n \n
tb.rulenode.send-attributes-deleted-notification-hint
\n
\n \n {{ \'tb.rulenode.notify-device\' | translate }}\n \n
tb.rulenode.notify-device-delete-hint
\n
\n
\n',components:[{type:E.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: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"]},{type:V.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:R.TranslateDirective,selector:"[translate],[ngx-translate]",inputs:["translate","translateParams"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:E.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:E.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:E.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:E.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: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:E.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:E.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:E.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:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:M.FormGroupName,selector:"[formGroupName]",inputs:["formGroupName"]},{type:E.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:Et}}),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:V.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:E.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: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:E.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:V.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:E.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: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:E.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:E.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:E.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:E.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,Et,_e,je],imports:[B,L,It],exports:[Le,Je,Qe,$e,Ne,Et,_e,je]}),We.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:We,imports:[[B,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,Et,_e,je],imports:[B,L,It],exports:[Le,Je,Qe,$e,Ne,Et,_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,He,Ve,Ie,Vt,ge,xe,ye,Me,be,Ce,Fe,ke,Ee,Pe,Ue,we,Be,qe,ve,Te,Ge,De,Re,ce,fe,he,Oe,Ae,Se,ze],imports:[B,L,It,We],exports:[Ke,Pt,He,Ve,Ie,Vt,ge,xe,ye,Me,be,Ce,Fe,ke,Ee,Pe,Ue,we,Be,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:[[B,L,It,We]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:Ye,decorators:[{type:i,args:[{declarations:[Ke,Pt,He,Ve,Ie,Vt,ge,xe,ye,Me,be,Ce,Fe,ke,Ee,Pe,Ue,we,Be,qe,ve,Te,Ge,De,Re,ce,fe,he,Oe,Ae,Se,ze],imports:[B,L,It,We],exports:[Ke,Pt,He,Ve,Ie,Vt,ge,xe,ye,Me,be,Ce,Fe,ke,Ee,Pe,Ue,we,Be,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:E.MatFormField,selector:"mat-form-field",inputs:["color","floatLabel","appearance","hideRequiredMarker","hintLabel"],exportAs:["matFormField"]},{type:V.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:E.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:E.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:V.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:V.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:E.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:E.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:Et}}),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:E.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:V.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:E.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(Ht),this.timeUnitsTranslationMap=Ut}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:E.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:U.MatSelect,selector:"mat-select",inputs:["disabled","disableRipple","tabIndex"],exportAs:["matSelect"]},{type:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:V.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:E.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:E.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:E.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:V.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:V.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:V.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:[B,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:[[B,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:[B,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:E.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:E.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:E.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:V.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:E.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:V.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:E.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: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","appearance","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:E.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=Bt,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:E.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:K.MatOption,selector:"mat-option",exportAs:["matOption"]},{type:V.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:E.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:E.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.tbelEnabled=Q(this.store).tbelEnabled,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,[]],tbelScript:[t?t.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.scriptConfigForm.get("scriptLang").value;e!==d.TBEL||this.tbelEnabled||(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("tbelScript").setValidators(e===d.TBEL?[q.required]:[]),this.scriptConfigForm.get("tbelScript").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":"tbelScript",o=t===d.JS?"rulenode/filter_node_script_fn":"rulenode/tbel/filter_node_script_fn",r=this.scriptConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(r,"filter",this.translate.instant("tb.rulenode.filter"),"Filter",["msg","metadata","msgType"],this.ruleNodeId,o,t).subscribe((t=>{t&&this.scriptConfigForm.get(e).setValue(t)}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.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:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],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}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{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.tbelEnabled=Q(this.store).tbelEnabled,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,[]],tbelScript:[t?t.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.switchConfigForm.get("scriptLang").value;e!==d.TBEL||this.tbelEnabled||(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("tbelScript").setValidators(e===d.TBEL?[q.required]:[]),this.switchConfigForm.get("tbelScript").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":"tbelScript",o=t===d.JS?"rulenode/switch_node_script_fn":"rulenode/tbel/switch_node_script_fn",r=this.switchConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(r,"switch",this.translate.instant("tb.rulenode.switch"),"Switch",["msg","metadata","msgType"],this.ruleNodeId,o,t).subscribe((t=>{t&&this.switchConfigForm.get(e).setValue(t)}))}onValidate(){this.switchConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.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:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],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}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{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:[B,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:[[B,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:[B,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,this.allowedEntityTypes=[y.DEVICE,y.ASSET,y.ENTITY_VIEW,y.USER,y.EDGE]}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:E.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: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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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.tbelEnabled=Q(this.store).tbelEnabled,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]],tbelScript:[t?t.tbelScript:null,[]]})}validatorTriggers(){return["scriptLang"]}updateValidators(t){let e=this.scriptConfigForm.get("scriptLang").value;e!==d.TBEL||this.tbelEnabled||(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("tbelScript").setValidators(e===d.TBEL?[q.required]:[]),this.scriptConfigForm.get("tbelScript").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":"tbelScript",o=t===d.JS?"rulenode/transformation_node_script_fn":"rulenode/tbel/transformation_node_script_fn",r=this.scriptConfigForm.get(e).value;this.nodeScriptTestService.testNodeScript(r,"update",this.translate.instant("tb.rulenode.transformer"),"Transform",["msg","metadata","msgType"],this.ruleNodeId,o,t).subscribe((t=>{t&&this.scriptConfigForm.get(e).setValue(t)}))}onValidate(){this.scriptConfigForm.get("scriptLang").value===d.JS&&this.jsFuncComponent.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:"tbelFuncComponent",first:!0,predicate:["tbelFuncComponent"],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}]}],tbelFuncComponent:[{type:a,args:["tbelFuncComponent",{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:E.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: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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgForOf,selector:"[ngFor][ngForOf]",inputs:["ngForOf","ngForTrackBy","ngForTemplate"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:E.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:E.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:E.MatHint,selector:"mat-hint",inputs:["align","id"]},{type:w.NgIf,selector:"[ngIf]",inputs:["ngIf","ngIfThen","ngIfElse"]},{type:E.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:E.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:E.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:E.MatError,selector:"mat-error",inputs:["id"]},{type:E.MatHint,selector:"mat-hint",inputs:["align","id"]}],pipes:{translate:R.TranslatePipe,safeHtml:Et}}),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:[B,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:[[B,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:[B,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","appearance","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:[B,L,We],exports:[ko,Mo]}),qo.ɵinj=e.ɵɵngDeclareInjector({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qo,imports:[[B,L,We]]}),e.ɵɵngDeclareClassMetadata({minVersion:"12.0.0",version:"12.2.16",ngImport:e,type:qo,decorators:[{type:i,args:[{declarations:[ko,Mo],imports:[B,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","send-attributes-updated-notification":"Send attributes updated notification","send-attributes-updated-notification-hint":"Send notification about updated attributes as a separate message to the rule engine queue.","send-attributes-deleted-notification":"Send attributes deleted notification","send-attributes-deleted-notification-hint":"Send notification about deleted attributes as a separate message to the rule engine queue.","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.","notify-device-delete-hint":"Send notification about deleted attributes to device.","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:[B,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:[[B,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:[B,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/rest/TbHttpClientTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java index f7a1d83ac9..28918538e2 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java @@ -18,6 +18,7 @@ package org.thingsboard.rule.engine.rest; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; +import org.assertj.core.api.Assertions; import org.awaitility.Awaitility; import org.junit.After; import org.junit.Assert; @@ -26,6 +27,7 @@ import org.junit.Test; import org.mockito.ArgumentCaptor; import org.mockito.Mockito; import org.mockserver.integration.ClientAndServer; +import org.springframework.util.LinkedMultiValueMap; import org.springframework.web.client.AsyncRestTemplate; import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.server.common.data.id.DeviceId; @@ -34,6 +36,8 @@ import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; import java.net.URI; +import java.util.List; +import java.util.Map; import java.util.concurrent.TimeUnit; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -200,5 +204,23 @@ public class TbHttpClientTest { ); } + @Test + public void testHeadersToMetaData() { + Map> headers = new LinkedMultiValueMap<>(); + headers.put("Content-Type", List.of("binary")); + headers.put("Set-Cookie", List.of("sap-context=sap-client=075; path=/", "sap-token=sap-client=075; path=/")); + + TbMsgMetaData metaData = new TbMsgMetaData(); + + willCallRealMethod().given(client).headersToMetaData(any(), any()); + + client.headersToMetaData(headers, metaData::putValue); + + Map data = metaData.getData(); + + Assertions.assertThat(data).hasSize(2); + Assertions.assertThat(data.get("Content-Type")).isEqualTo("binary"); + Assertions.assertThat(data.get("Set-Cookie")).isEqualTo("[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]"); + } } \ No newline at end of file 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/telemetry/TbMsgDeleteAttributesTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java similarity index 57% rename from rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesTest.java rename to rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java index 8972a3aa1f..e641e0ded4 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/telemetry/TbMsgDeleteAttributesNodeTest.java @@ -20,15 +20,11 @@ import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.junit.MockitoJUnitRunner; 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; -import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.msg.TbMsg; import org.thingsboard.server.common.msg.TbMsgMetaData; @@ -42,8 +38,10 @@ import java.util.function.Consumer; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyList; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.BDDMockito.willAnswer; import static org.mockito.BDDMockito.willReturn; import static org.mockito.Mockito.mock; @@ -51,14 +49,18 @@ import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; +import static org.thingsboard.server.common.data.DataConstants.NOTIFY_DEVICE_METADATA_KEY; +import static org.thingsboard.server.common.data.DataConstants.SCOPE; +import static org.thingsboard.server.common.data.DataConstants.SERVER_SCOPE; +import static org.thingsboard.server.common.data.DataConstants.SHARED_SCOPE; @Slf4j -public class TbMsgDeleteAttributesTest { +public class TbMsgDeleteAttributesNodeTest { final ObjectMapper mapper = new ObjectMapper(); DeviceId deviceId; - TbMsgDeleteAttributes node; - TbMsgDeleteAttributesConfiguration config; + TbMsgDeleteAttributesNode node; + TbMsgDeleteAttributesNodeConfiguration config; TbNodeConfiguration nodeConfiguration; TbContext ctx; TbMsgCallback callback; @@ -70,20 +72,20 @@ public class TbMsgDeleteAttributesTest { deviceId = new DeviceId(UUID.randomUUID()); callback = mock(TbMsgCallback.class); ctx = mock(TbContext.class); - config = new TbMsgDeleteAttributesConfiguration().defaultConfiguration(); + config = new TbMsgDeleteAttributesNodeConfiguration().defaultConfiguration(); config.setKeys(List.of("${TestAttribute_1}", "$[TestAttribute_2]", "$[TestAttribute_3]", "TestAttribute_4")); nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); - node = spy(new TbMsgDeleteAttributes()); + node = spy(new TbMsgDeleteAttributesNode()); node.init(ctx, nodeConfiguration); telemetryService = mock(RuleEngineTelemetryService.class); willReturn(telemetryService).given(ctx).getTelemetryService(); willAnswer(invocation -> { - TelemetryNodeCallback callBack = invocation.getArgument(4); + TelemetryNodeCallback callBack = invocation.getArgument(5); callBack.onSuccess(null); return null; }).given(telemetryService).deleteAndNotify( - any(), any(), anyString(), anyList(), any()); + any(), any(), anyString(), anyList(), anyBoolean(), any()); } @AfterEach @@ -93,18 +95,51 @@ public class TbMsgDeleteAttributesTest { @Test void givenDefaultConfig_whenVerify_thenOK() { - TbMsgDeleteAttributesConfiguration defaultConfig = new TbMsgDeleteAttributesConfiguration().defaultConfiguration(); - assertThat(defaultConfig.getScope()).isEqualTo(DataConstants.SERVER_SCOPE); + TbMsgDeleteAttributesNodeConfiguration defaultConfig = new TbMsgDeleteAttributesNodeConfiguration().defaultConfiguration(); + assertThat(defaultConfig.getScope()).isEqualTo(SERVER_SCOPE); assertThat(defaultConfig.getKeys()).isEqualTo(Collections.emptyList()); } @Test - void givenMsg_whenOnMsg_thenVerifyOutput() throws Exception { + void givenMsg_whenOnMsg_thenVerifyOutput_NoSendAttributesDeletedNotification_NoNotifyDevice() throws Exception { + onMsg_thenVerifyOutput(false, false, false); + } + + @Test + void givenMsg_whenOnMsg_thenVerifyOutput_SendAttributesDeletedNotification_NoNotifyDevice() throws Exception { + config.setSendAttributesDeletedNotification(true); + nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + node.init(ctx, nodeConfiguration); + onMsg_thenVerifyOutput(true, false, false); + } + + @Test + void givenMsg_whenOnMsg_thenVerifyOutput_SendAttributesDeletedNotification_NotifyDevice() throws Exception { + config.setSendAttributesDeletedNotification(true); + config.setNotifyDevice(true); + config.setScope(SHARED_SCOPE); + nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + node.init(ctx, nodeConfiguration); + onMsg_thenVerifyOutput(true, true, false); + } + + @Test + void givenMsg_whenOnMsg_thenVerifyOutput_NoSendAttributesDeletedNotification_NotifyDeviceMetadata() throws Exception { + nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + node.init(ctx, nodeConfiguration); + onMsg_thenVerifyOutput(false, false, true); + } + + void onMsg_thenVerifyOutput(boolean sendAttributesDeletedNotification, boolean notifyDevice, boolean notifyDeviceMetadata) throws Exception { final Map mdMap = Map.of( "TestAttribute_1", "temperature", "city", "NY" ); - final TbMsgMetaData metaData = new TbMsgMetaData(mdMap); + TbMsgMetaData metaData = new TbMsgMetaData(mdMap); + if (notifyDeviceMetadata) { + metaData.putValue(NOTIFY_DEVICE_METADATA_KEY, "true"); + metaData.putValue(SCOPE, SHARED_SCOPE); + } final String data = "{\"TestAttribute_2\": \"humidity\", \"TestAttribute_3\": \"voltage\"}"; TbMsg msg = TbMsg.newMsg("POST_ATTRIBUTES_REQUEST", deviceId, metaData, data, callback); @@ -114,12 +149,13 @@ public class TbMsgDeleteAttributesTest { ArgumentCaptor> failureCaptor = ArgumentCaptor.forClass(Consumer.class); ArgumentCaptor newMsgCaptor = ArgumentCaptor.forClass(TbMsg.class); - verify(ctx, times(1)).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); - successCaptor.getValue().run(); + if (sendAttributesDeletedNotification) { + verify(ctx, times(1)).enqueue(any(), successCaptor.capture(), failureCaptor.capture()); + successCaptor.getValue().run(); + verify(ctx, times(1)).attributesDeletedActionMsg(any(), any(), anyString(), anyList()); + } verify(ctx, times(1)).tellSuccess(newMsgCaptor.capture()); - - verify(ctx, times(1)).attributesDeletedActionMsg(any(), any(), anyString(), anyList()); verify(ctx, never()).tellFailure(any(), any()); - verify(telemetryService, times(1)).deleteAndNotify(any(), any(), anyString(), anyList(), any()); + verify(telemetryService, times(1)).deleteAndNotify(any(), any(), anyString(), anyList(), eq(notifyDevice || notifyDeviceMetadata), any()); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java index ae9a8f9fdb..82d65dd544 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/transform/TbJsonPathNodeTest.java @@ -85,7 +85,7 @@ public class TbJsonPathNodeTest { } @Test - void givenPrimitiveMsg_whenOnMsg_thenVerifyOutput() throws Exception { + void givenJsonMsg_whenOnMsg_thenVerifyOutputJsonPrimitiveNode() throws Exception { String data = "{\"Attribute_1\":22.5,\"Attribute_2\":100}"; VerifyOutputMsg(data, 1, 100); @@ -93,6 +93,17 @@ public class TbJsonPathNodeTest { VerifyOutputMsg(data, 2, "StringValue"); } + @Test + void givenJsonMsg_whenOnMsg_thenVerifyJavaPrimitiveOutput() throws Exception { + config.setJsonPath("$.attributes.length()"); + nodeConfiguration = new TbNodeConfiguration(mapper.valueToTree(config)); + node.init(ctx, nodeConfiguration); + + String data = "{\"attributes\":[{\"attribute_1\":10},{\"attribute_2\":20},{\"attribute_3\":30},{\"attribute_4\":40}]}"; + VerifyOutputMsg(data, 1, 4); + + } + @Test void givenJsonArray_whenOnMsg_thenVerifyOutput() throws Exception { String data = "{\"Attribute_1\":22.5,\"Attribute_2\":[{\"Attribute_3\":22.5,\"Attribute_4\":10.3}, {\"Attribute_5\":22.5,\"Attribute_6\":10.3}]}"; 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 91cf576ae1..d414a7a777 100644 --- a/tools/pom.xml +++ b/tools/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard tools 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/transport/coap/pom.xml b/transport/coap/pom.xml index dd151dc920..2640d1d80d 100644 --- a/transport/coap/pom.xml +++ b/transport/coap/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/http/pom.xml b/transport/http/pom.xml index 15a9305c76..7820c585ec 100644 --- a/transport/http/pom.xml +++ b/transport/http/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/lwm2m/pom.xml b/transport/lwm2m/pom.xml index b2d7809f4d..b36c8372ef 100644 --- a/transport/lwm2m/pom.xml +++ b/transport/lwm2m/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/mqtt/pom.xml b/transport/mqtt/pom.xml index 4115434335..91ce12908b 100644 --- a/transport/mqtt/pom.xml +++ b/transport/mqtt/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport org.thingsboard.transport diff --git a/transport/pom.xml b/transport/pom.xml index d46c850c41..26af07f6a2 100644 --- a/transport/pom.xml +++ b/transport/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard transport diff --git a/transport/snmp/pom.xml b/transport/snmp/pom.xml index 57cfd4d8f0..0701f7aa57 100644 --- a/transport/snmp/pom.xml +++ b/transport/snmp/pom.xml @@ -21,7 +21,7 @@ org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT transport diff --git a/ui-ngx/package.json b/ui-ngx/package.json index ea410747b2..6b8112adad 100644 --- a/ui-ngx/package.json +++ b/ui-ngx/package.json @@ -1,11 +1,11 @@ { "name": "thingsboard", - "version": "3.4.2", + "version": "3.4.3", "scripts": { "ng": "ng", "start": "node --max_old_space_size=8048 ./node_modules/@angular/cli/bin/ng serve --configuration development --host 0.0.0.0 --open", "build": "ng build", - "build:prod": "ng build --configuration production --vendor-chunk", + "build:prod": "node --max_old_space_size=3072 ./node_modules/@angular/cli/bin/ng build --configuration production --vendor-chunk", "build:types": "node generate-types.js", "test": "ng test", "lint": "ng lint", diff --git a/ui-ngx/pom.xml b/ui-ngx/pom.xml index dade6f8c28..f78d168144 100644 --- a/ui-ngx/pom.xml +++ b/ui-ngx/pom.xml @@ -20,7 +20,7 @@ 4.0.0 org.thingsboard - 3.4.2-SNAPSHOT + 3.4.3-SNAPSHOT thingsboard org.thingsboard diff --git a/ui-ngx/src/app/core/api/alarm-data-subscription.ts b/ui-ngx/src/app/core/api/alarm-data-subscription.ts index 42f915a12a..be134f81ab 100644 --- a/ui-ngx/src/app/core/api/alarm-data-subscription.ts +++ b/ui-ngx/src/app/core/api/alarm-data-subscription.ts @@ -62,6 +62,7 @@ export class AlarmDataSubscription { private alarmDataCommand: AlarmDataCmd; private pageData: PageData; + private prematureUpdates: Array>; private alarmIdToDataIndex: {[id: string]: number}; private subsTw: SubscriptionTimewindow; @@ -136,8 +137,21 @@ export class AlarmDataSubscription { this.subscriber.alarmData$.subscribe((alarmDataUpdate) => { if (alarmDataUpdate.data) { this.onPageData(alarmDataUpdate.data, alarmDataUpdate.allowedEntities, alarmDataUpdate.totalEntities); + if (this.prematureUpdates) { + for (const update of this.prematureUpdates) { + this.onDataUpdate(update); + } + this.prematureUpdates = null; + } } else if (alarmDataUpdate.update) { - this.onDataUpdate(alarmDataUpdate.update); + if (!this.pageData) { + if (!this.prematureUpdates) { + this.prematureUpdates = []; + } + this.prematureUpdates.push(alarmDataUpdate.update); + } else { + this.onDataUpdate(alarmDataUpdate.update); + } } }); diff --git a/ui-ngx/src/app/core/api/entity-data-subscription.ts b/ui-ngx/src/app/core/api/entity-data-subscription.ts index 3d176fc841..dfba1abaf2 100644 --- a/ui-ngx/src/app/core/api/entity-data-subscription.ts +++ b/ui-ngx/src/app/core/api/entity-data-subscription.ts @@ -120,6 +120,7 @@ export class EntityDataSubscription { private entityDataResolveSubject: Subject; private pageData: PageData; + private prematureUpdates: Array>; private data: Array>; private subsTw: SubscriptionTimewindow; private latestTsOffset: number; @@ -348,8 +349,21 @@ export class EntityDataSubscription { (entityDataUpdate) => { if (entityDataUpdate.data) { this.onPageData(entityDataUpdate.data); + if (this.prematureUpdates) { + for (const update of this.prematureUpdates) { + this.onDataUpdate(update); + } + this.prematureUpdates = null; + } } else if (entityDataUpdate.update) { - this.onDataUpdate(entityDataUpdate.update); + if (!this.pageData) { + if (!this.prematureUpdates) { + this.prematureUpdates = []; + } + this.prematureUpdates.push(entityDataUpdate.update); + } else { + this.onDataUpdate(entityDataUpdate.update); + } } } ); diff --git a/ui-ngx/src/app/core/auth/auth.models.ts b/ui-ngx/src/app/core/auth/auth.models.ts index 7e97cb7a13..96d2d834df 100644 --- a/ui-ngx/src/app/core/auth/auth.models.ts +++ b/ui-ngx/src/app/core/auth/auth.models.ts @@ -21,7 +21,7 @@ export interface SysParamsState { allowedDashboardIds: string[]; edgesSupportEnabled: boolean; hasRepository: boolean; - mvelEnabled: boolean; + tbelEnabled: 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 7408f29145..4f93896a4c 100644 --- a/ui-ngx/src/app/core/auth/auth.reducer.ts +++ b/ui-ngx/src/app/core/auth/auth.reducer.ts @@ -25,7 +25,7 @@ const emptyUserAuthState: AuthPayload = { allowedDashboardIds: [], edgesSupportEnabled: false, hasRepository: false, - mvelEnabled: false + tbelEnabled: 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 f703d72f57..d736c3447f 100644 --- a/ui-ngx/src/app/core/auth/auth.selectors.ts +++ b/ui-ngx/src/app/core/auth/auth.selectors.ts @@ -60,9 +60,9 @@ export const selectHasRepository = createSelector( (state: AuthState) => state.hasRepository ); -export const selectMvelEnabled = createSelector( +export const selectTbelEnabled = createSelector( selectAuthState, - (state: AuthState) => state.mvelEnabled + (state: AuthState) => state.tbelEnabled ); export function getCurrentAuthState(store: Store): AuthState { diff --git a/ui-ngx/src/app/core/auth/auth.service.ts b/ui-ngx/src/app/core/auth/auth.service.ts index 2b54306e2c..a62623260f 100644 --- a/ui-ngx/src/app/core/auth/auth.service.ts +++ b/ui-ngx/src/app/core/auth/auth.service.ts @@ -195,18 +195,22 @@ export class AuthService { )); } - public logout(captureLastUrl: boolean = false) { + public logout(captureLastUrl: boolean = false, ignoreRequest = false) { if (captureLastUrl) { this.redirectUrl = this.router.url; } - this.http.post('/api/auth/logout', null, defaultHttpOptions(true, true)) - .subscribe(() => { - this.clearJwtToken(); - }, - () => { - this.clearJwtToken(); - } - ); + if (!ignoreRequest) { + this.http.post('/api/auth/logout', null, defaultHttpOptions(true, true)) + .subscribe(() => { + this.clearJwtToken(); + }, + () => { + this.clearJwtToken(); + } + ); + } else { + this.clearJwtToken(); + } } private notifyUserLoaded(isUserLoaded: boolean) { @@ -478,9 +482,9 @@ export class AuthService { } } - private loadMvelEnabled(authUser: AuthUser): Observable { + private loadTbelEnabled(authUser: AuthUser): Observable { if (authUser.authority === Authority.TENANT_ADMIN) { - return this.http.get('/api/ruleChain/mvelEnabled', defaultHttpOptions()); + return this.http.get('/api/ruleChain/tbelEnabled', defaultHttpOptions()); } else { return of(false); } @@ -491,7 +495,7 @@ export class AuthService { this.fetchAllowedDashboardIds(authPayload), this.loadIsEdgesSupportEnabled(), this.loadHasRepository(authPayload.authUser), - this.loadMvelEnabled(authPayload.authUser), + this.loadTbelEnabled(authPayload.authUser), this.timeService.loadMaxDatapointsLimit()]; return forkJoin(sources) .pipe(map((data) => { @@ -499,8 +503,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; - const mvelEnabled: boolean = data[4] as boolean; - return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasRepository, mvelEnabled}; + const tbelEnabled: boolean = data[4] as boolean; + return {userTokenAccessEnabled, allowedDashboardIds, edgesSupportEnabled, hasRepository, tbelEnabled}; }, catchError((err) => { return of({}); }))); diff --git a/ui-ngx/src/app/core/http/admin.service.ts b/ui-ngx/src/app/core/http/admin.service.ts index 485f355b87..8933f7b99d 100644 --- a/ui-ngx/src/app/core/http/admin.service.ts +++ b/ui-ngx/src/app/core/http/admin.service.ts @@ -20,16 +20,18 @@ import { Observable } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { AdminSettings, - RepositorySettings, + AutoCommitSettings, + JwtSettings, MailServerSettings, + RepositorySettings, + RepositorySettingsInfo, SecuritySettings, TestSmsRequest, - UpdateMessage, - AutoCommitSettings, - RepositorySettingsInfo + UpdateMessage } from '@shared/models/settings.models'; import { EntitiesVersionControlService } from '@core/http/entities-version-control.service'; import { tap } from 'rxjs/operators'; +import { LoginResponse } from '@shared/models/login.models'; @Injectable({ providedIn: 'root' @@ -70,6 +72,14 @@ export class AdminService { defaultHttpOptionsFromConfig(config)); } + public getJwtSettings(config?: RequestConfig): Observable { + return this.http.get(`/api/admin/jwtSettings`, defaultHttpOptionsFromConfig(config)); + } + + public saveJwtSettings(jwtSettings: JwtSettings, config?: RequestConfig): Observable { + return this.http.post('/api/admin/jwtSettings', jwtSettings, defaultHttpOptionsFromConfig(config)); + } + public getRepositorySettings(config?: RequestConfig): Observable { return this.http.get(`/api/admin/repositorySettings`, defaultHttpOptionsFromConfig(config)); } diff --git a/ui-ngx/src/app/core/http/attribute.service.ts b/ui-ngx/src/app/core/http/attribute.service.ts index bad6547b5d..984b1e607b 100644 --- a/ui-ngx/src/app/core/http/attribute.service.ts +++ b/ui-ngx/src/app/core/http/attribute.service.ts @@ -43,7 +43,7 @@ export class AttributeService { public deleteEntityAttributes(entityId: EntityId, attributeScope: AttributeScope, attributes: Array, config?: RequestConfig): Observable { - const keys = attributes.map(attribute => encodeURI(attribute.key)).join(','); + const keys = attributes.map(attribute => encodeURIComponent(attribute.key)).join(','); return this.http.delete(`/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/${attributeScope}` + `?keys=${keys}`, defaultHttpOptionsFromConfig(config)); @@ -51,7 +51,7 @@ export class AttributeService { public deleteEntityTimeseries(entityId: EntityId, timeseries: Array, deleteAllDataForKeys = false, startTs?: number, endTs?: number, config?: RequestConfig): Observable { - const keys = timeseries.map(attribute => encodeURI(attribute.key)).join(','); + const keys = timeseries.map(attribute => encodeURIComponent(attribute.key)).join(','); let url = `/api/plugins/telemetry/${entityId.entityType}/${entityId.id}/timeseries/delete` + `?keys=${keys}&deleteAllDataForKeys=${deleteAllDataForKeys}`; if (isDefinedAndNotNull(startTs)) { diff --git a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts index 15c4d3ede3..bb9a7cef1d 100644 --- a/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts +++ b/ui-ngx/src/app/core/interceptors/global-http-interceptor.ts @@ -103,7 +103,8 @@ export class GlobalHttpInterceptor implements HttpInterceptor { const ignoreErrors = config.ignoreErrors; const resendRequest = config.resendRequest; const errorCode = errorResponse.error ? errorResponse.error.errorCode : null; - if (errorResponse.error && errorResponse.error.refreshTokenPending || errorResponse.status === 401) { + if (errorResponse.error && errorResponse.error.refreshTokenPending || + errorResponse.status === 401 && req.url !== Constants.entryPoints.tokenRefresh) { if (errorResponse.error && errorResponse.error.refreshTokenPending || errorCode && errorCode === Constants.serverErrorCode.jwtTokenExpired) { return this.refreshTokenAndRetry(req, next); @@ -153,7 +154,7 @@ export class GlobalHttpInterceptor implements HttpInterceptor { return this.jwtIntercept(req, next); }), catchError((err: Error) => { - this.authService.logout(true); + this.authService.logout(true, true); const message = err ? err.message : 'Unauthorized!'; return this.handleResponseError(req, next, new HttpErrorResponse({error: {message, timeout: 200}, status: 401})); })); diff --git a/ui-ngx/src/app/core/services/dashboard-utils.service.ts b/ui-ngx/src/app/core/services/dashboard-utils.service.ts index 90238ebaba..ccb7de73b1 100644 --- a/ui-ngx/src/app/core/services/dashboard-utils.service.ts +++ b/ui-ngx/src/app/core/services/dashboard-utils.service.ts @@ -395,7 +395,8 @@ export class DashboardUtilsService { sizeY: originalSize ? originalSize.sizeY : widget.sizeY, mobileOrder: widget.config.mobileOrder, mobileHeight: widget.config.mobileHeight, - mobileHide: widget.config.mobileHide + mobileHide: widget.config.mobileHide, + desktopHide: widget.config.desktopHide }; if (isUndefined(originalColumns)) { originalColumns = 24; diff --git a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts index 783b56f844..92dccc0d88 100644 --- a/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts +++ b/ui-ngx/src/app/core/ws/telemetry-websocket.service.ts @@ -233,7 +233,7 @@ export class TelemetryWebsocketService implements TelemetryService { }, () => { this.isOpening = false; - this.authService.logout(true); + this.authService.logout(true, true); } ); } diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts index e8fdde15c2..e147866df9 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/add-widget-dialog.component.ts @@ -140,6 +140,7 @@ export class AddWidgetDialogComponent extends DialogComponent
+ fxFlex tb-fullscreen [fullscreenElement]="elRef.nativeElement" [fullscreen]="widgetEditMode || iframeMode || forceFullscreen || isFullscreen">
- {{ (layoutsFormGroup.value.right ? 'layout.left' : 'layout.main') | translate }} + + {{ (layoutsFormGroup.value.right ? 'layout.left' : 'layout.main') | translate }} +
- {{ 'layout.right' | translate }} + + {{ 'layout.right' | translate }} +
{ - elementToDisable.disabled = false; - }, 250); - } - - if (this.layoutsFormGroup.get('type').value === LayoutWidthType.FIXED) { + if (this.layoutsFormGroup.get('type').value === LayoutWidthType.FIXED && this.layoutsFormGroup.get('right').value) { this.layoutsFormGroup.get('fixedLayout').setValue(layout); } } diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html index 40d301dd13..73904c54ed 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.html @@ -44,6 +44,11 @@ {{ 'admin.repository-read-only' | translate }} +
+ + {{ 'admin.show-merge-commits' | translate }} + +
admin.authentication-settings diff --git a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts index 1bb1487f0a..1270acf150 100644 --- a/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts +++ b/ui-ngx/src/app/modules/home/components/vc/repository-settings.component.ts @@ -75,6 +75,7 @@ export class RepositorySettingsComponent extends PageComponent implements OnInit repositoryUri: [null, [Validators.required]], defaultBranch: ['main', []], readOnly: [false, []], + showMergeCommits: [false, []], authMethod: [RepositoryAuthMethod.USERNAME_PASSWORD, [Validators.required]], username: [null, []], password: [null, []], 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 70c8586ccd..8ba8cda461 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 @@ -492,6 +492,9 @@ {{ 'widget-config.mobile-hide' | translate }} + + {{ 'widget-config.desktop-hide' | translate }} + widget-config.advanced-settings diff --git a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts index 4661f523c8..b8683741e3 100644 --- a/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts @@ -255,7 +255,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont this.layoutSettings = this.fb.group({ mobileOrder: [null, [Validators.pattern(/^-?[0-9]+$/)]], mobileHeight: [null, [Validators.min(1), Validators.max(10), Validators.pattern(/^\d*$/)]], - mobileHide: [false] + mobileHide: [false], + desktopHide: [false] }); this.actionsSettings = this.fb.group({ actionsData: [null, []] @@ -554,7 +555,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont { mobileOrder: layout.mobileOrder, mobileHeight: layout.mobileHeight, - mobileHide: layout.mobileHide + mobileHide: layout.mobileHide, + desktopHide: layout.desktopHide }, {emitEvent: false} ); @@ -563,7 +565,8 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont { mobileOrder: null, mobileHeight: null, - mobileHide: false + mobileHide: false, + desktopHide: false }, {emitEvent: false} ); diff --git a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts index 33fa5111c5..934fca9278 100644 --- a/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/dashboard-component.models.ts @@ -116,8 +116,12 @@ export class DashboardWidgets implements Iterable { } get activeDashboardWidgets(): Array { - if (this.dashboard.isMobileSize && !this.dashboard.isEdit) { - return this.dashboardWidgets.filter(w => !w.mobileHide); + if (!this.dashboard.isEdit) { + if (this.dashboard.isMobileSize) { + return this.dashboardWidgets.filter(w => !w.mobileHide); + } else { + return this.dashboardWidgets.filter(w => !w.desktopHide); + } } return this.dashboardWidgets; } @@ -358,6 +362,10 @@ export class DashboardWidget implements GridsterItem, IDashboardWidget { return this.widgetLayout ? this.widgetLayout.mobileHide === true : false; } + get desktopHide(): boolean { + return this.widgetLayout ? this.widgetLayout.desktopHide === true : false; + } + set gridsterItemComponent(item: GridsterItemComponentInterface) { this.gridsterItemComponentValue = item; this.gridsterItemComponentSubject.next(this.gridsterItemComponentValue); diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html index 80c9f384fd..39db84d8c6 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.html @@ -15,144 +15,239 @@ limitations under the License. --> -
- - -
- admin.security-settings - -
-
-
- - -
- -
-
-
- - - - -
admin.general-policy
-
-
- - admin.max-failed-login-attempts - - - {{ 'admin.minimum-max-failed-login-attempts-range' | translate }} - - - - admin.user-lockout-notification-email - - -
- - - -
admin.password-policy
-
-
-
- - admin.minimum-password-length - - - {{ 'admin.minimum-password-length-required' | translate }} - - - {{ 'admin.minimum-password-length-range' | translate }} - - - {{ 'admin.minimum-password-length-range' | translate }} - - - - admin.minimum-uppercase-letters - - - {{ 'admin.minimum-uppercase-letters-range' | translate }} - - - - admin.minimum-lowercase-letters - - - {{ 'admin.minimum-lowercase-letters-range' | translate }} - - - - admin.minimum-digits - - - {{ 'admin.minimum-digits-range' | translate }} - - - - admin.minimum-special-characters - - - {{ 'admin.minimum-special-characters-range' | translate }} - - - - admin.password-expiration-period-days - - - {{ 'admin.password-expiration-period-days-range' | translate }} - - - - admin.password-reuse-frequency-days - - - {{ 'admin.password-reuse-frequency-days-range' | translate }} - - - - admin.allow-whitespace - -
-
-
-
-
- -
+ + +
+ admin.security-settings + +
+
+
+ + +
+ + +
+
+ admin.general-policy + + admin.max-failed-login-attempts + + + {{ 'admin.minimum-max-failed-login-attempts-range' | translate }} + + + + admin.user-lockout-notification-email + + +
+ +
+ admin.password-policy +
+ + admin.minimum-password-length + + + {{ 'admin.minimum-password-length-required' | translate }} + + + {{ 'admin.minimum-password-length-range' | translate }} + + + {{ 'admin.minimum-password-length-range' | translate }} + + +
+ + admin.minimum-uppercase-letters + + + {{ 'admin.minimum-uppercase-letters-range' | translate }} + + + + admin.minimum-lowercase-letters + + + {{ 'admin.minimum-lowercase-letters-range' | translate }} + + +
+
+ + admin.minimum-digits + + + {{ 'admin.minimum-digits-range' | translate }} + + + + admin.minimum-special-characters + + + {{ 'admin.minimum-special-characters-range' | translate }} + + +
+
+ + admin.password-expiration-period-days + + + {{ 'admin.password-expiration-period-days-range' | translate }} + + + + admin.password-reuse-frequency-days + + + {{ 'admin.password-reuse-frequency-days-range' | translate }} + + +
+ + admin.allow-whitespace + +
- - - -
+
+ + +
+
+ + + + + +
+ admin.jwt.security-settings +
+
+ +
+
+
+ + admin.jwt.issuer-name + + + {{ 'admin.jwt.issuer-name-required' | translate }} + + + + admin.jwt.signings-key + + + admin.jwt.signings-key-hint + + {{ 'admin.jwt.signings-key-required' | translate }} + + + {{ 'admin.jwt.signings-key-base64' | translate }} + + + {{ 'admin.jwt.signings-key-min-length' | translate }} + + +
+
+ + admin.jwt.expiration-time + + + {{ 'admin.jwt.expiration-time-required' | translate }} + + + {{ 'admin.jwt.expiration-time-pattern' | translate }} + + + {{ 'admin.jwt.expiration-time-min' | translate }} + + + + admin.jwt.refresh-expiration-time + + + {{ 'admin.jwt.refresh-expiration-time-required' | translate }} + + + {{ 'admin.jwt.refresh-expiration-time-pattern' | translate }} + + + {{ 'admin.jwt.refresh-expiration-time-min' | translate }} + + + {{ 'admin.jwt.refresh-expiration-time-less-token' | translate }} + + +
+
+ + +
+
+
+
+
diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss index 32e514e010..5a6d5eed04 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.scss @@ -14,7 +14,26 @@ * limitations under the License. */ :host { - .mat-accordion-container { - margin-bottom: 16px; + .mat-headline { + margin-bottom: 8px; + } + + .mat-card-title { + margin: 0; + } + + .mat-card-content { + padding: 0 !important; + } + + .fields-group { + padding: 8px 16px 0; + margin: 10px 0; + border: 1px groove rgba(0, 0, 0, .25); + border-radius: 4px; + + legend { + color: rgba(0, 0, 0, .7); + } } } diff --git a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts index 6dc070b278..628d23e78a 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts +++ b/ui-ngx/src/app/modules/home/pages/admin/security-settings.component.ts @@ -14,40 +14,50 @@ /// limitations under the License. /// -import { Component, OnInit } from '@angular/core'; +import { Component } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; import { PageComponent } from '@shared/components/page.component'; import { Router } from '@angular/router'; -import { FormBuilder, FormGroup, Validators } from '@angular/forms'; -import { SecuritySettings } from '@shared/models/settings.models'; +import { FormBuilder, FormControl, FormGroup, Validators } from '@angular/forms'; +import { JwtSettings, SecuritySettings } from '@shared/models/settings.models'; import { AdminService } from '@core/http/admin.service'; import { HasConfirmForm } from '@core/guards/confirm-on-exit.guard'; +import { mergeMap, tap } from 'rxjs/operators'; +import { randomAlphanumeric } from '@core/utils'; +import { AuthService } from '@core/auth/auth.service'; +import { DialogService } from '@core/services/dialog.service'; +import { TranslateService } from '@ngx-translate/core'; +import { Observable, of } from 'rxjs'; @Component({ selector: 'tb-security-settings', templateUrl: './security-settings.component.html', styleUrls: ['./security-settings.component.scss', './settings-card.scss'] }) -export class SecuritySettingsComponent extends PageComponent implements OnInit, HasConfirmForm { +export class SecuritySettingsComponent extends PageComponent implements HasConfirmForm { securitySettingsFormGroup: FormGroup; - securitySettings: SecuritySettings; + jwtSecuritySettingsFormGroup: FormGroup; + + private securitySettings: SecuritySettings; + private jwtSettings: JwtSettings; constructor(protected store: Store, private router: Router, private adminService: AdminService, - public fb: FormBuilder) { + private authService: AuthService, + private dialogService: DialogService, + private translate: TranslateService, + private fb: FormBuilder) { super(store); - } - - ngOnInit() { this.buildSecuritySettingsForm(); + this.buildJwtSecuritySettingsForm(); this.adminService.getSecuritySettings().subscribe( - (securitySettings) => { - this.securitySettings = securitySettings; - this.securitySettingsFormGroup.reset(this.securitySettings); - } + securitySettings => this.processSecuritySettings(securitySettings) + ); + this.adminService.getJwtSettings().subscribe( + jwtSettings => this.processJwtSettings(jwtSettings) ); } @@ -70,18 +80,114 @@ export class SecuritySettingsComponent extends PageComponent implements OnInit, }); } + buildJwtSecuritySettingsForm() { + this.jwtSecuritySettingsFormGroup = this.fb.group({ + tokenIssuer: ['', Validators.required], + tokenSigningKey: ['', [Validators.required, this.base64Format]], + tokenExpirationTime: [0, [Validators.required, Validators.pattern('[0-9]*'), Validators.min(60)]], + refreshTokenExpTime: [0, [Validators.required, Validators.pattern('[0-9]*'), Validators.min(900)]] + }, {validators: this.refreshTokenTimeGreatTokenTime.bind(this)}); + this.jwtSecuritySettingsFormGroup.get('tokenExpirationTime').valueChanges.subscribe( + () => this.jwtSecuritySettingsFormGroup.get('refreshTokenExpTime').updateValueAndValidity({onlySelf: true}) + ); + } + save(): void { this.securitySettings = {...this.securitySettings, ...this.securitySettingsFormGroup.value}; this.adminService.saveSecuritySettings(this.securitySettings).subscribe( - (securitySettings) => { - this.securitySettings = securitySettings; - this.securitySettingsFormGroup.reset(this.securitySettings); - } + securitySettings => this.processSecuritySettings(securitySettings) ); } + saveJwtSettings() { + const jwtFormSettings = this.jwtSecuritySettingsFormGroup.value; + this.confirmChangeJWTSettings().pipe(mergeMap(value => { + if (value) { + return this.adminService.saveJwtSettings(jwtFormSettings).pipe( + tap((data) => this.authService.setUserFromJwtToken(data.token, data.refreshToken, false)), + mergeMap(() => this.adminService.getJwtSettings()), + tap(jwtSettings => this.processJwtSettings(jwtSettings)) + ); + } + return of(null); + })).subscribe(() => {}); + } + + discardSetting() { + this.securitySettingsFormGroup.reset(this.securitySettings); + } + + discardJwtSetting() { + this.jwtSecuritySettingsFormGroup.reset(this.jwtSettings); + } + + markAsTouched() { + this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').markAsTouched(); + } + + private confirmChangeJWTSettings(): Observable { + if (this.jwtSecuritySettingsFormGroup.get('tokenIssuer').value !== (this.jwtSettings?.tokenIssuer || '') || + this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').value !== (this.jwtSettings?.tokenSigningKey || '')) { + return this.dialogService.confirm( + this.translate.instant('admin.jwt.info-header'), + `
${this.translate.instant('admin.jwt.info-message')}
`, + this.translate.instant('action.discard-changes'), + this.translate.instant('action.confirm') + ); + } + return of(true); + } + + generateSigningKey() { + this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').setValue(btoa(randomAlphanumeric(64))); + if (this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').pristine) { + this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').markAsDirty(); + this.jwtSecuritySettingsFormGroup.get('tokenSigningKey').markAsTouched(); + } + } + + private processSecuritySettings(securitySettings: SecuritySettings) { + this.securitySettings = securitySettings; + this.securitySettingsFormGroup.reset(this.securitySettings); + } + + private processJwtSettings(jwtSettings: JwtSettings) { + this.jwtSettings = jwtSettings; + this.jwtSecuritySettingsFormGroup.reset(jwtSettings); + } + + private refreshTokenTimeGreatTokenTime(formGroup: FormGroup): { [key: string]: boolean } | null { + if (formGroup) { + const tokenTime = formGroup.value.tokenExpirationTime; + const refreshTokenTime = formGroup.value.refreshTokenExpTime; + if (tokenTime >= refreshTokenTime ) { + if (formGroup.get('refreshTokenExpTime').untouched) { + formGroup.get('refreshTokenExpTime').markAsTouched(); + } + formGroup.get('refreshTokenExpTime').setErrors({lessToken: true}); + return {lessToken: true}; + } + } + return null; + } + + private base64Format(control: FormControl): { [key: string]: boolean } | null { + if (control.value === '' || control.value === 'thingsboardDefaultSigningKey') { + return null; + } + try { + const value = atob(control.value); + if (value.length < 32) { + return {minLength: true}; + } + return null; + } catch (e) { + return {base64: true}; + } + } + confirmForm(): FormGroup { - return this.securitySettingsFormGroup; + return this.securitySettingsFormGroup.dirty ? this.securitySettingsFormGroup : this.jwtSecuritySettingsFormGroup; } } diff --git a/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.html b/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.html index 710339908b..f7586d5656 100644 --- a/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.html +++ b/ui-ngx/src/app/shared/components/dialog/confirm-dialog.component.html @@ -16,7 +16,7 @@ -->

{{data.title}}

-
+
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 c0f0ff4b42..b5e3c20163 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 }} ({{ (scriptLang === scriptLanguage.JS ? 'rulenode.script-lang-java-script' : 'rulenode.script-lang-mvel') | translate }})

+

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

diff --git a/ui-ngx/src/app/shared/models/dashboard.models.ts b/ui-ngx/src/app/shared/models/dashboard.models.ts index 1869420c56..bf71997e35 100644 --- a/ui-ngx/src/app/shared/models/dashboard.models.ts +++ b/ui-ngx/src/app/shared/models/dashboard.models.ts @@ -36,6 +36,7 @@ export interface DashboardInfo extends BaseData, ExportableEntity ```javascript var details = {}; if (metadata.prevAlarmDetails) { + details = JSON.parse(metadata.prevAlarmDetails); // remove prevAlarmDetails from metadata delete metadata.prevAlarmDetails; - details = JSON.parse(metadata.prevAlarmDetails); + //now metadata is the same as it comes IN this rule node } {:copy-code} ``` diff --git a/ui-ngx/src/assets/help/en_US/rulenode/create_alarm_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/create_alarm_node_script_fn.md index c6c2fe2481..36bdd21d16 100644 --- a/ui-ngx/src/assets/help/en_US/rulenode/create_alarm_node_script_fn.md +++ b/ui-ngx/src/assets/help/en_US/rulenode/create_alarm_node_script_fn.md @@ -22,10 +22,11 @@ is a raw String field, and it needs to be converted into object using this const ```javascript var details = {}; -if (metadata.prevAlarmDetails) { - // remove prevAlarmDetails from metadata - delete metadata.prevAlarmDetails; +if (metadata.prevAlarmDetails != null) { details = JSON.parse(metadata.prevAlarmDetails); + // remove prevAlarmDetails from metadata + metadata.remove('prevAlarmDetails'); + //now metadata is the same as it comes IN this rule node } {:copy-code} ``` @@ -44,11 +45,11 @@ Also put temperature attribute from inbound Message payload into Al ```javascript var details = {temperature: msg.temperature, count: 1}; -if (metadata.prevAlarmDetails) { +if (metadata.prevAlarmDetails != null) { var prevDetails = JSON.parse(metadata.prevAlarmDetails); // remove prevAlarmDetails from metadata - delete metadata.prevAlarmDetails; - if (prevDetails.count) { + metadata.remove('prevAlarmDetails'); + if (prevDetails.count != null) { details.count = prevDetails.count + 1; } } diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/clear_alarm_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/clear_alarm_node_script_fn.md new file mode 100644 index 0000000000..311086e785 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/clear_alarm_node_script_fn.md @@ -0,0 +1,69 @@ +#### Clear alarm details builder function + +
+
+ +*function Details(msg, metadata, msgType): any* + +[TBEL{:target="_blank"}](${siteBaseUrl}/docs/user-guide/tbel/) function generating **Alarm Details** object to update existing one. Used for storing additional parameters inside Alarm.
+For example you can save attribute name/value pair from Original Message payload or Metadata. + +**Parameters:** + +{% include rulenode/tbel/common_node_script_args %} + +**Returns:** + +Should return the object presenting **Alarm Details**. + +Current Alarm Details can be accessed via `metadata.prevAlarmDetails`.
+**Note** that `metadata.prevAlarmDetails` is a raw String field, and it needs to be converted into object using this construction: + +```javascript +var details = {}; +if (metadata.prevAlarmDetails != null) { + details = JSON.parse(metadata.prevAlarmDetails); + // remove prevAlarmDetails from metadata + metadata.remove('prevAlarmDetails'); + //now metadata is the same as it comes IN this rule node +} +{:copy-code} +``` + +
+ +##### Examples + +
    +
  • +Take count property from previous Alarm and increment it.
    +Also put temperature attribute from inbound Message payload into Alarm details: +
  • +
+ +```javascript +var details = {temperature: msg.temperature, count: 1}; + +if (metadata.prevAlarmDetails != null) { + var prevDetails = JSON.parse(metadata.prevAlarmDetails); + // remove prevAlarmDetails from metadata + metadata.remove('prevAlarmDetails'); + if (prevDetails.count != null) { + details.count = prevDetails.count + 1; + } +} + +return details; +{:copy-code} +``` + +
+ +More details about Alarms can be found in [this tutorial{:target="_blank"}](${siteBaseUrl}/docs/user-guide/alarms/). + +You can see the real life example, where this node is used, in the next tutorial: + +- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/) + +
+
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/common_node_script_args.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/common_node_script_args.md new file mode 100644 index 0000000000..296e5b735c --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/common_node_script_args.md @@ -0,0 +1,11 @@ +
    +
  • msg: {[key: string]: any} - is a Message payload key/value object. +
  • +
  • metadata: {[key: string]: string} - is a Message metadata key/value map, where both keys and values are strings. +
  • +
  • msgType: string - is a string containing Message type. See MessageType enum for common used values. +
  • +
+ +Enable 'debug mode' for your rule node to see the messages that arrive in near real-time. +See Debugging for more information. \ No newline at end of file diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/create_alarm_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/create_alarm_node_script_fn.md new file mode 100644 index 0000000000..afc9051590 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/create_alarm_node_script_fn.md @@ -0,0 +1,69 @@ +#### Create alarm details builder function + +
+
+ +*function Details(msg, metadata, msgType): any* + +[TBEL{:target="_blank"}](${siteBaseUrl}/docs/user-guide/tbel/) function generating **Alarm Details** object. Used for storing additional parameters inside Alarm.
+For example you can save attribute name/value pair from Original Message payload or Metadata. + +**Parameters:** + +{% include rulenode/tbel/common_node_script_args %} + +**Returns:** + +Should return the object presenting **Alarm Details**. + +**Optional:** previous Alarm Details can be accessed via `metadata.prevAlarmDetails`.
+If previous Alarm does not exist, this field will not be present in Metadata. **Note** that `metadata.prevAlarmDetails`
+is a raw String field, and it needs to be converted into object using this construction: + +```javascript +var details = {}; +if (metadata.prevAlarmDetails) { + // remove prevAlarmDetails from metadata + delete metadata.prevAlarmDetails; + details = JSON.parse(metadata.prevAlarmDetails); +} +{:copy-code} +``` + +
+ +##### Examples + +
    +
  • +Take count property from previous Alarm and increment it.
    +Also put temperature attribute from inbound Message payload into Alarm details: +
  • +
+ +```javascript +var details = {temperature: msg.temperature, count: 1}; + +if (metadata.prevAlarmDetails) { + var prevDetails = JSON.parse(metadata.prevAlarmDetails); + // remove prevAlarmDetails from metadata + delete metadata.prevAlarmDetails; + if (prevDetails.count) { + details.count = prevDetails.count + 1; + } +} + +return details; +{:copy-code} +``` + +
+ +More details about Alarms can be found in [this tutorial{:target="_blank"}](${siteBaseUrl}/docs/user-guide/alarms/). + +You can see the real life example, where this node is used, in the next tutorial: + +- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/) + +
+
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/filter_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/filter_node_script_fn.md new file mode 100644 index 0000000000..4435acaeff --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/filter_node_script_fn.md @@ -0,0 +1,84 @@ +#### Filter message function + +
+
+ +*function Filter(msg, metadata, msgType): boolean* + +[TBEL{:target="_blank"}](${siteBaseUrl}/docs/user-guide/tbel/) function defines a boolean expression based on the incoming Message and Metadata. + +**Parameters:** + +{% include rulenode/tbel/common_node_script_args %} + +**Returns:** + +Must return a `boolean` value. If `true` - routes Message to subsequent rule nodes that are related via **True** link, +otherwise sends Message to rule nodes related via **False** link. +Uses 'Failure' link in case of any failures to evaluate the expression. + +
+ +##### Examples + +* Forward all messages with `temperature` value greater than `20` to the **True** link and all other messages to the **False** link. + Assumes that incoming messages always contain the 'temperature' field: + +```javascript +return msg.temperature > 20; +{:copy-code} +``` + + +Example of the rule chain configuration: + +![image](${helpBaseUrl}/help/images/rulenode/examples/filter-node.png) + +* Same as above, but checks that the message has 'temperature' field to **avoid failures** on unexpected messages: + +```javascript +return msg.temperature != null && msg.temperature > 20; +{:copy-code} +``` + +* Forward all messages with type `ATTRIBUTES_UPDATED` to the **True** chain and all other messages to the **False** chain: + +```javascript +if (msgType == 'ATTRIBUTES_UPDATED') { + return true; +} else { + return false; +} +{:copy-code} +``` + +
    +
  • Send message to the True chain if the following conditions are met.
    Message type is POST_TELEMETRY_REQUEST and
    +(device type is vehicle and humidity value is greater than 50 or
    +device type is controller and temperature value is greater than 20 and humidity value is greater than 60).
    +Otherwise send message to the False chain: +
  • +
+ +```javascript +if (msgType == 'POST_TELEMETRY_REQUEST') { + if (metadata.deviceType == 'vehicle') { + return msg.humidity > 50; + } else if (metadata.deviceType == 'controller') { + return msg.temperature > 20 && msg.humidity > 60; + } +} +return false; +{:copy-code} +``` + +
+ +You can see real life example, how to use this node in those tutorials: + +- [Create and Clear Alarms{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/create-clear-alarms/#node-a-filter-script) +- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-filter-script-node) + +
+
+ diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/generator_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/generator_node_script_fn.md new file mode 100644 index 0000000000..7618ac4090 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/generator_node_script_fn.md @@ -0,0 +1,110 @@ +#### Message generator function + +
+
+ +*function Generate(prevMsg, prevMetadata, prevMsgType): {msg: object, metadata: object, msgType: string}* + +[TBEL{:target="_blank"}](${siteBaseUrl}/docs/user-guide/tbel/) function generating new Message using previous Message payload, Metadata and Message type as input arguments. + +**Parameters:** + +
    +
  • prevMsg: {[key: string]: any} - is a previously generated Message payload key/value object. +
  • +
  • prevMetadata: {[key: string]: string} - is a previously generated Message metadata key/value object. +
  • +
  • prevMsgType: string - is a previously generated string Message type. See MessageType enum for common used values. +
  • +
+ +**Returns:** + +Should return the object with the following structure: + +```javascript +{ + msg: {[key: string]: any}, + metadata: {[key: string]: string}, + msgType: string +} +``` + +All fields in resulting object are mandatory. + +
+ +##### Examples + +* Generate message of type `POST_TELEMETRY_REQUEST` with random `temperature` value from `18` to `32`: + +```javascript +var temperature = 18 + Math.random() * (32 - 18); +// Round to at most 2 decimal places (optional) +temperature = Math.round( temperature * 100 ) / 100; +var msg = { temperature: temperature }; +return { msg: msg, metadata: {}, msgType: "POST_TELEMETRY_REQUEST" }; +{:copy-code} +``` + + +
    +
  • +Generate message of type POST_TELEMETRY_REQUEST with temp value 42, +humidity value 77
    +and metadata with field data having value 40: +
  • +
+ +```javascript +var msg = { temp: 42, humidity: 77 }; +var metadata = { data: 40 }; +return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" }; +{:copy-code} +``` + +
    +
  • +Generate message of type POST_TELEMETRY_REQUEST with temperature value
    +increasing and decreasing linearly in the range from 18 to 32: +
  • +
+ +```javascript +var lower = 18; +var upper = 32; +var isDecrement = 'false'; +var temperature = lower; + +// Get previous values + +if (prevMetadata != null && prevMetadata.isDecrement != null) { + isDecrement = prevMetadata.isDecrement; +} +if (prevMsg != null && prevMsg.temperature != null) { + temperature = prevMsg.temperature; +} + +if (isDecrement == 'true') { + temperature--; + if (temperature <= lower) { + isDecrement = 'false'; + temperature = lower; + } +} else { + temperature++; + if (temperature >= upper) { + isDecrement = 'true'; + temperature = upper; + } +} + +var msg = { temperature: temperature }; +var metadata = { isDecrement: isDecrement }; + +return { msg: msg, metadata: metadata, msgType: "POST_TELEMETRY_REQUEST" }; +{:copy-code} +``` + +
+
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/log_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/log_node_script_fn.md new file mode 100644 index 0000000000..bab38ad46f --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/log_node_script_fn.md @@ -0,0 +1,37 @@ +#### Message to string function + +
+
+ +*function toString(msg, metadata, msgType): string* + +[TBEL{:target="_blank"}](${siteBaseUrl}/docs/user-guide/tbel/) function transforming incoming Message to String for further logging to the server log file. + +**Parameters:** + +{% include rulenode/tbel/common_node_script_args %} + +**Returns:** + +Should return `string` value used for logging to the server log file. + +
+ +##### Examples + +* Create string message containing incoming message and incoming metadata values: + +```javascript +return 'Incoming message:\n' + JSON.stringify(msg) + + '\nIncoming metadata:\n' + JSON.stringify(metadata); +{:copy-code} +``` + +
+ +You can see real life example, how to use this node in this tutorial: + +- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#log-unknown-request) + +
+
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/switch_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/switch_node_script_fn.md new file mode 100644 index 0000000000..7fd3b7447b --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/switch_node_script_fn.md @@ -0,0 +1,101 @@ +#### Switch message function + +
+
+ +*function Switch(msg, metadata, msgType): string[]* + +[TBEL{:target="_blank"}](${siteBaseUrl}/docs/user-guide/tbel/) function computing **an array of Link names** to forward the incoming Message. + +**Parameters:** + +{% include rulenode/tbel/common_node_script_args %} + +**Returns:** + +Should return an array of `string` values presenting **link names** that the Rule Engine should use to further route the incoming Message.
+If the result is an empty array - message will not be routed to any Node and will be immediately +acknowledged. + +
+ +##### Examples + +
    +
  • +Forward all messages with temperature value greater than 30 to the 'High temperature' chain,
    +with temperature value lower than 20 to the 'Low temperature' chain and all other messages
    +to the 'Other' chain: +
  • +
+ +```javascript +if (msg.temperature > 30) { + return ['High temperature']; +} else if (msg.temperature < 20) { + return ['Low temperature']; +} else { + return ['Other']; +} +{:copy-code} +``` + +Example of the rule chain configuration: + +![image](${helpBaseUrl}/help/images/rulenode/examples/switch-node.png) + +
    +
  • + For messages with type POST_TELEMETRY_REQUEST: +
      +
    • + if temperature value lower than 18 forward to the 'Low temperature telemetry' chain, +
    • +
    • + otherwise to the 'Normal temperature telemetry' chain. +
    • +
    + For messages with type POST_ATTRIBUTES_REQUEST:
    +
      +
    • + if currentState value is IDLE forward to the 'Idle State' and 'Update State Attribute' chains, +
    • +
    • + if currentState value is RUNNING forward to the 'Running State' and 'Update State Attribute' chains, +
    • +
    • + otherwise to the 'Unknown State' chain. +
    • +
    + For all other message types - discard the message (do not route to any Node). +
  • +
+ +```javascript +if (msgType == 'POST_TELEMETRY_REQUEST') { + if (msg.temperature < 18) { + return ['Low Temperature Telemetry']; + } else { + return ['Normal Temperature Telemetry']; + } +} else if (msgType == 'POST_ATTRIBUTES_REQUEST') { + if (msg.currentState == 'IDLE') { + return ['Idle State', 'Update State Attribute']; + } else if (msg.currentState == 'RUNNING') { + return ['Running State', 'Update State Attribute']; + } else { + return ['Unknown State']; + } +} +return []; +{:copy-code} +``` + +
+ +You can see real life example, how to use this node in this tutorial: + +- [Data function based on telemetry from 2 devices{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/function-based-on-telemetry-from-two-devices#delta-temperature-rule-chain) + +
+
diff --git a/ui-ngx/src/assets/help/en_US/rulenode/tbel/transformation_node_script_fn.md b/ui-ngx/src/assets/help/en_US/rulenode/tbel/transformation_node_script_fn.md new file mode 100644 index 0000000000..44ada19967 --- /dev/null +++ b/ui-ngx/src/assets/help/en_US/rulenode/tbel/transformation_node_script_fn.md @@ -0,0 +1,89 @@ +#### Transform message function + +
+
+ +*function Transform(msg, metadata, msgType): {msg: object, metadata: object, msgType: string}* + +The [TBEL{:target="_blank"}](${siteBaseUrl}/docs/user-guide/tbel/) function to transform input Message payload, Metadata and/or Message type to the output message. + +**Parameters:** + +{% include rulenode/tbel/common_node_script_args %} + +**Returns:** + +Should return the object with the following structure: + +```javascript +{ + msg?: {[key: string]: any}, + metadata?: {[key: string]: string}, + msgType?: string +} +``` + +All fields in resulting object are optional and will be taken from original message if not specified. + +
+ +##### Examples + +* Add sum of two fields ('a' and 'b') as a new field ('sum') of existing message: + +```javascript +if(msg.a != null && msg.b != null){ + msg.sum = msg.a + msg.b; +} +return {msg: msg}; +``` + +* Transform value of the 'temperature' field from °F to °C: + +```javascript +msg.temperature = toFixed((msg.temperature - 32) * 5 / 9, 1); +return {msg: msg}; +``` + +* Replace the incoming message with the new message that contains only one field - count of properties in the original message: + +```javascript +var newMsg = { + count: msg.size() +}; +return {msg: newMsg}; +``` + +
    +
  • Change message type to CUSTOM_UPDATE,
    add additional attribute version into payload with value v1.1,
    change sensorType attribute value in Metadata to roomTemp:
  • +
+ +```javascript +var newType = "CUSTOM_UPDATE"; +msg.version = "v1.1"; +metadata.sensorType = "roomTemp" +return {msg: msg, metadata: metadata, msgType: newType}; +{:copy-code} +``` + +* Replace the incoming message with **two** new messages that contain only one field - sum or difference of properties in the original message: + +```javascript +var sum = msg.a + msg.b; +var diff = msg.a - msg.b; + +return [ + {msg: {sum: sum}, metadata: metadata, msgType: msgType}, + {msg: {difference: diff}, metadata: metadata, msgType: msgType} + ]; +``` + +
+ +You can see real life example, how to use this node in those tutorials: + +- [Transform incoming telemetry{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/transform-incoming-telemetry/) +- [Reply to RPC Calls{:target="_blank"}](${siteBaseUrl}/docs/user-guide/rule-engine-2-0/tutorials/rpc-reply-tutorial#add-transform-script-node) + +
+
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 3f3f70860f..fb8d7d8adc 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -329,6 +329,7 @@ "repository-url-required": "Repository URL is required.", "default-branch": "Default branch name", "repository-read-only": "Read-only", + "show-merge-commits": "Show merge commits", "authentication-settings": "Authentication settings", "auth-method": "Authentication method", "auth-method-username-password": "Password / access token", @@ -381,6 +382,28 @@ "within-time": "Within time (sec)", "within-time-pattern": "Time must be a positive integer.", "within-time-required": "Time is required." + }, + "jwt": { + "security-settings": "JWT security settings", + "issuer-name": "Issuer name", + "issuer-name-required": "Issuer name is required.", + "signings-key": "Signing key", + "signings-key-hint": "Base64 encoded string representing at least 256 bits of data.", + "signings-key-required": "Signing key is required.", + "signings-key-min-length": "Signing key must be at least 256 bits of data.", + "signings-key-base64": "Signing key must be base64 format.", + "expiration-time": "Token expiration time (sec)", + "expiration-time-required": "Token expiration time is required.", + "expiration-time-pattern": "Token expiration time be a positive integer.", + "expiration-time-min": "Minimum time is 60 seconds (1 minute).", + "refresh-expiration-time": "Refresh token expiration time (sec)", + "refresh-expiration-time-required": "Refresh token expiration time is required.", + "refresh-expiration-time-pattern": "Refresh token expiration time be a positive integer.", + "refresh-expiration-time-min": "Minimum time is 900 seconds (15 minute).", + "refresh-expiration-time-less-token": "Refresh token time must be greater token time.", + "generate-key": "Generate key", + "info-header": "All users will be to re-logined", + "info-message": "Change of the JWT Signing Key will cause all issued tokens to be invalid. All users will need to re-login. This will also affect scripts that use Rest API/Websockets." } }, "alarm": { @@ -2991,7 +3014,7 @@ "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", + "script-lang-tbel": "TBEL", "message": "Message", "message-type": "Message type", "select-message-type": "Select message type", @@ -3685,6 +3708,7 @@ "order": "Order", "height": "Height", "mobile-hide": "Hide widget in mobile mode", + "desktop-hide": "Hide widget in desktop mode", "units": "Special symbol to show next to value", "decimals": "Number of digits after floating point", "timewindow": "Timewindow", 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 8263cc1614..2f1d4cab96 100644 --- a/ui-ngx/src/assets/locale/locale.constant-zh_CN.json +++ b/ui-ngx/src/assets/locale/locale.constant-zh_CN.json @@ -1,3282 +1,4786 @@ { - "access": { - "access-forbidden": "禁止访问", - "access-forbidden-text": "您没有访问此位置的权限
如果您仍希望访问此位置,请尝试使用其他用户登录。", - "permission-denied": "权限被拒绝", - "permission-denied-text": "您没有执行此操作的权限!", - "refresh-token-expired": "会话已过期", - "refresh-token-failed": "无法刷新会话", - "unauthorized": "未授权", - "unauthorized-access": "未授权访问", - "unauthorized-access-text": "您需要登陆才能访问这个资源!" - }, - "action": { - "activate": "激活", - "add": "添加", - "apply": "应用", - "apply-changes": "应用更改", - "assign": "分配", - "back": "后退", - "cancel": "取消", - "clear-search": "清除查询", - "close": "关闭", - "continue": "继续", - "copy": "复制", - "copy-reference": "复制引用", - "create": "创建", - "decline-changes": "撤销更改", - "delete": "删除", - "discard-changes": "放弃更改", - "download": "下载", - "drag": "拖拽", - "edit": "编辑", - "edit-mode": "编辑模式", - "enter-edit-mode": "进入编辑模式", - "export": "导出", - "hide": "隐藏", - "import": "导入", - "make-private": "私有", - "next-with-label": "下一个:{{label}}", - "no": "否", - "ok": "确定", - "paste": "粘贴", - "paste-reference": "粘贴引用", - "read-more": "阅读更多", - "refresh": "刷新", - "remove": "移除", - "run": "运行", - "save": "保存", - "saveAs": "另存为", - "search": "查询", - "select": "选择", - "share": "分享", - "share-via": "通过 {{provider}}分享", - "sign-in": "登录!", - "suspend": "暂停", - "unassign": "取消分配", - "undo": "撤销", - "update": "更新", - "view": "查看", - "yes": "是" - }, - "admin": { - "aws-access-key-id": "AWS访问密钥ID", - "aws-access-key-id-required": "需要访问AWS密钥ID", - "aws-region": "AWS地区", - "aws-region-required": "AWS 区域必填", - "aws-secret-access-key": "AWS秘密访问密钥", - "aws-secret-access-key-required": "AWS 访问密钥必填", - "base-url": "基本URL", - "base-url-required": "基本URL必填。", - "domain-name": "域名", - "domain-name-unique": "域名和协议必须是唯一的。", - "enable-proxy": "启用代理", - "enable-tls": "启用TLS", - "error-verification-url": "域名不应包含符号 “/” 和 “:”。例:thingsboard.io", - "general": "基本设置", - "general-policy": "基本策略", - "general-settings": "基本设置", - "home-settings": "首页设置", - "mail-from": "邮件来自", - "mail-from-required": "邮件发件人必填。", - "max-failed-login-attempts": "登录失败之前的最大登录尝试次数", - "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": "最少大写字母位数不能为负数", - "number-from": "发送方电话号码", - "number-from-required": "发送方电话号码必填。", - "number-to": "电话号码至", - "number-to-required": "电话号码必填。", - "oauth2": { - "access-token-uri": "访问令牌URI", - "access-token-uri-required": "访问令牌 URI 必填。", - "activate-user": "激活用户", - "add-domain": "添加域", - "add-provider": "添加 Provider", - "allow-user-creation": "允许用户创建", - "always-fullscreen": "始终全屏", - "authorization-uri": "授权URI", - "authorization-uri-required": "授权 URI 必填。", - "client-authentication-method": "客户端身份验证方法", - "client-id": "客户端ID", - "client-id-required": "客户端 ID 必填。", - "client-secret": "客户机密", - "client-secret-required": "需要客户端密码。", - "copy-redirect-uri": "复制重定向URI", - "custom-setting": "自定义设置", - "customer-name-pattern": "客户名称模式", - "default-dashboard-name": "默认仪表板名称", - "delete-domain": "删除域", - "delete-domain-text": "请注意:确认后,域和所有 provider data 将不可恢复。", - "delete-domain-title": "确定要删除域 '{{domainName}}' 的设置吗?", - "delete-provider": "删除 Provider", - "delete-registration-text": "请注意:确认后 provider data 将不可恢复。", - "delete-registration-title": "确定要删除 provider '{{name}}' 吗?", - "domain-schema-http": "HTTP", - "domain-schema-https": "HTTPS", - "domain-schema-mixed": "HTTP+HTTPS", - "email-attribute-key": "电子邮件属性键", - "email-attribute-key-required": "电子邮件属性密钥必填。", - "enable": "启用OAuth2设置", - "first-name-attribute-key": "名字属性键", - "general": "General", - "jwk-set-uri": "JSON Web Key URI", - "last-name-attribute-key": "姓氏属性键", - "login-button-icon": "登录按钮图标", - "login-button-label": "Provider 标签", - "login-button-label-placeholder": "使用 $(Provider label) 登录", - "login-button-label-required": "标签必填。", - "login-provider": "Login provider", - "mapper": "Mapper", - "new-domain": "新建域", - "oauth2": "OAuth2", - "protocol": "协议", - "redirect-uri-template": "重定向URI模板", - "registration-id": "注册ID", - "registration-id-required": "注册 ID 必填。", - "registration-id-unique": "系统的注册ID必须是唯一的。", - "scope": "范围", - "scope-required": "范围必填。", - "tenant-name-pattern": "租户名称模式", - "tenant-name-pattern-required": "租户名称模式必填。", - "tenant-name-strategy": "租户名称策略", - "type": "映射器类型", - "uri-pattern-error": "无效的URI格式。", - "url": "统一资源定位地址", - "url-pattern": "无效的URL格式。", - "url-required": "URL 必填。", - "user-info-uri": "用户信息URI", - "user-info-uri-required": "用户信息 URI 必填。", - "user-name-attribute-name": "用户名属性键", - "user-name-attribute-name-required": "用户名属性密钥必填" - }, - "outgoing-mail": "发送邮件", - "outgoing-mail-settings": "发送邮件设置", - "password-expiration-period-days": "密码有效期(天)", - "password-expiration-period-days-range": "密码过期期限(天)不能为负", - "password-policy": "密码策略", - "password-reuse-frequency-days": "密码重用频率(天)", - "password-reuse-frequency-days-range": "天内密码重用频率不能为负", - "phone-number-hint": "E.164格式的手机号码,例如+19995550123", - "phone-number-pattern": "手机号码无效。应为E.164格式,例如+19995550123。", - "prohibit-different-url": "禁止从客户端请求头中使用主机名", - "prohibit-different-url-hint": "应为生产环境启用此设置。禁用时可能会导致安全问题", - "proxy-host": "代理主机", - "proxy-host-required": "代理主机必填。", - "proxy-password": "代理密码", - "proxy-port": "代理端口", - "proxy-port-range": "代理端口应在1到65535之间。", - "proxy-port-required": "代理端口必填。", - "proxy-user": "代理用户", - "security-settings": "安全设置", - "send-test-mail": "发送测试邮件", - "send-test-sms": "发送测试短信", - "sms-message": "短信", - "sms-message-max-length": "短信长度不能超过1600个字符", - "sms-message-required": "短消息内容必填。", - "sms-provider": "SMS 服务商", - "sms-provider-settings": "SMS 服务商设置", - "sms-provider-type": "SMS 服务商类型", - "sms-provider-type-aws-sns": "亚马逊社交网站", - "sms-provider-type-required": "SMS 服务商类型必填。", - "sms-provider-type-twilio": "Twilio", - "smtp-host": "SMTP主机", - "smtp-host-required": "SMTP主机必填。", - "smtp-port": "SMTP端口", - "smtp-port-invalid": "这看起来不是有效的smtp端口。", - "smtp-port-required": "您必须提供一个smtp端口。", - "smtp-protocol": "SMTP协议", - "system-settings": "系统设置", - "test-mail-sent": "测试邮件发送成功!", - "test-sms-sent": "测试短信发送成功!", - "timeout-invalid": "这看起来不像有效的超时值。", - "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": "如果用户帐户锁定,请发送通知到电子邮件", - "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": "聚合", - "avg": "平均值", - "count": "计数", - "function": "数据聚合功能", - "group-interval": "分组间隔", - "limit": "限制数", - "max": "最大值", - "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": "告警必填", - "alarm-severity-list": "警报严重性列表", - "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-time": "清除时间", - "created-time": "创建时间", - "details": "详情", - "display-status": { - "ACTIVE_ACK": "激活已确认", - "ACTIVE_UNACK": "激活未确认", - "CLEARED_ACK": "清除已确认", - "CLEARED_UNACK": "清除未确认" - }, - "end-time": "结束时间", - "fetch-size": "获取大小", - "fetch-size-error-min": "最小值为10。", - "fetch-size-required": "Fetch size 必填。", - "max-count-load": "要加载的最大告警数(0-无限制)", - "max-count-load-error-min": "最小值为0。", - "max-count-load-required": "加载的最大告警数必填。", - "min-polling-interval-message": "轮询间隔至少是1秒。", - "no-alarms-matching": "未找到匹配 '{{entity}}' 的告警", - "no-alarms-prompt": "未发现告警", - "no-data": "无数据显示", - "originator": "发起者", - "originator-type": "发起者类型", - "polling-interval": "告警轮询间隔(秒)", - "polling-interval-required": "告警轮询间隔必填。", - "search": "查找告警", - "search-propagated-alarms": "检索已传递的警报", - "search-status": { - "ACK": "已确认", - "ACTIVE": "激活", - "ANY": "所有", - "CLEARED": "已清除", - "UNACK": "未确认" - }, - "select-alarm": "选择告警", - "selected-alarms": "已选择 { count, plural, 1 {# 个告警} other {# 个告警} }", - "severity": "严重程度", - "severity-critical": "危险", - "severity-indeterminate": "不确定", - "severity-major": "重要", - "severity-minor": "次要", - "severity-warning": "警告", - "start-time": "开始时间", - "status": "状态", - "type": "类型" - }, - "alias": { - "add": "添加别名", - "all-entities": "所有实体", - "any-relation": "不限", - "default-entity-parameter-name": "默认", - "default-state-entity": "默认状态实体", - "duplicate-alias": "别名已经存在。", - "edit": "编辑别名", - "entity-filter": "实体筛选器", - "entity-filter-no-entity-matched": "未找到匹配指定筛选条件的实体。", - "filter-type": "筛选器类型", - "filter-type-apiUsageState": "Api使用状态", - "filter-type-asset-search-query": "资产搜索查询", - "filter-type-asset-search-query-description": "类型为 {{assetTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的资产", - "filter-type-asset-type": "资产类型", - "filter-type-asset-type-and-name-description": "类型为 '{{assetType}}' 且以 '{{prefix}}' 开头的资产", - "filter-type-asset-type-description": "类型为 '{{assetType}}' 的资产", - "filter-type-device-search-query": "设备搜索查询", - "filter-type-device-search-query-description": "类型为 {{deviceTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的设备", - "filter-type-device-type": "设备类型", - "filter-type-device-type-and-name-description": "类型为 '{{deviceType}}' 且以 '{{prefix}}' 开头的设备", - "filter-type-device-type-description": "类型为 '{{deviceType}}' 的设备", - "filter-type-edge-type": "边缘类型", - "filter-type-edge-type-description": "类型为 '{{edgeType}}' 的边缘", - "filter-type-edge-type-and-name-description": "类型为 '{{edgeType}}' 且以 '{{prefix}}' 开头的边缘", - "filter-type-edge-search-query": "边缘搜索查询", - "filter-type-edge-search-query-description": "类型为 {{edgeTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的边缘", - "filter-type-entity-list": "实体列表", - "filter-type-entity-name": "实体名称", - "filter-type-entity-view-search-query": "实体视图搜索查询", - "filter-type-entity-view-search-query-description": "类型为 {{entityViewTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的实体视图", - "filter-type-entity-view-type": "实体视图类型", - "filter-type-entity-view-type-and-name-description": "类型为 {{entityView}}' 且以 '{{prefix}}' 开头的实体视图", - "filter-type-entity-view-type-description": "类型为 '{{entityView}}' 的实体视图", - "filter-type-relations-query": "关联查询", - "filter-type-relations-query-description": "具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的 {{entities}} ", - "filter-type-required": "筛选器类型必填。", - "filter-type-single-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": "解析为多实体", - "root-entity": "根实体", - "root-state-entity": "使用仪表板状态实体作为根实体", - "state-entity": "仪表板状态实体", - "state-entity-parameter-name": "状态实体参数名称", - "unlimited-level": "不限层级" - }, - "api-usage": { - "api-usage": "Api 使用统计", - "data-points": "数据点", - "data-points-storage-days": "存储点天", - "email": "Email", - "email-messages": "Email messages", - "email-messages-daily-activity": "Email messages daily activity", - "email-messages-hourly-activity": "Email messages hourly activity", - "email-messages-monthly-activity": "Email messages monthly activity", - "exceptions": "Exceptions", - "executions": "Executions", - "javascript": "JavaScript", - "javascript-executions": "JavaScript executions", - "javascript-functions": "JavaScript functions", - "javascript-functions-daily-activity": "JavaScript functions daily activity", - "javascript-functions-hourly-activity": "JavaScript functions hourly activity", - "javascript-functions-monthly-activity": "JavaScript functions monthly activity", - "latest-error": "最新错误", - "messages": "消息", - "permanent-failures": "${entityName} 永久性故障", - "permanent-timeouts": "${entityName} 永久超时", - "processing-failures": "${entityName} 处理失败", - "processing-failures-and-timeouts": "处理失败和超时", - "processing-timeouts": "${entityName} 处理超时", - "queue-stats": "队列统计信息", - "rule-chain": "规则链", - "rule-engine": "规则引擎", - "rule-engine-daily-activity": "Rule Engine daily activity", - "rule-engine-executions": "Rule Engine executions", - "rule-engine-hourly-activity": "Rule Engine hourly activity", - "rule-engine-monthly-activity": "Rule Engine monthly activity", - "rule-engine-statistics": "规则引擎统计信息", - "rule-node": "规则节点", - "sms": "SMS", - "sms-messages": "短信", - "sms-messages-daily-activity": "SMS messages daily activity", - "sms-messages-hourly-activity": "SMS messages hourly activity", - "sms-messages-monthly-activity": "SMS messages monthly activity", - "successful": "${entityName} 成功", - "telemetry": "遥测数据", - "telemetry-persistence": "Telemetry persistence", - "telemetry-persistence-daily-activity": "Telemetry persistence daily activity", - "telemetry-persistence-hourly-activity": "Telemetry persistence hourly activity", - "telemetry-persistence-monthly-activity": "Telemetry persistence monthly activity", - "transport": "Transport", - "transport-daily-activity": "Transport daily activity", - "transport-data-points": "传输数据点", - "transport-hourly-activity": "Transport hourly activity", - "transport-messages": "传输消息", - "transport-monthly-activity": "Transport monthly activity", - "view-details": "查看详细信息", - "view-statistics": "查看统计信息", - "alarm":"告警", - "alarms-created": "创建告警数", - "notifications": "通知" - }, - "asset": { - "add": "添加资产", - "add-asset-text": "添加新资产", - "any-asset": "任何资产", - "asset": "资产", - "asset-details": "资产详情", - "asset-file": "资产档案", - "asset-public": "资产公开", - "asset-required": "资产必填", - "asset-type": "资产类型", - "asset-type-list-empty": "资产类型未选择。", - "asset-type-required": "资产类型必填。", - "asset-types": "资产类型", - "assets": "资产", - "assign-asset-to-customer": "将资产分配给客户", - "assign-asset-to-customer-text": "请选择要分配给客户的资产", - "assign-asset-to-edge-text": "请选择要分配给边缘的资产", - "assign-asset-to-edge-title": "将资产分配给边缘", - "assign-assets": "分配资产", - "assign-assets-text": "分配 { count, plural, 1 {# 个资产} other {# 个资产} } 给客户", - "assign-new-asset": "分配新资产", - "assign-to-customer": "分配给客户", - "assign-to-customer-text": "请选择客户以分配资产", - "assignedToCustomer": "分配客户", - "copyId": "复制资产ID", - "delete": "删除资产", - "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 {# 个资产} }吗?", - "description": "说明", - "details": "详情", - "enter-asset-type": "输入资产类型", - "events": "事件", - "idCopiedMessage": "资产ID已经复制到粘贴板", - "import": "导入资产", - "label": "标签", - "make-private": "资产设为私有", - "make-private-asset-text": "确认后,资产及其所有数据将被私有化,无法被他人访问。", - "make-private-asset-title": "确定要将资产 '{{assetName}}' 设为私有吗?", - "make-public": "资产设为公开", - "make-public-asset-text": "确认后,资产及其所有数据将被公开并被他人访问。", - "make-public-asset-title": "确定要将资产 '{{assetName}}' 设为公开吗?", - "management": "资产管理", - "name": "名称", - "name-required": "名称必填。", - "name-starts-with": "资产名称以此开头", - "no-asset-types-matching": "未找到匹配 '{{entitySubtype}}' 的资产类型。", - "no-assets-matching": "未找到匹配 '{{entity}}' 的资产。", - "no-assets-text": "未找到资产", - "public": "公开", - "search": "查找资产", - "select-asset": "选择资产", - "select-asset-type": "选择资产类型", - "selected-assets": "已选择 { count, plural, 1 {# 个资产} other {# 个资产} }", - "type": "类型", - "type-required": "类型必填。", - "unassign-asset": "未分配资产", - "unassign-asset-from-edge": "取消分配边缘", - "unassign-asset-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。", - "unassign-asset-from-edge-title": "确定要取消对'{{assetName}}'资产的分配吗?", - "unassign-asset-text": "确认后,资产将未分配,客户无法访问。", - "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-from-customer": "取消分配客户", - "unassign-assets-from-edge": "取消分配边缘", - "unassign-assets-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。", - "unassign-assets-from-edge-title": "确定要取消分配 { count, plural, 1 {1 资产} other {# 个资产} }吗?", - "view-assets": "查看资产" - }, - "attribute": { - "add": "添加属性", - "add-to-dashboard": "添加到仪表板", - "add-widget-to-dashboard": "将部件添加到仪表板", - "attributes": "属性", - "attributes-scope": "设备属性范围", - "delete-attributes": "删除属性", - "delete-attributes-text": "注意,确认后所有选中的属性都会被删除。", - "delete-attributes-title": "确定要删除 { count, plural, 1 {# 个属性} other {# 个属性} }吗?", - "enter-attribute-value": "输入属性值", - "key": "键名", - "key-required": "属性键必填。", - "last-update-time": "最后更新时间", - "latest-telemetry": "最新遥测数据", - "next-widget": "下一个部件", - "no-attributes-text": "未找到属性", - "no-telemetry-text": "未找到遥测数据", - "prev-widget": "上一个部件", - "scope-client": "客户端属性", - "scope-latest-telemetry": "最新遥测数据", - "scope-server": "服务端属性", - "scope-shared": "共享属性", - "selected-attributes": "已选择{ count, plural, 1 {# 个属性} other {# 个属性} }", - "selected-telemetry": "已选择 { count, plural, 1 {# telemetry unit} other {# telemetry units} }", - "show-on-widget": "在部件上显示", - "value": "数值", - "value-required": "属性值必填。", - "widget-mode": "部件模式" - }, - "audit-log": { - "action-data": "活动数据", - "audit": "审计", - "audit-log-details": "审计日志详情", - "audit-logs": "审计日志", - "clear-search": "清空查找", - "details": "详情", - "entity-name": "实体名称", - "entity-type": "实体类型", - "failure-details": "失败详情", - "no-audit-logs-prompt": "未找到日志", - "search": "查找审计日志", - "status": "状态", - "status-failure": "失败", - "status-success": "成功", - "timestamp": "时间戳", - "type": "类型", - "type-activated": "激活", - "type-added": "添加", - "type-alarm-ack": "已确认", - "type-alarm-clear": "已清除", - "type-assigned-from-tenant": "从租户分配", - "type-assigned-to-customer": "分配给客户", - "type-assigned-to-edge": "分配给边缘", - "type-assigned-to-tenant": "分配给租户", - "type-attributes-deleted": "删除属性", - "type-attributes-read": "读取属性", - "type-attributes-updated": "更新属性", - "type-credentials-read": "读取凭据", - "type-credentials-updated": "更新凭据", - "type-deleted": "删除", - "type-lockout": "锁定", - "type-login": "登录", - "type-logout": "注销", - "type-provision-failure": "设备预配置失败", - "type-provision-success": "设备已预配置", - "type-relation-add-or-update": "关联已更新", - "type-relation-delete": "关联已删除", - "type-relations-delete": "删除所有关联", - "type-rpc-call": "RPC调用", - "type-suspended": "暂停", - "type-timeseries-deleted": "遥测数据已删除", - "type-timeseries-updated": "遥测数据已更新", - "type-unassigned-from-customer": "未分配给客户", - "type-unassigned-from-edge": "未分配给边缘", - "type-updated": "更新", - "user": "用户" - }, - "common": { - "created-time": "创建时间", - "enter-password": "输入密码", - "enter-search": "输入检索条件", - "enter-username": "输入用户名", - "loading": "正在加载中...", - "password": "密码", - "username": "用户名", - "proceed": "继续", - "open-details-page": "打开详情页" - }, - "confirm-on-exit": { - "html-message": "有未保存的更改。
确定要离开此页面吗?", - "message": "有未保存的更改,确定要离开此页吗?", - "title": "未保存的更改" - }, - "contact": { - "address": "地址", - "address2": "地址二", - "city": "城市", - "country": "国家", - "email": "电子邮件", - "no-address": "无地址", - "phone": "电话", - "postal-code": "邮政编码", - "postal-code-invalid": "只允许数字。", - "state": "州" - }, - "content-type": { - "binary": "Binary (Base64)", - "json": "Json", - "text": "Text" - }, - "custom": { - "widget-action": { - "action-cell-button": "动作单元格按钮", - "element-click": "点击HTML元素时", - "marker-click": "点击标记时", - "node-selected": "选择节点时", - "pie-slice-click": "点击切片时", - "polygon-click": "单击多边形", - "row-click": "点击行时", - "row-double-click": "双击行时", - "tooltip-tag-action": "Tooltip tag action" - } - }, - "customer": { - "add": "添加客户", - "add-customer-text": "添加新客户", - "assets": "客户资产", - "copyId": "复制客户ID", - "customer": "客户", - "customer-details": "客户详情", - "customer-required": "客户必填", - "customers": "客户", - "dashboard": "客户仪表板", - "dashboards": "客户仪表板", - "default-customer": "默认客户", - "default-customer-required": "为了调试租户级别上的仪表板,需要默认客户。", - "delete": "删除此客户", - "delete-customer-text": "请注意:确认后,客户及其所有相关数据将不可恢复。", - "delete-customer-title": "确定要删除客户'{{customerTitle}}'吗?", - "delete-customers-action-title": "删除 { count, plural, 1 {# 个客户} other {# 个客户} }", - "delete-customers-text": "请注意:确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", - "delete-customers-title": "确定要删除 { count, plural, 1 {# 个客户} other {# 个客户} }吗?", - "description": "说明", - "details": "详情", - "devices": "客户设备", - "edges": "客户边缘实例", - "entity-views": "客户实体视图", - "events": "事件", - "idCopiedMessage": "客户ID已复制到粘贴板", - "manage-assets": "管理资产", - "manage-customer-assets": "管理客户资产", - "manage-customer-dashboards": "管理客户仪表板", - "manage-customer-devices": "管理客户设备", - "manage-customer-edges": "管理客户边缘", - "manage-customer-users": "管理客户用户", - "manage-dashboards": "管理仪表板", - "manage-devices": "管理设备", - "manage-edges": "管理边缘", - "manage-public-assets": "管理公共资产", - "manage-public-dashboards": "管理公共仪表板", - "manage-public-devices": "管理公共设备", - "manage-public-edges": "管理公共边缘", - "manage-users": "管理用户", - "management": "客户管理", - "no-customers-matching": "未找到匹配 '{{entity}}' 的客户。", - "no-customers-text": "未找到客户", - "public-assets": "公共资产", - "public-dashboards": "公共仪表板", - "public-devices": "公共设备", - "public-entity-views": "公共实体视图", - "public-edges": "公共边缘", - "search": "查找客户", - "select-customer": "选择客户", - "select-default-customer": "选择默认的客户", - "selected-customers": "已选择 { count, plural, 1 {# 个客户} other {# 个客户} }", - "title": "标题", - "title-required": "标题必填。" - }, - "dashboard": { - "add": "添加仪表板", - "add-dashboard-text": "添加新的仪表板", - "add-state": "添加仪表板状态", - "add-widget": "添加新的部件", - "alias-resolution-error-title": "仪表板别名配置错误", - "assign-dashboard-to-customer": "将仪表板分配给客户", - "assign-dashboard-to-customer-text": "请选择要分配给客户的仪表板", - "assign-dashboard-to-edge": "将仪表板分配给边缘", - "assign-dashboard-to-edge-text": "请选择要分配给边缘的仪表板", - "assign-dashboard-to-edge-title": "将仪表板分配给边缘", - "assign-dashboards": "分配仪表板", - "assign-dashboards-text": "分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户", - "assign-new-dashboard": "分配新的仪表板", - "assign-to-customer": "分配给客户", - "assign-to-customer-text": "请选择客户分配仪表板", - "assign-to-customers": "将仪表板分配给客户", - "assign-to-customers-text": "请选择客户指定仪表板", - "assigned-customers": "已分配的客户", - "assignedToCustomer": "分配给客户", - "assignedToCustomers": "分配给客户", - "autofill-height": "自动填充布局高度", - "background-color": "背景颜色", - "background-image": "背景图片", - "background-size-mode": "背景大小模式", - "cannot-upload-file": "无法上传文件", - "close-toolbar": "关闭工具栏", - "columns-count": "列数", - "columns-count-required": "需要列数。", - "configuration-error": "配置错误", - "copy-public-link": "复制公共链接", - "create-new": "创建新的仪表板", - "create-new-dashboard": "创建新的仪表板", - "create-new-widget": "创建新部件", - "dashboard": "仪表板", - "dashboard-details": "仪表板详情", - "dashboard-file": "仪表板文件", - "dashboard-import-missing-aliases-title": "配置导入仪表板使用的别名", - "dashboard-logo-image": "仪表板 Logo 图片", - "dashboard-required": "仪表板必填。", - "dashboards": "仪表板库", - "delete": "删除仪表板", - "delete-dashboard-text": "请注意:确认后,仪表板及其所有相关数据将不可恢复。", - "delete-dashboard-title": "确定要删除仪表板 '{{dashboardTitle}}'吗?", - "delete-dashboards": "删除仪表板", - "delete-dashboards-action-title": "删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", - "delete-dashboards-text": "请注意:确认后,所有选定的仪表板将被删除,所有相关数据将不可恢复。", - "delete-dashboards-title": "确定要删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }吗?", - "delete-state": "删除仪表板状态", - "delete-state-text": "确定要删除仪表板状态 '{{stateName}}' 吗?", - "delete-state-title": "删除仪表板状态", - "description": "说明", - "details": "详情", - "display-dashboard-export": "显示导出", - "display-dashboard-logo": "在仪表板全屏模式下显示 Logo", - "display-dashboard-timewindow": "显示时间窗口", - "display-dashboards-selection": "显示仪表板选项", - "display-entities-selection": "显示实体选项", - "display-filters": "显示筛选器", - "display-title": "显示仪表板标题", - "drop-image": "拖拽图像或单击以选择要上传的文件。", - "maximum-upload-file-size": "最大上传文件大小: {{ size }}", - "edit-state": "仪表板状态编辑", - "export": "导出仪表板", - "export-failed-error": "无法导出仪表板: {{error}}", - "hide-details": "隐藏详情", - "home-dashboard": "首页仪表板", - "home-dashboard-hide-toolbar": "隐藏首页仪表板工具栏", - "horizontal-margin": "水平边距", - "horizontal-margin-required": "需要水平边距值。", - "import": "导入仪表板", - "import-widget": "导入部件", - "invalid-aliases-config": "无法找到与某些别名筛选器匹配的任何设备。
请联系您的管理员以解决此问题。", - "invalid-dashboard-file-error": "无法导入仪表板: 仪表板数据结构无效。", - "invalid-widget-file-error": "无法导入窗口部件: 窗口部件数据结构无效。", - "is-root-state": "根状态", - "make-private": "仪表板设为私有", - "make-private-dashboard": "仪表板设为私有", - "make-private-dashboard-text": "确认后,仪表板将被设为私有,不能被其他人访问。", - "make-private-dashboard-title": "确定要将仪表板 '{{dashboardTitle}}' 设为私有吗?", - "make-public": "仪表板设为公开", - "manage-assigned-customers": "管理已分配的客户", - "manage-states": "仪表板状态管理", - "management": "仪表板管理", - "margin-required": "边距值必填。", - "max-columns-count-message": "只允许最多1000列", - "max-horizontal-margin-message": "只允许50作为最大水平边距值。", - "max-margin-message": "仅允许50作为最大边距值。", - "max-mobile-row-height-message": "移动端行高距最多200px。", - "max-vertical-margin-message": "只允许50作为最大垂直边距值。", - "min-columns-count-message": "只允许最少10列", - "min-horizontal-margin-message": "只允许0作为最小水平边距值。", - "min-margin-message": "最小边距值只允许为0。", - "min-mobile-row-height-message": "移动端行高距至少5px。", - "min-vertical-margin-message": "只允许0作为最小垂直边距值。", - "mobile-layout": "移动端布局设置", - "mobile-row-height": "移动端行高距(px)", - "mobile-row-height-required": "移动端行高距必填。", - "new-dashboard-title": "新仪表板标题", - "no-dashboards-matching": "未找到匹配 '{{entity}}' 的仪表板。", - "no-dashboards-text": "未找到仪表板", - "no-image": "无图像选择", - "no-states-text": "未找到状态", - "no-widgets": "没有配置部件", - "open-dashboard": "打开仪表板", - "open-toolbar": "打开仪表板工具栏", - "public": "公开", - "public-dashboard-notice": "提示: 不要忘记将相关设备公开以访问其数据。", - "public-dashboard-text": "仪表板 {{dashboardTitle}} 已被公开,可通过如下 链接访问:", - "public-dashboard-title": "仪表板现已公开", - "public-link": "公共链接", - "public-link-copied-message": "仪表板的公共链接已被复制到剪贴板", - "search": "查找仪表板", - "search-states": "仪表板状态检索", - "select-dashboard": "选择仪表板", - "select-devices": "选择设备", - "select-existing": "选择现有仪表板", - "select-state": "选择目标状态", - "select-widget-subtitle": "可用的部件类型列表", - "select-widget-title": "选择部件", - "select-widget-value": "{{title}}: 选择部件", - "selected-dashboards": "已选择 { count, plural, 1 {# 个仪表盘} other {# 个仪表盘} }", - "selected-states": "已选择 { count, plural, 1 {# 个仪表板状态} other {# 个仪表板状态} }", - "set-background": "设置背景", - "settings": "设置", - "show-details": "显示详情", - "socialshare-text": "'{{dashboardTitle}}' 由Thingsboard提供支持", - "socialshare-title": "'{{dashboardTitle}}' 由Thingsboard提供支持", - "state": "仪表板状态", - "state-controller": "状态控制", - "state-id": "状态ID", - "state-id-exists": "仪表板状态ID已经存在。", - "state-id-required": "仪表板状态ID必填。", - "state-name": "名称", - "state-name-required": "仪表板状态名必填。", - "states": "仪表板状态", - "title": "标题", - "image": "仪表板图片", - "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-dashboards": "取消分配仪表板", - "unassign-dashboards-action-text": "取消分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户", - "unassign-dashboards-action-title": "取消分配此客户 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", - "unassign-dashboards-text": "确认后,所有选定的仪表板将被取消分配,客户将无法访问。", - "unassign-dashboards-title": "确定要取消分配仪表板 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 吗?", - "unassign-dashboards-from-edge-text": "确认后,所有选定的仪表板将被取消分配,边缘将无法访问。", - "unassign-dashboards-from-edge-title": "确定要取消分配仪表板 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 吗?", - "unassign-from-customer": "取消分配客户", - "unassign-from-customers": "客户未分配仪表板", - "unassign-from-customers-text": "请选择从仪表板中取消分配的客户", - "vertical-margin": "垂直边距", - "vertical-margin-required": "需要垂直边距值。", - "view-dashboards": "查看仪表板", - "widget-file": "部件文件", - "widget-import-missing-aliases-title": "配置导入的窗口部件使用的别名", - "widgets-margins": "部件间边距" - }, - "datakey": { - "advanced": "高级", - "alarm": "告警字段", - "alarm-fields-required": "告警字段必填。", - "alarm-fields-timeseries-or-attributes-required": "告警字段或实体 Timeseries/属性必填。", - "attributes": "属性", - "color": "颜色", - "configuration": "数据键配置", - "data-generation-func": "数据生成功能", - "decimals": "小数位数", - "entity-field": "实体字段", - "function-types": "函数类型", - "function-types-required": "需要函数类型。", - "label": "标签", - "maximum-function-types": "最多允许 { count, plural, 1 {# 个函数类型} other {# 个函数类型} }", - "maximum-timeseries-or-attributes": "最多允许 { count, plural, 1 {# 个 timeseries/属性。} other {# 个 timeseries/属性。} }", - "prev-orig-value-description": "先前的原始值;", - "prev-value-description": "上一次函数调用的结果;", - "settings": "设置", - "time-description": "当前值的时间戳;", - "time-prev-description": "上一个值的时间戳;", - "timeseries": "Timeseries", - "timeseries-or-attributes-required": "实体 Timeseries/属性必填。", - "timeseries-required": "实体 Timeseries 必填。", - "units": "单位符号", - "use-data-post-processing-func": "使用数据后处理功能", - "value-description": "当前值;" - }, - "datasource": { - "add-datasource-prompt": "请添加数据源", - "label": "标签", - "name": "名称", - "type": "数据源类型" - }, - "datetime": { - "date-from": "日期从", - "date-to": "日期到", - "time-from": "时间从", - "time-to": "时间到" - }, - "details": { - "details": "详情", - "edit-json": "编辑JSON", - "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-details": "详情模板:", - "add-clear-alarm-rule": "添加清除条件", - "add-create-alarm-rule": "添加创建条件", - "add-create-alarm-rule-prompt": "请添加创建告警规则", - "advanced-settings": "高级设置", - "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-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": "清除告警规则", - "coap-device-payload-type": "CoAP 设备消息 Payload", - "coap-device-type": "CoAP 设备类型", - "coap-device-type-default": "默认", - "coap-device-type-efento": "Efento NB-IoT", - "coap-device-type-required": "CoAP 设备类型必填。", - "condition": "条件", - "condition-duration": "条件持续时间", - "condition-duration-time-unit": "时间单位", - "condition-duration-time-unit-required": "时间单位必填。", - "condition-duration-value": "持续时间值", - "condition-duration-value-pattern": "持续时间值应为整数。", - "condition-duration-value-range": "持续时间值应在1到2147483647之间。", - "condition-duration-value-required": "持续时间值必填。", - "condition-during": "在 {{during}} 期间", - "condition-repeat-times": "重复 { count, plural, 1 {# 次} other {# 次} }", - "condition-repeating-value": "事件计数", - "condition-repeating-value-pattern": "事件计数应为整数。", - "condition-repeating-value-range": "事件计数应在1到2147483647之间。", - "condition-repeating-value-required": "事件计数值必填。", - "condition-type": "条件类型", - "condition-type-duration": "持续时间", - "condition-type-repeating": "重复", - "condition-type-required": "条件类型必填。", - "condition-type-simple": "简单", - "copy-provision-key": "复制预配置密钥名", - "copy-provision-secret": "复制预配置密钥", - "copyId": "复制设备配置 ID", - "create-alarm-pattern": "创建 {{alarmType}} 告警", - "create-alarm-rules": "创建告警规则", - "create-device-profile": "创建设备配置", - "create-new-device-profile": "创建一个新的!", - "default": "默认", - "default-rule-chain": "默认规则链", - "mobile-dashboard": "移动端仪表板", - "mobile-dashboard-hint": "被移动端应用用作设备详情仪表板", - "delete": "删除设备配置", - "delete-device-profile-text": "请注意:确认后,设备配置和所有相关数据将不可恢复。", - "delete-device-profile-title": "确定要删除 '{{deviceProfileName}}' 设备配置吗?", - "delete-device-profiles-text": "请注意:确认后,所有选定的设备配置将被删除,所有相关数据将不可恢复。", - "delete-device-profiles-title": "确定要删除 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }吗?", - "description": "说明", - "device-profile": "设备配置", - "device-profile-details": "设备配置详情", - "device-profile-file": "设备配置文件", - "device-profile-required": "设备配置必填", - "device-profiles": "设备配置", - "device-provisioning": "设备预配置", - "edit": "编辑设备配置", - "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": "无法导入设备配置:无效的设备配置数据结构。", - "mqtt-device-payload-type": "MQTT 设备 Payload", - "mqtt-device-payload-type-json": "JSON", - "mqtt-device-payload-type-proto": "Protobuf", - "mqtt-device-topic-filters": "MQTT 设备 Topic 筛选器", - "mqtt-device-topic-filters-unique": "MQTT设备 Topic 筛选器必须唯一。", - "mqtt-payload-type-required": "Payload 类型必填。", - "multi-level-wildcards-hint": "[#]可以替换 topic filter 本身,并且必须是 topic 的最后一个符号。例如:# or v1/devices/me/#。", - "name": "名称", - "name-required": "名称是必需的。", - "new-device-profile-name": "设备配置名称", - "new-device-profile-name-required": "设备配置名称必填。", - "no-alarm-rules": "未配置告警规则", - "no-clear-alarm-rule": "未配置明确条件", - "no-create-alarm-rules": "未配置创建条件", - "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": "单级通配符的使用无效", - "profile-configuration": "配置", - "propagate-alarm": "传递警报", - "provision-device-key": "预配置设备密钥名", - "provision-device-key-required": "预配置设备密钥名必填。", - "provision-device-secret": "预配置设备密钥", - "provision-device-secret-required": "预配置设备密钥必填。", - "provision-key-copied-message": "预配置密钥名已复制到剪贴板", - "provision-secret-copied-message": "预配置密钥已复制到剪贴板", - "provision-strategy": "预配置策略", - "provision-strategy-check-pre-provisioned": "检查预配置的设备", - "provision-strategy-created-new": "允许创建新设备", - "provision-strategy-disabled": "禁用", - "provision-strategy-required": "预配置策略必填。", - "rpc-request-proto-schema": "RPC 请求 proto schema", - "rpc-request-proto-schema-hint": "RPC 请求消息应始终包含字段:string method = 1; int32 requestId = 2; 和params = 3的任何数据类型。", - "rpc-request-proto-schema-required": "RPC 请求 proto schema 必填。", - "rpc-response-proto-schema": "RPC 响应 proto schema", - "rpc-response-proto-schema-required": "RPC 响应 proto schema 必填。", - "rpc-response-topic-filter": "RPC响应 Topic 筛选器", - "rpc-response-topic-filter-required": "RPC响应 Topic 筛选器必填。", - "schedule": "启用规则:", - "schedule-any-time": "始终启用", - "schedule-custom": "自定义启用", - "schedule-day": { - "friday": "星期五", - "monday": "星期一", - "saturday": "星期六", - "sunday": "星期日", - "thursday": "星期四", - "tuesday": "星期二", - "wednesday": "星期三" - }, - "schedule-days": "天", - "schedule-days-of-week-required": "每周至少选择一天。", - "schedule-specific-time": "定时启用", - "schedule-time": "时间", - "schedule-time-from": "从", - "schedule-time-to": "到", - "schedule-type": "计划程序类型", - "schedule-type-required": "计划类型必填。", - "search": "查找设备配置", - "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}}' 设为默认值吗?", - "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", - "telemetry-proto-schema-required": "遥测数据 proto schema 必填。", - "telemetry-topic-filter": "遥测数据 topic 筛选器", - "telemetry-topic-filter-required": "遥测数据 topic 筛选器必填。", - "transport-configuration": "传输配置", - "transport-type": "传输方式", - "transport-type-coap-hint": "启用高级 CoAP 传输设置", - "transport-type-default": "默认", - "transport-type-default-hint": "支持基本MQTT、HTTP和CoAP传输", - "transport-type-lwm2m": "LWM2M", - "transport-type-lwm2m-hint": "LWM2M传输类型", - "transport-type-mqtt": "MQTT", - "transport-type-mqtt-hint": "启用高级MQTT传输设置", - "transport-type-required": "传输方式必填。", - "transport-type-snmp-hint": "指定 SNMP 传输配置", - "type": "配置类型", - "type-default": "默认", - "type-required": "配置类型必填。", - "image": "设备配置图片" - }, - "device": { - "access-token": "访问令牌", - "access-token-invalid": "访问令牌长度必须为1到32个字符。", - "access-token-required": "访问令牌必填", - "accessTokenCopiedMessage": "设备访问令牌已复制到剪贴板", - "add": "添加设备", - "add-alias": "添加设备别名", - "add-device-text": "添加新设备", - "alias": "别名", - "alias-required": "设备别名必填。", - "aliases": "设备别名", - "any-device": "任意设备", - "assign-device-to-customer": "将设备分配给客户", - "assign-device-to-customer-text": "请选择要分配给客户的设备", - "assign-device-to-edge-text":"请选择要分配给边缘的设备", - "assign-device-to-edge-title": "将设备分配给边缘", - "assign-devices": "分配设备", - "assign-devices-text": "将 {count,plural,1 {# 个设备} other {# 个设备} }分配给客户", - "assign-new-device": "分配新设备", - "assign-to-customer": "分配给客户", - "assign-to-customer-text": "请选择客户分配设备", - "assignedToCustomer": "分配给客户", - "client-id": "客户端ID", - "client-id-or-user-name-necessary": "客户端ID和/或用户名是必需的", - "client-id-pattern": "包含无效字符。", - "configure-alias": "配置 '{{alias}}' 别名", - "copy-mqtt-authentication": "复制MQTT凭据", - "copyAccessToken": "复制访问令牌", - "copyId": "复制设备ID", - "create-new-alias": "创建一个新的!", - "create-new-key": "创建一个新的!", - "credentials": "凭据", - "credentials-type": "凭据类型", - "delete": "删除设备", - "delete-device-text": "请注意:确认后,设备及其所有相关数据将不可恢复。", - "delete-device-title": "确定要删除设备的{{deviceName}}吗?", - "delete-devices": "删除设备", - "delete-devices-action-title": "删除 {count,plural,1 {# 个设备} other {# 个设备} }", - "delete-devices-text": "请注意:确认后,所有选定的设备将被删除,所有相关数据将不可恢复。", - "delete-devices-title": "确定要删除{count,plural,1 {# 个设备} other {# 个设备} } 吗?", - "description": "说明", - "details": "详情", - "device": "设备", - "device-alias": "设备别名", - "device-configuration": "设备配置", - "device-credentials": "设备凭据", - "device-details": "设备详细信息", - "device-file": "设备文件", - "device-list": "设备列表", - "device-list-empty": "没有被选中的设备", - "device-name-filter-no-device-matched": "未找到以'{{device}}' 开头的设备。", - "device-name-filter-required": "设备名称筛选器必填。", - "device-public": "设备公开", - "device-required": "设备必填", - "device-type": "设备类型", - "device-type-list-empty": "未选择设备类型", - "device-type-required": "设备类型必填。", - "device-types": "设备类型", - "devices": "设备", - "duplicate-alias-error": "找到重复别名 '{{alias}}'。
设备别名必须是唯一的。", - "enter-device-type": "输入设备类型", - "events": "事件", - "idCopiedMessage": "设备ID已复制到剪贴板", - "import": "导入设备", - "is-gateway": "是否网关", - "label": "标签", - "make-private": "私有", - "make-private-device-text": "确认后,设备及其所有数据将被设为私有,不被其他人访问。", - "make-private-device-title": "确定要将设备 '{{deviceName}}' 设为私有吗?", - "make-public": "公开", - "make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。", - "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": "未找到密钥。", - "overwrite-activity-time": "覆盖已连接设备的活动时间", - "password": "密码", - "public": "公开", - "remove-alias": "删除设备别名", - "search": "查找设备", - "secret": "密钥", - "secret-required": "密钥必填", - "select-device": "选择设备", - "select-device-type": "选择设备类型", - "selected-devices": "已选择 { count, plural, 1 {# 个设备} other {# 个设备} }", - "transport-configuration": "传输配置", - "unable-delete-device-alias-text": "设备别名 '{{deviceAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", - "unable-delete-device-alias-title": "无法删除设备别名", - "unassign-device": "取消分配设备", - "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", - "unassign-device-title": "确定要取消分配设备 '{{deviceName}}' 吗?", - "unassign-device-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。", - "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-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。", - "unassign-devices-title": "确定要取消分配 {count,plural,1 {# 个设备} other {# 个设备} } 吗?", - "unassign-from-customer": "取消分配客户", - "use-device-name-filter": "使用筛选器", - "user-name": "用户名", - "user-name-required": "用户名必填。", - "view-credentials": "查看凭据", - "view-devices": "查看设备", - "wizard": { - "add-credentials": "添加凭据", - "customer-to-assign-device": "客户分配设备", - "device-details": "设备详细信息", - "device-wizard": "设备向导", - "existing-device-profile": "选择已有设备配置", - "new-device-profile": "新建设备配置", - "specific-configuration": "指定配置" - } - }, - "dialog": { - "close": "关闭对话框" - }, - "direction": { - "column": "列", - "row": "排" - }, - "edge": { - "add": "增加边缘", - "add-edge-text": "增加新的边缘", - "any-edge": "任何边缘", - "assets": "边缘资产", - "assign-edge-to-customer": "分配边缘给客户", - "assign-edge-to-customer-text": "请选择需要分配给边缘的客户", - "assign-new-edge": "分配新边缘", - "assign-to-customer": "分配给客户", - "assign-to-customer-text": "请选择需要分配给边缘的客户", - "assigned-to-customer": "分配给: {{customerTitle}}", - "assignedToCustomer": "分配给客户", - "copy-edge-key": "复制边缘键", - "copy-edge-secret": "复制边缘密钥", - "copy-id": "复制边缘编号", - "dashboard": "边缘仪表板", - "dashboards": "边缘仪表板", - "delete": "删除边缘", - "delete-edge-text": "当心, 确认后,边缘以及所有关联数据将不可恢复。", - "delete-edge-title": "确定删除边缘 '{{edgeName}}'吗?", - "delete-edges-text": "当心, 确认后,选定的边缘以及所有关联数据将不可恢复。", - "delete-edges-title": "确定删除 { count, plural, 1 {1 个边缘} other {# 个边缘} }吗?", - "deployed": "已部署", - "description": "说明", - "details": "详情", - "devices": "边缘设备", - "downlinks": "下行", - "edge": "边缘", - "edge-assets": "边缘资产", - "edge-dashboards": "边缘仪表板", - "edge-details": "边缘详情", - "edge-devices": "边缘设备", - "edge-entity-views": "边缘实体视图", - "edge-file": "边缘文件", - "edge-instances": "边缘实例", - "edge-key": "边缘键", - "edge-key-copied-message": "边缘键已经被复制到剪切板", - "edge-public": "边缘公开", - "edge-required": "边缘必填。", - "edge-rulechains": "边缘规则链", - "edge-secret": "边缘密钥", - "edge-secret-copied-message": "边缘密钥已经被复制到剪切板", - "edge-type": "边缘类型", - "edge-type-list-empty": "没有选择边缘类型。", - "edge-type-required": "边缘类型必填。", - "edge-types": "边缘类型", - "enter-edge-type": "输入边缘类型", - "entity-id": "实体编号", - "entity-views": "边缘实体视图", - "event-action": "事件行动", - "events": "事件", - "id-copied-message": "边缘编号已经复制到剪切板", - "import": "导入边缘", - "label": "标签", - "label-max-length": "标签长度必须少于256个字符", - "load-entity-error": "加载数据失败,实体已经被删除。", - "make-private": "私有", - "make-private-edge-text": "确认后,边缘及其所有数据将被设为私有,不被其他人访问。", - "make-private-edge-title": "确定要将边缘 '{{edgeName}}' 设为私有吗?", - "make-public": "公开", - "make-public-edge-text": "确认后,边缘及其所有数据将被设为公开并可被其他人访问。", - "make-public-edge-title": "确定要将边缘 '{{edgeName}}' 设为公开吗?", - "management": "边缘管理", - "missing-related-rule-chains-text": "分配给边缘的规则链使用规则节点将消息转发给未分配给当前边缘的规则链。

缺少的规则链列表:
{{missingRuleChains}}", - "missing-related-rule-chains-title": "边缘缺少关联规则链", - "name": "名称", - "name-max-length": "名称长度必须少于256个字符", - "name-required": "名称必填。", - "name-starts-with": "边缘名称前缀", - "no-downlinks-prompt": "未找到下行", - "no-edge-types-matching": "未找到匹配的边缘类型 '{{entitySubtype}}'。", - "no-edges-matching": "未找到匹配的边缘 '{{entity}}'。", - "no-edges-text": "未找到边缘", - "pending": "待定", - "public": "公开", - "rulechain-templates": "规则链模版", - "rulechains": "规则链", - "search": "搜索边缘", - "select-edge-type": "选择边缘类型", - "selected-edges": "{ count, plural, 1 {1 个边缘} other {# 个边缘} } 被选中", - "sync": "同步边缘", - "sync-process-started-successfully": "同步处理开始成功!", - "type-max-length": "类型长度必须少于256个字符", - "unassign-edge-text": "确定后,边缘将被取消分配,并且客户将无法访问。", - "unassign-edge-title": "确定取消分配边缘 '{{edgeName}}' 吗?", - "unassign-edges-text": "确定后,所有选定的边缘将被取消分配,并且客户将无法访问。", - "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-assigned-to-customer": "分配给客户", - "action-type-assigned-to-edge": "分配给边缘", - "action-type-attributes-deleted": "属性删除", - "action-type-attributes-updated": "属性更新", - "action-type-credentials-request": "认证请求", - "action-type-credentials-updated": "认证更新", - "action-type-deleted": "删除", - "action-type-entity-merge-request": "实体合并请求", - "action-type-post-attributes": "推送属性", - "action-type-relation-add-or-update": "关联增加或更新", - "action-type-relation-deleted": "关联删除", - "action-type-rpc-call": "RPC调用", - "action-type-timeseries-updated": "时序更新", - "action-type-unassigned-from-customer": "取消分配客户", - "action-type-unassigned-from-edge": "取消分配边缘", - "action-type-updated": "更新", - "type-admin-settings": "管理员设置", - "type-alarm": "告警", - "type-asset": "资产", - "type-customer": "客户", - "type-dashboard": "仪表板", - "type-device": "设备", - "type-device-profile": "设备概要", - "type-edge": "边缘", - "type-entity-view": "实体视图", - "type-relation": "关联", - "type-rule-chain": "规则链", - "type-rule-chain-metadata": "规则链元数据", - "type-user": "用户", - "type-widgets-bundle": "部件包", - "type-widgets-type": "部件类型" - }, - "entity-field": { - "address": "地址", - "address2": "地址二", - "city": "城市", - "country": "国家", - "created-time": "创建时间", - "email": "电子邮件", - "first-name": "名字", - "label": "标签", - "last-name": "姓氏", - "name": "名称", - "phone": "电话", - "state": "省/州", - "title": "标题", - "type": "类型", - "zip": "邮政编码" - }, - "entity-view": { - "add": "添加实体视图", - "add-alias": "添加实体视图别名", - "add-entity-view-text": "添加新实体视图", - "alias": "别名", - "alias-required": "实体视图别名必填。", - "aliases": "实体视图别名", - "any-entity-view": "任何实体视图", - "assign-entity-view-to-customer": "将实体视图分配给客户", - "assign-entity-view-to-customer-text": "请选择要分配给客户的实体视图", - "assign-entity-view-to-edge": "将实体视图分配给边缘", - "assign-entity-view-to-edge-text": "请选择要分配给边缘的实体视图", - "assign-entity-view-to-edge-title": "将实体视图分配给边缘", - "assign-entity-views": "分配实体视图", - "assign-entity-views-text": "分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 给客户", - "assign-new-entity-view": "分配新实体视图", - "assign-to-customer": "分配给客户", - "assign-to-customer-text": "请选择客户分配实体视图", - "assignedToCustomer": "分配给客户", - "attributes-propagation": "属性传播", - "attributes-propagation-hint": "每次保存或更新这个实体视图时,实体视图将自动从目标实体复制指定的属性。由于性能原因,目标实体属性不会在每次属性更改时传递到实体视图。您可以通过配置\"copy to view\"规则链中的规则节点,并将\"Post attributes\"和\"attributes Updated\"消息链接到新规则节点,从而启用自动传递。", - "client-attributes": "客户端属性", - "client-attributes-placeholder": "客户端属性", - "configure-alias": "配置 '{{alias}}' 别名", - "copyId": "复制实体视图ID", - "create-new-alias": "创建一个新的!", - "create-new-key": "创建一个新的!", - "created-time": "创建时间", - "date-limits": "日期限制", - "delete": "删除实体视图", - "delete-entity-view-text": "请注意:确认后实体视图及其所有相关数据将不可恢复。", - "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 {# 实体视图} }吗?", - "description": "说明", - "details": "详情", - "duplicate-alias-error": "找到重复别名 '{{alias}}'。
实体视图别名必须是唯一的。", - "end-date": "结束日期", - "end-ts": "结束时间", - "enter-entity-view-type": "输入实体视图类型", - "entity-view": "实体视图", - "entity-view-alias": "实体视图别名", - "entity-view-details": "实体视图详细信息", - "entity-view-list": "实体视图列表", - "entity-view-list-empty": "没有被选中的实体视图", - "entity-view-name-filter-no-entity-view-matched": "未找到以'{{entityView}}' 开头的实体视图。", - "entity-view-name-filter-required": "实体视图名称筛选器必填。", - "entity-view-public": "实体视图是公共的", - "entity-view-required": "实体视图必填。", - "entity-view-type": "实体视图类型", - "entity-view-type-list-empty": "实体视图类型未选择。", - "entity-view-type-required": "实体视图类型必填。", - "entity-view-types": "实体视图类型", - "entity-views": "实体视图", - "events": "事件", - "idCopiedMessage": "实体视图ID已复制到剪贴板", - "make-private": "实体视图设为私有", - "make-private-entity-view-text": "确认后,实体视图及其所有数据将被私有化,无法被他人访问。", - "make-private-entity-view-title": "确定要将实体视图 '{{entityViewName}}' 设为私有吗?", - "make-public": "实体视图设为公开", - "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": "未找到密钥。", - "public": "公开", - "remove-alias": "删除实体视图别名", - "search": "查找实体视图", - "select-entity-view": "选择实体视图", - "select-entity-view-type": "选择实体视图类型", - "selected-entity-views": "已选择 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }", - "server-attributes": "服务端属性", - "server-attributes-placeholder": "服务端属性", - "shared-attributes": "共享属性", - "shared-attributes-placeholder": "共享属性", - "start-date": "开始日期", - "start-ts": "开始时间", - "target-entity": "目标实体", - "timeseries": "时间序列", - "timeseries-data": "时间序列数据", - "timeseries-data-hint": "配置目标实体的 Timeseries 数据键,以便实体视图可以访问这些键。此 Timeseries 数据是只读的。", - "timeseries-placeholder": "时间序列", - "unable-entity-view-device-alias-text": "实体视图别名 '{{entityViewAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", - "unable-entity-view-device-alias-title": "无法删除实体视图别名", - "unassign-entity-view": "未分配实体视图", - "unassign-entity-view-text": "确认后,实体视图将未分配,客户无法访问。", - "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-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-text": "确认后,所有选定的实体视图将被分配,客户无法访问。", - "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}}' 开头的告警", - "alias": "别名", - "alias-required": "实体别名必填。", - "aliases": "实体别名", - "all-subtypes": "全部", - "any-entity": "任意实体", - "asset-name-starts-with": "以 '{{prefix}}' 开头的资产", - "columns-to-display": "要显示的列", - "configure-alias": "别名 '{{alias}}' 配置", - "create-new-alias": "创建一个新的!", - "create-new-key": "创建一个新的!", - "customer-name-starts-with": "以 '{{prefix}}' 开头的客户", - "dashboard-name-starts-with": "以 '{{prefix}}' 开头的仪表板", - "details": "实体详情", - "device-name-starts-with": "以 '{{prefix}}' 开头的设备", - "device-profile-name-starts-with": "名称以 '{{prefix}}' 开头的设备配置", - "duplicate-alias-error": "别名 '{{alias}}' 重复。
同一仪表板别名必须唯一。", - "enter-entity-type": "输入实体类型", - "entities": "实体", - "entities-count": "实体数量", - "entity": "实体", - "entity-alias": "实体别名", - "entity-label": "实体标签", - "entity-list": "实体列表", - "entity-list-empty": "没有选择实体。", - "entity-name": "实体名", - "entity-name-filter-no-entity-matched": "未找到以 '{{entity}}' 开头的实体", - "entity-name-filter-required": "实体名筛选器必填。", - "entity-type": "实体类型", - "entity-type-list": "实体类型列表", - "entity-type-list-empty": "没有选择实体类型。", - "entity-types": "实体类型", - "entity-view-name-starts-with": "以 '{{prefix}}' 开头的实体视图", - "key": "键名", - "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-data": "无数据", - "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}}' 开头的规则", - "rulechain-name-starts-with": "规则链前缀名称 '{{prefix}}'", - "rulenode-name-starts-with": "名称以 '{{prefix}}' 开头的规则节点", - "search": "实体检索", - "select-entities": "选择实体", - "selected-entities": "已选择 { count, plural, 1 {# 个实体} other {# 个实体} }", - "tenant-name-starts-with": "以 '{{prefix}}' 开头的租户", - "tenant-profile-name-starts-with": "名称以 '{{prefix}}' 开头的租户配置", - "type": "类型", - "type-alarm": "告警", - "type-alarms": "告警", - "type-api-usage-state": "Api使用状态", - "type-asset": "资产", - "type-assets": "资产", - "type-current-customer": "当前客户", - "type-current-tenant": "当前租户", - "type-current-user": "当前用户", - "type-current-user-owner": "当前用户所有者", - "type-customer": "客户", - "type-customers": "客户", - "type-dashboard": "仪表板", - "type-dashboards": "仪表板", - "type-device": "设备", - "type-device-profile": "设备配置", - "type-device-profiles": "设备配置", - "type-devices": "设备", - "type-entity-view": "实体视图", - "type-entity-views": "实体视图", - "type-plugin": "插件", - "type-plugins": "插件", - "type-required": "实体类型必填。", - "type-rule": "规则", - "type-rulechain": "规则链", - "type-rulechains": "规则链库", - "type-rulenode": "规则节点", - "type-rulenodes": "规则节点", - "type-rules": "规则", - "type-tenant": "租户", - "type-tenant-profile": "租户简介", - "type-tenant-profiles": "租户配置", - "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": "用户筛选器", - "user-name-starts-with": "以 '{{prefix}}' 开头的用户", - "type-edge": "边缘", - "type-edges": "边缘", - "list-of-edges": "{ count, plural, 1 {1 个边缘} other {列表 # 个边缘} }", - "edge-name-starts-with": "以 '{{prefix}}' 开头的边缘" - }, - "error": { - "unable-to-connect": "无法连接到服务器!请检查您的互联网连接。", - "unhandled-error-code": "未处理的错误代码: {{errorCode}}", - "unknown-error": "未知错误" - }, - "event": { - "alarm": "告警", - "body": "整体", - "data": "数据", - "data-type": "数据类型", - "entity-id": "实体ID", - "error": "错误", - "errors-occurred": "错误发生", - "event": "事件", - "event-time": "事件时间", - "event-type": "事件类型", - "events-filter": "事件筛选器", - "failed": "失败", - "has-error": "有错误", - "message-id": "消息ID", - "message-type": "消息类型", - "messages-processed": "消息处理", - "metadata": "元数据", - "method": "方法", - "no-events-prompt": "未找到事件", - "relation-type": "关联类型", - "server": "服务器", - "status": "状态", - "success": "成功", - "type": "类型", - "type-debug-rule-chain": "调试", - "type-debug-rule-node": "调试", - "type-error": "错误", - "type-lc-event": "生命周期事件", - "type-stats": "类型统计", - "all-events": "全部", - "entity-type": "实体类型" - }, - "extension": { - "add": "添加扩展", - "add-attribute": "添加属性", - "add-attribute-request": "添加属性请求", - "add-attribute-update": "添加属性更新", - "add-broker": "添加代理服务器", - "add-config": "添加转换器配置", - "add-connect-request": "添加连接请求", - "add-converter": "添加转换器", - "add-device": "添加服务器", - "add-disconnect-request": "添加断开请求", - "add-map": "添加映射元素", - "add-server-side-rpc-request": "添加服务端RPC请求", - "add-timeseries": "添加 Timeseries", - "anonymous": "匿名", - "attr-json-key-expression": "属性键JSON表达式", - "attr-topic-key-expression": "属性键名Topic表达式", - "attribute-filter": "属性筛选器", - "attribute-key-expression": "属性键名表达式", - "attribute-requests": "属性请求", - "attribute-updates": "属性更新", - "attributes": "属性", - "basic": "Basic", - "brokers": "代理服务器组", - "ca-cert": "CA证书文件 *", - "cert": "证书文件 *", - "client-scope": "客户范围", - "configuration": "配置", - "connect-requests": "连接请求", - "converter-configurations": "转换器的配置", - "converter-id": "转换器序号", - "converter-json": "Json", - "converter-json-parse": "无法解析转换JSON。", - "converter-json-required": "Converter JSON 必填。", - "converter-type": "转换类型", - "converters": "转换器", - "credentials": "证书", - "custom": "定制", - "delete": "删除扩展", - "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": "设备类型表达式", - "disconnect-requests": "断开请求", - "drop-file": "删除文件或单击以选择要上载的文件。", - "edit": "编辑扩展", - "export-extension": "导出扩展", - "export-extensions-configuration": "导出扩展配置", - "extension-id": "扩展ID", - "extension-type": "扩展类型", - "extensions": "扩展", - "field-required": "必填字段", - "file": "扩展文件", - "filter-expression": "筛选条件表达式", - "host": "主机", - "id": "ID", - "import-extension": "导入扩展", - "import-extensions": "导入扩展", - "import-extensions-configuration": "导入扩展配置", - "invalid-file-error": "无效的扩展文件", - "json-name-expression": "设备名称JSON表达式", - "json-parse": "无法解析转换器JSON。", - "json-required": "Transformer JSON 必填。", - "json-type-expression": "设备类型JSON表达式", - "key": "键名", - "mapping": "映射", - "method-filter": "方法筛选器", - "modbus-add-server": "添加 server/slave", - "modbus-add-server-prompt": "请添加 server/slave", - "modbus-attributes-poll-period": "轮询属性周期 (毫秒)", - "modbus-baudrate": "波特率", - "modbus-byte-order": "字节顺序", - "modbus-databits": "数据位", - "modbus-databits-range": "数据位应该在7到8的范围内。", - "modbus-device-name": "设备名称", - "modbus-encoding": "编码", - "modbus-function": "函数", - "modbus-parity": "奇偶性", - "modbus-poll-period": "轮询周期 (毫秒)", - "modbus-poll-period-range": "轮询周期应为正值。", - "modbus-port-name": "串口名称", - "modbus-register-address": "寄存器地址", - "modbus-register-address-range": "寄存器地址应该在0到65535的范围内。", - "modbus-register-bit-index": "位索引", - "modbus-register-bit-index-range": "位索引应该在0到15的范围内。", - "modbus-register-count": "寄存器计数", - "modbus-register-count-range": "寄存器计数应该是一个正值。", - "modbus-rtu-over-tcp": "RTU over TCP", - "modbus-server": "Servers/slaves", - "modbus-stopbits": "停止位", - "modbus-stopbits-range": "停止位应该在1到2的范围内。", - "modbus-tag": "标签", - "modbus-tcp-reconnect": "自动重新连接", - "modbus-timeseries-poll-period": "Timeseries 轮询周期 (毫秒)", - "modbus-transport": "Transport", - "modbus-unit-id": "单位编号", - "modbus-unit-id-range": "单位ID应该在1到247的范围内", - "no-file": "没有选择文件。", - "opc-add-server": "添加服务器", - "opc-add-server-prompt": "请添加服务器", - "opc-application-name": "应用名称", - "opc-application-uri": "应用URI", - "opc-device-name-pattern": "设备名称模式", - "opc-device-node-pattern": "设备节点模式", - "opc-identity": "身份", - "opc-keystore": "密钥库", - "opc-keystore-alias": "别名", - "opc-keystore-key-password": "密钥密码", - "opc-keystore-location": "位置 *", - "opc-keystore-password": "密码", - "opc-keystore-type": "类型", - "opc-scan-period-in-seconds": "秒级扫描周期", - "opc-security": "安全性", - "opc-server": "服务器组", - "opc-type": "类型", - "password": "密码", - "pem": "PEM", - "port": "端口", - "port-range": "端口应该在1到65535的范围内。", - "private-key": "私钥文件 *", - "request-id-expression": "请求ID表达式", - "request-id-json-expression": "请求ID JSON表达式", - "request-id-topic-expression": "请求ID主题表达式", - "request-topic-expression": "请求主题表达式", - "response-timeout": "响应超时(毫秒)", - "response-topic-expression": "响应主题表达式", - "retry-interval": "重试间隔(毫秒)", - "selected-extensions": "已选择 { count, plural, 1 {# 个扩展} other {# 个扩展} }", - "server-side-rpc": "服务端RPC", - "ssl": "SSL", - "sync": { - "last-sync-time": "最后同步时间", - "not-available": "无法使用", - "not-sync": "不同步", - "status": "状态", - "sync": "同步" - }, - "timeout": "超时时间(毫秒)", - "timeseries": "Timeseries", - "to-double": "加倍", - "token": "安全令牌", - "topic": "主题", - "topic-expression": "主题表达", - "topic-filter": "Topic筛选器", - "topic-name-expression": "设备名称主题表达式", - "topic-type-expression": "设备类型主题表达式", - "transformer": "转换器", - "transformer-json": "JSON *", - "type": "类型", - "unique-id-required": "当前扩展ID已经存在。", - "username": "用户名", - "value": "价值", - "value-expression": "值表达式" - }, - "filter": { - "add": "添加筛选器", - "add-complex": "添加复合", - "add-complex-filter": "添加复合筛选器", - "add-filter": "添加筛选器", - "add-filter-prompt": "请添加筛选器", - "add-key-filter": "添加键名筛选器", - "autogenerated-label": "自动生成标签", - "complex-filter": "复合筛选器", - "create-new-filter": "请新增!", - "current-customer": "当前客户", - "current-device": "当前设备", - "current-tenant": "当前租户", - "current-user": "当前用户", - "date": "日期", - "default-value": "默认值", - "display-label": "要显示的标签", - "duplicate-filter": "同名筛选器已存在。", - "duplicate-filter-error": "找到重复的筛选器 '{{filter}}'。
筛选器在仪表板中必须是唯一的。", - "dynamic-source-type": "动态源类型", - "edit": "编辑筛选器", - "edit-complex-filter": "编辑复合筛选器", - "edit-filter-user-params": "编辑筛选器谓词用户参数", - "edit-key-filter": "编辑键名筛选器", - "editable": "可编辑", - "filter": "筛选器", - "filter-required": "筛选器必填。", - "filter-user-params": "过滤谓词用户参数", - "filters": "筛选器", - "ignore-case": "忽略大小写", - "key-filter": "键名筛选器", - "key-filters": "键名筛选器", - "key-name": "键名", - "key-name-required": "键名必填。", - "key-type": { - "attribute": "属性", - "constant": "常量", - "entity-field": "实体", - "key-type": "键类型", - "timeseries": "Timeseries" - }, - "key-value-type-change-message": "如果您确认新的值类型,所有输入的键过滤器将被删除。", - "key-value-type-change-title": "确定要更改键值类型吗?", - "missing-key-filters-error": "筛选器 '{{filter}}' 的键名筛选条件缺失。", - "name": "筛选器名称", - "name-required": "筛选器名称必填。", - "no-dynamic-value": "无动态值", - "no-filter-matching": "未找到 '{{filter}}' 。", - "no-filter-text": "未指定筛选器", - "no-filters": "未配置筛选器", - "no-filters-found": "未找到筛选器。", - "no-key-filters": "未配置键名筛选器", - "operation": { - "and": "和", - "contains": "包含", - "ends-with": "结束于", - "equal": "等于", - "greater": "大于", - "greater-or-equal": "大于或等于", - "less": "小于", - "less-or-equal": "小于或等于", - "not-contains": "不包含", - "not-equal": "不等于", - "operation": "操作", - "or": "或", - "starts-with": "开始于" - }, - "order-priority": "字段顺序优先级", - "preview": "筛选器预览", - "remove-filter": "删除筛选器", - "remove-key-filter": "删除键名筛选器", - "source-attribute": "源属性", - "switch-to-default-value": "切换到默认值", - "switch-to-dynamic-value": "切换到动态值", - "time": "时间", - "unable-delete-filter-text": "无法删除筛选器 '{{filter}}' ,因为它由以下小部件使用:
{{widgetsList}}", - "unable-delete-filter-title": "无法删除筛选器", - "user-parameters": "用户参数", - "value": "值", - "value-type-required": "键值类型是必需的。", - "value-type": { - "boolean": "布尔值", - "date-time": "日期时间", - "numeric": "数字", - "string": "字符串", - "value-type": "值类型" - } - }, - "fullscreen": { - "exit": "退出全屏", - "expand": "展开到全屏", - "fullscreen": "全屏", - "toggle": "切换全屏模式" - }, - "function": { - "function": "函数" - }, - "gateway": { - "add-entry": "添加配置", - "connector-add": "添加新连接器", - "connector-enabled": "启用连接器", - "connector-name": "连接器名称", - "connector-name-required": "Connector 名称必填。", - "connector-type": "连接器类型", - "connector-type-required": "Connector 类型必填。", - "connectors": "连接器配置", - "create-new-gateway": "创建新网关", - "create-new-gateway-text": "确定要创建名为 '{{gatewayName}}' 的新网关?", - "delete": "删除配置", - "download-tip": "下载配置", - "gateway": "网关", - "gateway-exists": "同名设备已存在。", - "gateway-name": "网关名称", - "gateway-name-required": "网关名称必填。", - "gateway-saved": "已成功保存网关配置。", - "json-parse": "无效的JSON。", - "json-required": "字段不能为空。", - "no-connectors": "无连接器", - "no-data": "没有配置", - "no-gateway-found": "未找到网关。", - "no-gateway-matching": "未找到 '{{item}}' 。", - "path-logs": "日志文件的路径", - "path-logs-required": "路径是必需的。", - "remote": "远程配置", - "remote-logging-level": "日志记录级别", - "remove-entry": "删除配置", - "save-tip": "保存配置", - "security-type": "安全类型", - "security-types": { - "access-token": "访问令牌", - "tls": "TLS" - }, - "storage": "存储", - "storage-max-file-records": "文件中的最大记录数", - "storage-max-files": "最大文件数", - "storage-max-files-min": "最小值为1。", - "storage-max-files-pattern": "数字无效。", - "storage-max-files-required": "数字是必需的。", - "storage-max-records": "存储中的最大记录数", - "storage-max-records-min": "最小记录数为1。", - "storage-max-records-pattern": "数字无效。", - "storage-max-records-required": "最大记录项必填。", - "storage-pack-size": "最大事件包大小", - "storage-pack-size-min": "最小值为1。", - "storage-pack-size-pattern": "数字无效。", - "storage-pack-size-required": "最大事件包大小必填。", - "storage-path": "存储路径", - "storage-path-required": "存储路径必填。", - "storage-type": "存储类型", - "storage-types": { - "file-storage": "文件存储", - "memory-storage": "内存存储" - }, - "thingsboard": "ThingsBoard", - "thingsboard-host": "ThingsBoard主机", - "thingsboard-host-required": "主机必填。", - "thingsboard-port": "ThingsBoard端口", - "thingsboard-port-max": "最大端口号为65535。", - "thingsboard-port-min": "最小端口号为1。", - "thingsboard-port-pattern": "端口无效。", - "thingsboard-port-required": "端口必填。", - "tidy": "整洁", - "tidy-tip": "整理配置JSON", - "title-connectors-json": "连接器 {{typeName}} 配置", - "tls-path-ca-certificate": "网关上CA证书的路径", - "tls-path-client-certificate": "网关上客户端证书的路径", - "tls-path-private-key": "网关上私钥的路径", - "toggle-fullscreen": "切换全屏", - "transformer-json-config": "配置JSON*", - "update-config": "添加/更新配置JSON" - }, - "grid": { - "add-item-text": "添加新项目", - "delete-item": "删除项目", - "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 {# 项} }吗?", - "item-details": "项目详细信息", - "no-items-text": "未找到项目", - "scroll-to-top": "滚动到顶部" - }, - "help": { - "goto-help-page": "转到帮助页面" - }, - "home": { - "avatar": "头像", - "home": "首页", - "logout": "注销", - "menu": "菜单", - "open-user-menu": "打开用户菜单", - "profile": "属性" - }, - "icon": { - "icon": "图标", - "material-icons": "素材图标", - "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": "属性/遥测键", - "column-title": "标题", - "column-type": { - "access-token": "访问令牌", - "client-attribute": "客户端属性", - "column-type": "列类型", - "description": "说明", - "entity-field": "实体字段", - "isgateway": "是否网关", - "label": "标签", - "name": "名称", - "routing-key": "边缘键", - "secret": "边缘密钥", - "server-attribute": "服务器属性", - "shared-attribute": "共享属性", - "timeseries": "Timeseries", - "type": "类型" - }, - "column-value": "数值", - "csv-delimiter": "CSV分隔符", - "csv-first-line-header": "第一行包含列名", - "csv-update-data": "更新属性/遥测", - "drop-file": "拖动一个JSON文件或者单击以选择要上传的文件。", - "drop-file-csv": "拖动一个CSV文件或单击以选择要上载的文件。", - "import-csv-invalid-format-error": "文件格式无效。行: '{{line}}'", - "import-csv-number-columns-error": "一个文件至少应该包含两列", - "message": { - "create-entities": "{{count}} 个新实体已成功创建。", - "error-entities": "创建 {{count}} 个实体时出错。", - "update-entities": "{{count}} 个实体已成功更新。" - }, - "no-file": "没有选择文件", - "stepper-text": { - "column-type": "选择列类型", - "configuration": "导入配置", - "creat-entities": "创建新实体", - "done": "完成", - "select-file": "选择一个文件" - } - }, - "item": { - "selected": "选择" - }, - "js-func": { - "mini": "迷你", - "no-return-error": "函数必须返回值!", - "return-type-mismatch": "函数必须返回 '{{type}}' 类型的值!", - "tidy": "整洁" - }, - "key-val": { - "add-entry": "增加条目", - "key": "键名", - "no-data": "没有条目", - "remove-entry": "删除条目", - "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": "语言" - }, - "layout": { - "color": "颜色", - "layout": "布局", - "main": "主体", - "manage": "布局管理", - "right": "右侧", - "select": "选择目标布局", - "settings": "布局设置" - }, - "legend": { - "avg": "平均值", - "comparison-time-ago": { - "days": "(一天前)", - "months": "(一个月前)", - "weeks": "(一周前)", - "years": "(一年前)" - }, - "direction": "图例方向", - "max": "最大值", - "min": "最小值", - "position": "图例位置", - "settings": "图例设置", - "show-avg": "显示平均值", - "show-max": "显示最大值", - "show-min": "显示最小值", - "show-total": "显示总数", - "sort-legend": "在图例中排序数据键", - "total": "总数" - }, - "login": { - "create-password": "创建密码", - "email": "电子邮件", - "error": "登录错误", - "expired-password-reset-message": "您的凭据已过期!请创建新密码。", - "forgot-password": "忘记密码?", - "login": "登录", - "login-with": "使用 {{name}} 登录", - "new-password": "新密码", - "new-password-again": "再次输入新密码", - "or": "或", - "password-again": "再次输入密码", - "password-link-sent-message": "密码重置链接已成功发送!", - "password-reset": "密码重置", - "passwords-mismatch-error": "输入的密码必须相同!", - "remember-me": "记住我", - "request-password-reset": "请求密码重置", - "reset-password": "重置密码", - "sign-in": "登录 ", - "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": "添加包", - "assign-firmware": "分配的固件", - "assign-firmware-required": "分配的固件必填", - "assign-software": "分配的软件", - "assign-software-required": "分配的软件必填", - "auto-generate-checksum": "自动生成校验和", - "checksum": "校验和", - "checksum-hint": "如果校验和为空,会自动生成", - "checksum-algorithm": "校验和算法", - "checksum-copied-message": "包校验和已复制到剪贴板", - "change-firmware": "固件的更改可能会导致 { count, plural, 1 {1 个设备} other {# 个设备} } 的更新。", - "change-software": "软件的更改可能会导致 { count, plural, 1 {1 个设备} other {# 个设备} } 的更新。", - "chose-compatible-device-profile": "上传的包仅适用于具有所选配置文件的设备。", - "chose-firmware-distributed-device": "选择将分发到设备的固件", - "chose-software-distributed-device": "选择将分发到设备的软件", - "content-type": "内容类型", - "copy-checksum": "复制校验和", - "copy-direct-url": "复制直接URL", - "copyId": "复制包ID", - "copied": "已复制!", - "delete": "删除包", - "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必填", - "download": "下载包", - "drop-file": "拖放打包文件或点击选择要上传的文件。", - "file-name": "文件名", - "file-size": "文件大小", - "file-size-bytes": "文件大小(以字节为单位)", - "idCopiedMessage": "包ID已被复制到剪贴板", - "no-firmware-matching": "未找到与'{{entity}}'匹配的兼容固件OTA升级包。", - "no-firmware-text": "没有提供兼容的固件OTA升级包。", - "no-packages-text": "未找到包", - "no-software-matching": "未找到匹配 '{{entity}}' 的兼容软件OTA升级包。", - "no-software-text": "没有提供兼容的软件OTA升级包。", - "ota-update": "OTA 升级", - "ota-update-details": "OTA 升级详情", - "ota-updates": "OTA 升级", - "package-type": "包类型", - "packages-repository": "包仓库", - "search": "搜索包", - "selected-package": "{ count, plural, 1 {1 个包} other {# 个包} } 选中", - "title": "标题", - "title-required": "标题必填。", - "types": { - "firmware": "固件", - "software": "软件" - }, - "upload-binary-file": "上传二进制文件", - "use-external-url": "使用外部URL", - "version": "版本", - "version-required": "版本必填。", - "version-tag": "版本标签", - "version-tag-hint": "自定义标签应与您设备报告的软件包版本相匹配。", - "warning-after-save-no-edit": "上传包后,您将无法修改标题、版本、设备配置文件和包类型。" - }, - "position": { - "bottom": "底部", - "left": "左侧", - "right": "右侧", - "top": "顶部" - }, - "profile": { - "change-password": "更改密码", - "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 }}", + "access": { + "unauthorized": "未授权", + "unauthorized-access": "未授权访问", + "unauthorized-access-text": "您需要登陆才能访问这个资源!", + "access-forbidden": "禁止访问", + "access-forbidden-text": "您没有访问此位置的权限
如果您仍希望访问此位置,请尝试使用其他用户登录。", + "refresh-token-expired": "会话已过期", + "refresh-token-failed": "无法刷新会话", + "permission-denied": "权限被拒绝", + "permission-denied-text": "您没有执行此操作的权限!" + }, + "action": { + "activate": "激活", + "suspend": "暂停", + "save": "保存", + "saveAs": "另存为", + "cancel": "取消", + "ok": "确定", + "delete": "删除", + "add": "添加", + "yes": "是", + "no": "否", + "update": "更新", + "remove": "移除", + "select": "选择", + "search": "查询", + "clear-search": "清除查询", + "assign": "分配", + "unassign": "取消分配", + "share": "分享", + "make-private": "私有", + "apply": "应用", + "apply-changes": "应用更改", + "edit-mode": "编辑模式", + "enter-edit-mode": "进入编辑模式", + "decline-changes": "撤销更改", + "close": "关闭", + "back": "后退", + "run": "运行", + "sign-in": "登录!", + "edit": "编辑", + "view": "查看", + "create": "创建", + "drag": "拖拽", + "refresh": "刷新", + "undo": "撤销", + "copy": "复制", + "paste": "粘贴", + "copy-reference": "复制引用", + "paste-reference": "粘贴引用", + "import": "导入", + "export": "导出", + "share-via": "通过{{provider}}分享", + "continue": "继续", + "discard-changes": "放弃更改", + "download": "下载", + "next-with-label": "下一个:{{label}}", + "read-more": "阅读更多", + "hide": "隐藏", + "done": "完毕", + "print": "打印", + "restore": "恢复", + "confirm": "确认" + }, + "aggregation": { + "aggregation": "聚合", + "function": "数据聚合功能", + "limit": "限制数", + "group-interval": "分组间隔", + "min": "最小值", + "max": "最大值", + "avg": "平均值", + "sum": "求和", + "count": "计数", + "none": "无" + }, + "admin": { + "general": "基本设置", + "general-settings": "基本设置", + "home-settings": "首页设置", + "outgoing-mail": "发送邮件", + "outgoing-mail-settings": "发送邮件设置", + "system-settings": "系统设置", + "test-mail-sent": "测试邮件发送成功!", + "base-url": "基本URL", + "base-url-required": "基本URL必填。", + "prohibit-different-url": "禁止从客户端请求头中使用主机名", + "prohibit-different-url-hint": "应为生产环境启用此设置。禁用时可能会导致安全问题", + "mail-from": "邮件来自", + "mail-from-required": "邮件发件人必填。", + "smtp-protocol": "SMTP协议", + "smtp-host": "SMTP主机", + "smtp-host-required": "SMTP主机必填。", + "smtp-port": "SMTP端口", + "smtp-port-required": "您必须提供一个smtp端口。", + "smtp-port-invalid": "这看起来不是有效的smtp端口。", + "timeout-msec": "超时时间(毫秒)", + "timeout-required": "超时必填。", + "timeout-invalid": "这看起来不像有效的超时值。", + "enable-tls": "启用TLS", + "tls-version": "TLS版本", + "enable-proxy": "启用代理", + "proxy-host": "代理主机", + "proxy-host-required": "代理主机必填。", + "proxy-port": "代理端口", + "proxy-port-required": "代理端口必填。", + "proxy-port-range": "代理端口应在1到65535之间。", + "proxy-user": "代理用户", + "proxy-password": "代理密码", + "change-password": "Change password", + "send-test-mail": "发送测试邮件", + "sms-provider": "SMS 服务商", + "sms-provider-settings": "SMS 服务商设置", + "sms-provider-type": "SMS 服务商类型", + "sms-provider-type-required": "SMS 服务商类型必填。", + "sms-provider-type-aws-sns": "亚马逊社交网站", + "sms-provider-type-twilio": "Twilio", + "sms-provider-type-smpp": "SMPP", + "aws-access-key-id": "AWS访问密钥ID", + "aws-access-key-id-required": "需要访问AWS密钥ID", + "aws-secret-access-key": "AWS秘密访问密钥", + "aws-secret-access-key-required": "AWS 访问密钥必填", + "aws-region": "AWS地区", + "aws-region-required": "AWS 区域必填", + "number-from": "发送方电话号码", + "number-from-required": "发送方电话号码必填。", + "number-to": "电话号码至", + "number-to-required": "电话号码必填。", + "phone-number-hint": "E.164格式的手机号码,例如+19995550123", + "phone-number-hint-twilio": "Phone Number in E.164 format/Phone Number's SID/Messaging Service SID, ex. +19995550123/PNXXX/MGXXX", + "phone-number-pattern": "手机号码无效。应为E.164格式,例如+19995550123。", + "phone-number-pattern-twilio": "Invalid phone number. Should be in E.164 format/Phone Number's SID/Messaging Service SID, ex. +19995550123/PNXXX/MGXXX.", + "sms-message": "短信", + "sms-message-required": "短消息内容必填。", + "sms-message-max-length": "短信长度不能超过1600个字符", + "twilio-account-sid": "Twilio帐户SID", + "twilio-account-sid-required": "Twilio 帐户的 SID 必填", + "twilio-account-token": "Twilio帐户令牌", + "twilio-account-token-required": "Twilio 帐户令牌必填", + "send-test-sms": "发送测试短信", + "test-sms-sent": "测试短信发送成功!", + "security-settings": "安全设置", + "password-policy": "密码策略", + "minimum-password-length": "最小密码长度", + "minimum-password-length-required": "最小密码长度必填", + "minimum-password-length-range": "最小密码长度应在5到50之间", + "minimum-uppercase-letters": "最少大写字母位数", + "minimum-uppercase-letters-range": "最少大写字母位数不能为负数", + "minimum-lowercase-letters": "最少小写字母位数", + "minimum-lowercase-letters-range": "最少小写字母位数不能为负数", + "minimum-digits": "最少数字位数", + "minimum-digits-range": "最少数字位数不能为负数", + "minimum-special-characters": "最少特殊字符位数", + "minimum-special-characters-range": "最少特殊字符位数不能为负数", + "password-expiration-period-days": "密码有效期(天)", + "password-expiration-period-days-range": "密码过期期限(天)不能为负", + "password-reuse-frequency-days": "密码重用频率(天)", + "password-reuse-frequency-days-range": "天内密码重用频率不能为负", + "allow-whitespace": "Allow whitespace", + "general-policy": "基本策略", + "max-failed-login-attempts": "登录失败之前的最大登录尝试次数", + "minimum-max-failed-login-attempts-range": "登录失败次数不能为负数", + "user-lockout-notification-email": "如果用户帐户锁定,请发送通知到电子邮件", + "domain-name": "域名", + "domain-name-unique": "域名和协议必须是唯一的。", + "domain-name-max-length": "域名长度不能大于256", + "error-verification-url": "域名不应包含符号 “/” 和 “:”。例:thingsboard.io", + "oauth2": { + "access-token-uri": "访问令牌URI", + "access-token-uri-required": "访问令牌 URI 必填。", + "activate-user": "激活用户", + "add-domain": "添加域", + "delete-domain": "删除域", + "add-provider": "添加 Provider", + "delete-provider": "删除 Provider", + "allow-user-creation": "允许用户创建", + "always-fullscreen": "始终全屏", + "authorization-uri": "授权URI", + "authorization-uri-required": "授权 URI 必填。", + "client-authentication-method": "客户端身份验证方法", + "client-id": "客户端ID", + "client-id-required": "客户端 ID 必填。", + "client-id-max-length": "客户端ID长度不能大于256", + "client-secret": "客户机密", + "client-secret-required": "需要客户端密码。", + "client-secret-max-length": "客户端密钥长度不能大于2049", + "custom-setting": "自定义设置", + "customer-name-pattern": "客户名称模式", + "customer-name-pattern-max-length": "客户名称模式长度不能大于256", + "default-dashboard-name": "默认仪表板名称", + "default-dashboard-name-max-length": "默认仪表板名称长度不能大于256", + "delete-domain-text": "请注意:确认后,域和所有 provider data 将不可恢复。", + "delete-domain-title": "确定要删除域 '{{domainName}}' 的设置吗?", + "delete-registration-text": "请注意:确认后 provider data 将不可恢复。", + "delete-registration-title": "确定要删除 provider '{{name}}' 吗?", + "email-attribute-key": "电子邮件属性键", + "email-attribute-key-required": "电子邮件属性密钥必填。", + "email-attribute-key-max-length": "电子邮件属性键长度不能大于32", + "first-name-attribute-key": "名字属性键", + "first-name-attribute-key-max-length": "名字属性键长度不能大于32", + "general": "基本设置", + "jwk-set-uri": "JSON Web Key URI", + "last-name-attribute-key": "姓氏属性键", + "last-name-attribute-key-max-length": "姓氏属性键长度不能大于32", + "login-button-icon": "登录按钮图标", + "login-button-label": "Provider 标签", + "login-button-label-placeholder": "使用 $(Provider label) 登录", + "login-button-label-required": "标签必填。", + "login-provider": "Login provider", + "mapper": "Mapper", + "new-domain": "新建域", + "oauth2": "OAuth2", + "password-max-length": "密码长度不能大于256", + "redirect-uri-template": "重定向URI模板", + "copy-redirect-uri": "复制重定向URI", + "registration-id": "注册ID", + "registration-id-required": "注册 ID 必填。", + "registration-id-unique": "系统的注册ID必须是唯一的。", + "scope": "范围", + "scope-required": "范围必填。", + "tenant-name-pattern": "租户名称模式", + "tenant-name-pattern-required": "租户名称模式必填。", + "tenant-name-pattern-max-length": "租户名称模式长度不能大于256", + "tenant-name-strategy": "租户名称策略", + "type": "映射器类型", + "uri-pattern-error": "无效的URI格式。", + "url": "统一资源定位地址", + "url-pattern": "无效的URL格式。", + "url-required": "URL 必填。", + "url-max-length": "URL地址长度不能大于256", + "user-info-uri": "用户信息URI", + "user-info-uri-required": "用户信息 URI 必填。", + "username-max-length": "用户名称长度不能大于256", + "user-name-attribute-name": "用户名属性键", + "user-name-attribute-name-required": "用户名属性密钥必填", + "protocol": "协议", + "domain-schema-http": "HTTP", + "domain-schema-https": "HTTPS", + "domain-schema-mixed": "HTTP+HTTPS", + "enable": "启用OAuth2设置", + "domains": "域名", + "mobile-apps": "移动端", + "no-mobile-apps": "无移动端应用配置", + "mobile-package": "应用程包", + "mobile-package-placeholder": "例如: my.example.app", + "mobile-package-hint": "Android:应用程序ID,iOS:产品标识符", + "mobile-package-unique": "应用程序包必须是唯一的。", + "mobile-app-secret": "应用程序密钥", + "invalid-mobile-app-secret": "应用程序密钥必须包含字母,数字,字符并且长度必须在16到2048个字符。", + "copy-mobile-app-secret": "复制应用程序密钥", + "add-mobile-app": "添加应用程序", + "delete-mobile-app": "删除应用程序", + "providers": "提供程序", + "platform-web": "Web", + "platform-android": "Android", + "platform-ios": "iOS", + "all-platforms": "所有平台", + "allowed-platforms": "允许平台" + }, + "smpp-provider": { + "smpp-version": "SMPP版本", + "smpp-host": "SMPP主机", + "smpp-host-required": "SMPP主机必填", + "smpp-port": "SMPP端口", + "smpp-port-required": "SMPP端口必填", + "system-id": "系统ID", + "system-id-required": "系统ID必填", + "password": "密码", + "password-required": "密码必填", + "type-settings": "类型设置", + "source-settings": "源设置", + "destination-settings": "目的地", + "additional-settings": "其他设置", + "system-type": "系统类型", + "bind-type": "绑定类型", + "service-type": "服务类型", + "source-address": "源地址", + "source-ton": "源TON", + "source-npi": "源NPI", + "destination-ton": "目的地TON号码", + "destination-npi": "目的地NPI号码", + "address-range": "地址范围", + "coding-scheme": "编码方案", + "bind-type-tx": "发送者", + "bind-type-rx": "接收者", + "bind-type-trx": "收发器", + "ton-unknown": "未知", + "ton-international": "International", + "ton-national": "National", + "ton-network-specific": "Network Specific", + "ton-subscriber-number": "Subscriber Number", + "ton-alphanumeric": "Alphanumeric", + "ton-abbreviated": "Abbreviated", + "npi-unknown": "0 - Unknown", + "npi-isdn": "1 - ISDN/telephone numbering plan (E163/E164)", + "npi-data-numbering-plan": "3 - Data numbering plan (X.121)", + "npi-telex-numbering-plan": "4 - Telex numbering plan (F.69)", + "npi-land-mobile": "6 - Land Mobile (E.212)", + "npi-national-numbering-plan": "8 - National numbering plan", + "npi-private-numbering-plan": "9 - Private numbering plan", + "npi-ermes-numbering-plan": "10 - ERMES numbering plan (ETSI DE/PS 3 01-3)", + "npi-internet": "13 - Internet (IP)", + "npi-wap-client-id": "18 - WAP Client Id (to be defined by WAP Forum)", + "scheme-smsc": "0 - SMSC Default Alphabet (ASCII for short and long code and to GSM for toll-free)", + "scheme-ia5": "1 - IA5 (ASCII for short and long code, Latin 9 for toll-free (ISO-8859-9))", + "scheme-octet-unspecified-2": "2 - Octet Unspecified (8-bit binary)", + "scheme-latin-1": "3 - Latin 1 (ISO-8859-1)", + "scheme-octet-unspecified-4": "4 - Octet Unspecified (8-bit binary)", + "scheme-jis": "5 - JIS (X 0208-1990)", + "scheme-cyrillic": "6 - Cyrillic (ISO-8859-5)", + "scheme-latin-hebrew": "7 - Latin/Hebrew (ISO-8859-8)", + "scheme-ucs-utf": "8 - UCS2/UTF-16 (ISO/IEC-10646)", + "scheme-pictogram-encoding": "9 - Pictogram Encoding", + "scheme-music-codes": "10 - Music Codes (ISO-2022-JP)", + "scheme-extended-kanji-jis": "13 - Extended Kanji JIS (X 0212-1990)", + "scheme-korean-graphic-character-set": "14 - Korean Graphic Character Set (KS C 5601/KS X 1001)" + }, + "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": "默认分支名称", + "repository-read-only": "Read-only", + "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": "时间必填。" + } + }, + "alarm": { + "alarm": "告警", + "alarms": "告警", + "select-alarm": "选择告警", + "no-alarms-matching": "未找到匹配 '{{entity}}' 的告警", + "alarm-required": "告警必填", + "alarm-status": "告警状态", + "alarm-status-list": "告警状态列表", + "any-status": "任何状态", + "search-status": { + "ANY": "所有", + "ACTIVE": "激活", + "CLEARED": "已清除", + "ACK": "已确认", + "UNACK": "未确认" + }, + "display-status": { + "ACTIVE_UNACK": "激活未确认", + "ACTIVE_ACK": "激活已确认", + "CLEARED_UNACK": "清除未确认", + "CLEARED_ACK": "清除已确认" + }, + "no-alarms-prompt": "未发现告警", + "created-time": "创建时间", + "type": "类型", + "severity": "严重程度", + "originator": "发起者", + "originator-type": "发起者类型", + "details": "详情", + "status": "状态", + "alarm-details": "告警详细信息", + "start-time": "开始时间", + "end-time": "结束时间", + "ack-time": "确认时间", + "clear-time": "清除时间", + "alarm-severity-list": "警报严重性列表", + "any-severity": "任何严重程度", + "severity-critical": "危险", + "severity-major": "重要", + "severity-minor": "次要", + "severity-warning": "警告", + "severity-indeterminate": "不确定", + "acknowledge": "应答", + "clear": "清除", + "search": "查找告警", + "selected-alarms": "已选择 { count, plural, 1 {# 个告警} other {# 个告警} }", + "no-data": "无数据显示", + "polling-interval": "告警轮询间隔(秒)", + "polling-interval-required": "告警轮询间隔必填。", + "min-polling-interval-message": "轮询间隔至少是1秒。", + "aknowledge-alarms-title": "确认 { count, plural, 1 {# 个告警} other {# 个告警} }", + "aknowledge-alarms-text": "确定要确认 { count, plural, 1 {# 个告警} other {# 个告警} }吗?", + "aknowledge-alarm-title": "确认告警", + "aknowledge-alarm-text": "确定要确认告警吗?", + "clear-alarms-title": "清除 { count, plural, 1 {# 个告警} other {# 个告警} }", + "clear-alarms-text": "确定要清除 { count, plural, 1 {# 个告警} other {# 个告警} }?", + "clear-alarm-title": "清除警报", + "clear-alarm-text": "确定要清除警报吗?", + "alarm-status-filter": "告警状态筛选器", + "alarm-filter": "告警筛选器", + "max-count-load": "要加载的最大告警数(0-无限制)", + "max-count-load-required": "加载的最大告警数必填。", + "max-count-load-error-min": "最小值为0。", + "fetch-size": "获取大小", + "fetch-size-required": "Fetch size 必填。", + "fetch-size-error-min": "最小值为10。", + "alarm-type-list": "告警类型列表", + "any-type": "任何类型", + "search-propagated-alarms": "检索已传递的警报" + }, + "alias": { + "add": "添加别名", + "edit": "编辑别名", + "name": "别名", + "name-required": "别名必填", + "duplicate-alias": "别名已经存在。", + "filter-type-single-entity": "单个实体", + "filter-type-entity-list": "实体列表", + "filter-type-entity-name": "实体名称", + "filter-type-entity-type": "Entity type", + "filter-type-state-entity": "仪表板状态实体", + "filter-type-state-entity-description": "仪表板实体令牌状态参数", + "filter-type-asset-type": "资产类型", + "filter-type-asset-type-description": "类型为 '{{assetType}}' 的资产", + "filter-type-asset-type-and-name-description": "类型为 '{{assetType}}' 且以 '{{prefix}}' 开头的资产", + "filter-type-device-type": "设备类型", + "filter-type-device-type-description": "类型为 '{{deviceType}}' 的设备", + "filter-type-device-type-and-name-description": "类型为 '{{deviceType}}' 且以 '{{prefix}}' 开头的设备", + "filter-type-entity-view-type": "实体视图类型", + "filter-type-entity-view-type-description": "类型为 '{{entityView}}' 的实体视图", + "filter-type-entity-view-type-and-name-description": "类型为 {{entityView}}' 且以 '{{prefix}}' 开头的实体视图", + "filter-type-edge-type": "边缘类型", + "filter-type-edge-type-description": "类型为 '{{edgeType}}' 的边缘", + "filter-type-edge-type-and-name-description": "类型为 '{{edgeType}}' 且以 '{{prefix}}' 开头的边缘", + "filter-type-relations-query": "关联查询", + "filter-type-relations-query-description": "具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的 {{entities}} ", + "filter-type-asset-search-query": "资产搜索查询", + "filter-type-asset-search-query-description": "类型为 {{assetTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的资产", + "filter-type-device-search-query": "设备搜索查询", + "filter-type-device-search-query-description": "类型为 {{deviceTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的设备", + "filter-type-entity-view-search-query": "实体视图搜索查询", + "filter-type-entity-view-search-query-description": "类型为 {{entityViewTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的实体视图", + "filter-type-apiUsageState": "Api使用状态", + "filter-type-edge-search-query": "边缘搜索查询", + "filter-type-edge-search-query-description": "类型为 {{edgeTypes}} 且具有 {{relationType}} 关联 {{direction}} {{rootEntity}} 的边缘", + "entity-filter": "实体筛选器", + "resolve-multiple": "解析为多实体", + "filter-type": "筛选器类型", + "filter-type-required": "筛选器类型必填。", + "entity-filter-no-entity-matched": "未找到匹配指定筛选条件的实体。", + "no-entity-filter-specified": "没有指定实体筛选器", + "root-state-entity": "使用仪表板状态实体作为根实体", + "last-level-relation": "仅获取最后一级关联", + "root-entity": "根实体", + "state-entity-parameter-name": "状态实体参数名称", + "default-state-entity": "默认状态实体", + "default-entity-parameter-name": "默认", + "max-relation-level": "最大关联层级", + "unlimited-level": "不限层级", + "state-entity": "仪表板状态实体", + "all-entities": "所有实体", + "any-relation": "不限" + }, + "asset": { + "asset": "资产", + "assets": "资产", + "management": "资产管理", + "view-assets": "查看资产", + "add": "添加资产", + "asset-type-max-length": "资产类型要小于256", + "assign-to-customer": "分配给客户", + "assign-asset-to-customer": "将资产分配给客户", + "assign-asset-to-customer-text": "请选择要分配给客户的资产", + "no-assets-text": "未找到资产", + "assign-to-customer-text": "请选择客户以分配资产", + "public": "公开", + "assignedToCustomer": "分配客户", + "make-public": "资产设为公开", + "make-private": "资产设为私有", + "unassign-from-customer": "取消分配客户", + "delete": "删除资产", + "asset-public": "资产公开", + "asset-type": "资产类型", + "asset-type-required": "资产类型必填。", + "select-asset-type": "选择资产类型", + "enter-asset-type": "输入资产类型", + "any-asset": "任何资产", + "no-asset-types-matching": "未找到匹配 '{{entitySubtype}}' 的资产类型。", + "asset-type-list-empty": "资产类型未选择。", + "asset-types": "资产类型", + "name": "名称", + "name-required": "名称必填。", + "name-max-length": "名称长度不能大于256", + "label-max-length": "标签长度应小于256", + "description": "说明", + "type": "类型", + "type-required": "类型必填。", + "details": "详情", + "events": "事件", + "add-asset-text": "添加新资产", + "asset-details": "资产详情", + "assign-assets": "分配资产", + "assign-assets-text": "分配 { count, plural, 1 {# 个资产} other {# 个资产} } 给客户", + "assign-asset-to-edge-title": "将资产分配给边缘", + "assign-asset-to-edge-text": "请选择要分配给边缘的资产", + "delete-assets": "删除资产", + "unassign-assets": "取消分配资产", + "unassign-assets-action-title": "从客户处取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }", + "assign-new-asset": "分配新资产", + "delete-asset-title": "确定要删除资产 '{{assetName}}'吗?", + "delete-asset-text": "请注意:确认后,资产及其所有相关数据将不可恢复。", + "delete-assets-title": "确定要删除 { count, plural, 1 {# 个资产} other {# 个资产} }吗?", + "delete-assets-action-title": "删除 { count, plural, 1 {# 个资产} other {# 个资产} }", + "delete-assets-text": "请注意:确认后,所有选定的资产将被删除,所有相关的数据将不可恢复。", + "make-public-asset-title": "确定要将资产 '{{assetName}}' 设为公开吗?", + "make-public-asset-text": "确认后,资产及其所有数据将被公开并被他人访问。", + "make-private-asset-title": "确定要将资产 '{{assetName}}' 设为私有吗?", + "make-private-asset-text": "确认后,资产及其所有数据将被私有化,无法被他人访问。", + "unassign-asset-title": "确定要取消对'{{assetName}}'资产的分配吗?", + "unassign-asset-text": "确认后,资产将未分配,客户无法访问。", + "unassign-asset": "未分配资产", + "unassign-assets-title": "确定要取消分配 { count, plural, 1 {# 个资产} other {# 个资产} }吗?", + "unassign-assets-text": "确认后,所有选定的资产将被分配,客户无法访问。", + "unassign-assets-from-edge": "取消分配边缘", + "copyId": "复制资产ID", + "idCopiedMessage": "资产ID已经复制到粘贴板", + "select-asset": "选择资产", + "no-assets-matching": "未找到匹配 '{{entity}}' 的资产。", + "asset-required": "资产必填", + "name-starts-with": "资产名称以此开头", + "help-text": "根据需要可以使用'%'进行匹配,例如:'%asset_name_contains%', '%asset_name_ends', 'asset_starts_with'。", + "import": "导入资产", + "asset-file": "资产档案", + "label": "标签", + "search": "查找资产", + "assign-asset-to-edge": "Assign Asset(s) To Edge", + "unassign-asset-from-edge": "取消分配边缘", + "unassign-asset-from-edge-title": "确定要取消对'{{assetName}}'资产的分配吗?", + "unassign-asset-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。", + "unassign-assets-from-edge-title": "确定要取消分配 { count, plural, 1 {1 资产} other {# 个资产} }吗?", + "unassign-assets-from-edge-text": "确认后,所有选定的资产将被分配,边缘无法访问。", + "selected-assets": "已选择 { count, plural, 1 {# 个资产} other {# 个资产} }" + }, + "attribute": { + "attributes": "属性", + "latest-telemetry": "最新遥测数据", + "attributes-scope": "设备属性范围", + "scope-latest-telemetry": "最新遥测数据", + "scope-client": "客户端属性", + "scope-server": "服务端属性", + "scope-shared": "共享属性", + "add": "添加属性", + "key": "键名", + "key-max-length": "关键字长度应小于256", + "last-update-time": "最后更新时间", + "key-required": "属性键必填。", + "value": "数值", + "value-required": "属性值必填。", + "delete-attributes-title": "确定要删除 { count, plural, 1 {# 个属性} other {# 个属性} }吗?", + "delete-attributes-text": "注意,确认后所有选中的属性都会被删除。", + "delete-attributes": "删除属性", + "enter-attribute-value": "输入属性值", + "show-on-widget": "在部件上显示", + "widget-mode": "部件模式", + "next-widget": "下一个部件", + "prev-widget": "上一个部件", + "add-to-dashboard": "添加到仪表板", + "add-widget-to-dashboard": "将部件添加到仪表板", + "selected-attributes": "已选择{ count, plural, 1 {# 个属性} other {# 个属性} }", + "selected-telemetry": "已选择 { count, plural, 1 {# 遥测} other {# 遥测} }", + "no-attributes-text": "未找到属性", + "no-telemetry-text": "未找到遥测数据" + }, + "api-usage": { + "api-usage": "Api统计", + "alarm": "警报", + "alarms-created": "创建警报数", + "alarms-created-daily-activity": "每天产生的警报", + "alarms-created-hourly-activity": "每小时产生的警报", + "alarms-created-monthly-activity": "每月产生的警报", + "data-points": "数据点", + "data-points-storage-days": "日存储数据点数", + "email": "邮件", + "email-messages": "邮件信息", + "email-messages-daily-activity": "每天产生的邮件信息", + "email-messages-monthly-activity": "每月产生的邮件信息", + "exceptions": "例外", + "executions": "执行", + "javascript": "JavaScript", + "javascript-executions": "JavaScript例外", + "javascript-functions": "JavaScript函数", + "javascript-functions-daily-activity": "每天执行的JavaScript函数", + "javascript-functions-hourly-activity": "每小时执行的JavaScript函数", + "javascript-functions-monthly-activity": "每月执行的JavaScript函数", + "latest-error": "最新错误", + "messages": "消息", + "notifications": "通知", + "notifications-email-sms": "通知(Email/SMS)", + "notifications-hourly-activity": "每小时的通知", + "permanent-failures": "${entityName}永久性故障", + "permanent-timeouts": "${entityName}永久超时", + "processing-failures": "${entityName}处理失败", + "processing-failures-and-timeouts": "处理失败和超时", + "processing-timeouts": "${entityName}处理超时", + "queue-stats": "队列统计信息", + "rule-chain": "规则链", + "rule-engine": "规则引擎", + "rule-engine-daily-activity": "每天的规则引擎活动", + "rule-engine-executions": "规则引擎执行", + "rule-engine-hourly-activity": "每小时的规则引擎活动", + "rule-engine-monthly-activity": "每月的规则引擎活动", + "rule-engine-statistics": "规则引擎统计信息", + "rule-node": "规则节点", "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": { - "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": "添加关联", - "add-relation-filter": "添加关联筛选器", - "additional-info": "附加信息 (JSON)", - "any-relation": "任意关联", - "any-relation-type": "任何类型", - "delete": "删除关联", - "delete-from-relation-text": "确定删除后,当前实体将与实体 '{{entityName}}' 取消关联", - "delete-from-relation-title": "确定要从实体 '{{entityName}}' 删除关联吗?", - "delete-from-relations-text": "确定删除所有选择的关联关系后,当前实体将与对应的实体取消关联", - "delete-from-relations-title": "确定删除 { count, plural, 1 {# 个关联} other {# 个关联} } 吗?", - "delete-to-relation-text": "确定删除后实体 '{{entityName}}' 将取消与当前实体的关联关系。", - "delete-to-relation-title": "确定要删除实体 '{{entityName}}' 的关联吗?", - "delete-to-relations-text": "确定删除所有选择的关联关系后,与当前实体对应的所有关联关系将被移除。", - "delete-to-relations-title": "确定要删除 { count, plural, 1 {# 个关联} other {# 个关联} }吗?", - "direction": "方向", - "direction-type": { - "FROM": "从", - "TO": "到" - }, - "edit": "编辑关联", - "from-entity": "从实体", - "from-entity-name": "从实体类型", - "from-entity-type": "从实体类型", - "from-relations": "向外的关联", - "invalid-additional-info": "无法解析附加信息JSON。", - "no-relations-text": "未找到关联", - "relation-filters": "关联筛选器", - "relation-type": "关联类型", - "relation-type-required": "关联类型必填", - "relations": "关联", - "remove-relation-filter": "移除关联筛选器", - "search-direction": { - "FROM": "从", - "TO": "到" - }, - "selected-relations": "已选择{ count, plural, 1 {# 个关联} other {# 个关联} }", - "to-entity": "到实体", - "to-entity-name": "到实体名称", - "to-entity-type": "到实体类型", - "to-relations": "向内的关联", - "type": "类型" - }, - "resource": { - "add": "添加资源", - "delete": "删除资源", - "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 {# 个资源} }吗?", - "drop-file": "拖拽资源文件或单击以选择要上传的文件。", - "empty": "资源为空", - "no-resource-matching": "未找到与 '{{widgetsBundle}}' 匹配的资源。", - "no-resource-text": "未找到资源", - "open-widgets-bundle": "打开部件库", - "resource": "资源", - "resource-library-details": "资源库详情", - "resource-type": "资源类型", - "resources-library": "资源库", - "search": "查找资源", - "selected-resources": "已选择{ count, plural, 1 {# 个资源} other {# 个资源} }", - "system": "系统", - "title": "标题", - "title-required": "标题是必填项。" -}, - "rulechain": { - "add": "添加规则链", - "add-rulechain-text": "添加新的规则链", - "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-rulechains-action-title": "删除 { count, plural, 1 {# 个规则链} other {# 个规则链} }", - "delete-rulechains-text": "请注意:确认后,所有选定的规则链将被删除,所有相关的数据将不可恢复。", - "delete-rulechains-title": "确定要删除{count, plural, 1 { 1 个规则链} other {# 个规则链} }吗?", - "description": "说明", - "details": "详情", - "edge-rulechain": "边缘规则链", - "edge-template-root": "模版根链", - "events": "事件", - "export": "导出规则链", - "export-failed-error": "无法导出规则链:{{error}}", - "idCopiedMessage": "规则ID已经复制到粘贴板", - "import": "导入规则链", - "invalid-rulechain-file-error": "不能导入规则链:无效的规则链数据格式。", - "invalid-rulechain-type-error": "不能导入规则链:无效的规则链类型。期望类型为{{expectedRuleChainType}}。", - "management": "规则集管理", - "name": "名称", - "name-required": "名称必填。", - "no-rulechains-matching": "没有发现匹配'{{entity}}'的规则链。", - "no-rulechains-text": "未找到规则链", - "open-rulechain": "打开规则链", - "root": "是否根链", - "rulechain": "规则链", - "rulechain-details": "规则链详情", - "rulechain-file": "规则链文件", - "rulechain-required": "规则链必填", - "rulechains": "规则链库", - "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-root": "设置为根规则链", - "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}}'自动分配给新创建的边缘吗?" - }, - "rulenode": { - "add": "添加规则节点", - "add-link": "添加链接", - "configuration": "配置", - "copy-selected": "选择副本", - "create-new-link-label": "创建一个新的!", - "custom-link-label": "自定义链接标签", - "custom-link-label-required": "自定义链接标签必填", - "debug-mode": "调试模式", - "delete": "删除规则节点", - "delete-selected": "删除选定", - "delete-selected-objects": "删除选定的节点和连接", - "description": "说明", - "deselect-all": "取消选择", - "deselect-all-objects": "取消选择所有节点和连接", - "details": "详情", - "directive-is-not-loaded": "定义的配置指令 '{{directiveName}}' 不可用。", - "events": "事件", - "help": "帮助", - "invalid-target-rulechain": "无法解析目标规则链!", - "link": "链接", - "link-details": "规则节点链接详情", - "link-label": "链接标签", - "link-label-required": "链接标签必填", - "link-labels": "链接标签", - "link-labels-required": "链接标签必填。", - "message": "消息", - "message-type": "消息类型", - "message-type-required": "消息类型必填", - "metadata": "元数据", - "metadata-required": "元数据项不能为空。", - "name": "名称", - "name-required": "名称必填。", - "no-link-label-matching": "未找到匹配 '{{label}}' 的链接标签。", - "no-link-labels-found": "未找到链接标签", - "open-node-library": "打开节点库", - "output": "输出", - "reset-debug-mode": "重置所有节点中的调试模式", - "rulenode-details": "规则节点详情", - "search": "查找节点", - "select-all": "选择全部", - "select-all-objects": "选择所有节点和连接", - "select-message-type": "选择消息类型", - "test": "测试", - "test-script-function": "测试脚本功能", - "type": "类型", - "type-action": "动作", - "type-action-details": "执行特别动作", - "type-enrichment": "属性集", - "type-enrichment-details": "向消息元数据中添加附加信息", - "type-external": "外部的", - "type-external-details": "与外部系统交互", - "type-filter": "筛选器", - "type-filter-details": "使用配置条件筛选传入消息", - "type-input": "输入", - "type-input-details": "规则链的逻辑输入,将传入消息转发到下一个相关规则节点", - "type-rule-chain": "规则链", - "type-rule-chain-details": "将传入消息转发到指定的规则链", - "type-transformation": "变换", - "type-transformation-details": "更改消息 Payload 和元数据", - "type-unknown": "未知", - "type-unknown-details": "未解析的规则节点", - "ui-resources-load-error": "加载配置UI资源失败。" - }, - "tenant-profile": { - "tenant-profile": "租户配置", - "tenant-profiles": "租户配置", - "add": "添加租户配置", - "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": "删除租户配置", - "copyId": "复制租户配置ID", - "name": "名称", - "name-required": "名称必填。", - "data": "配置数据", - "profile-configuration": "配置设置", - "description": "说明", - "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-assets-range": "最大资产数不能为负数", - "maximum-customers": "最大客户数", - "maximum-customers-required": "最大客户数必填。", - "maximum-customers-range": "最大客户数不能为负数", - "maximum-users": "最大用户数", - "maximum-users-required": "最大用户数必填。", - "maximum-users-range": "最大用户数不能为负数", - "maximum-dashboards": "最大仪表板数", - "maximum-dashboards-required": "最大仪表板数必填。", - "maximum-dashboards-range": "最大仪表板数不能为负数", - "maximum-rule-chains": "最大规则链数", - "maximum-rule-chains-required": "最大规则链数必填。", - "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": "添加租户", - "add-tenant-text": "添加新租户", - "admins": "管理员", - "copyId": "复制租户ID", - "delete": "删除租户", - "delete-tenant-text": "请注意:确认后,租户和所有相关数据将不可恢复。", - "delete-tenant-title": "确定要删除租户'{{tenantTitle}}'吗?", - "delete-tenants-action-title": "删除 { count, plural, 1 {# 个租户} other {# 个租户} }", - "delete-tenants-text": "请注意:确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", - "delete-tenants-title": "确定要删除 {count,plural,1 {# 个租户} other {# 个租户} } 吗?", - "description": "说明", - "details": "详情", - "events": "事件", - "idCopiedMessage": "租户ID已经复制到粘贴板", - "isolated-tb-rule-engine": "使用独立的规则引擎服务", - "isolated-tb-rule-engine-details": "每个独立租户需要单独的规则引擎微服务", - "manage-tenant-admins": "管理租户管理员", - "management": "租户管理", - "no-tenants-matching": "未找到匹配 '{{entity}}' 的租户", - "no-tenants-text": "未找到租户", - "search": "查找租户", - "select-tenant": "选择租户", - "selected-tenants": "已选择 { count, plural, 1 {# 个租户} other {# 个租户} }", - "tenant": "租户", - "tenant-details": "租客详情", - "tenant-required": "租户必填", - "tenants": "租户", - "title": "标题", - "title-required": "标题必填。" - }, - "timeinterval": { - "advanced": "高级", - "days": "天", - "days-interval": "{ days, plural, 1 {# 天} other {# 天} }", - "hours": "小时", - "hours-interval": "{ hours, plural, 1 {# 小时} other {# 小时} }", - "minutes": "分钟", - "minutes-interval": "{ minutes, plural, 1 {# 分} other {# 分} }", - "seconds": "秒", - "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": "天", - "hours": "小时", - "minutes": "分钟", - "seconds": "秒" - }, - "timewindow": { - "date-range": "日期范围", - "days": "{ days, plural, 1 {# 天 } other {# 天 } }", - "edit": "编辑时间窗口", - "hide": "隐藏", - "history": "历史", - "hours": "{ hours, plural, 0 {- 小时 } 1 {# 小时 } other {# 小时 } }", - "last": "最后", - "last-prefix": "最后", - "minutes": "{ minutes, plural, 0 {- 分 } 1 {# 分 } other {# 分 } }", - "period": "从 {{ startTime }} 到 {{ endTime }}", - "realtime": "实时", - "seconds": "{ seconds, plural, 0 {- 秒 } 1 {# 秒 } other {# 秒 } }", - "time-period": "时间段", - "interval": "区间" - }, - "timezone": { - "no-timezones-matching": "No timezones matching '{{timezone}}' were found.", - "select-timezone": "选择时区", - "timezone": "时区", - "timezone-required": "时区必填。" - }, - "user": { - "activation-email-sent-message": "激活电子邮件已成功发送!", - "activation-link": "用户激活链接", - "activation-link-copied-message": "用户激活链接已经复制到粘贴板", - "activation-link-text": "使用该链接 激活 激活用户:", - "activation-method": "激活方式", - "add": "添加用户", - "add-user-text": "添加新用户", - "always-fullscreen": "始终全屏", - "anonymous": "匿名", - "copy-activation-link": "复制用户激活链接", - "customer": "客户", - "customer-users": "客户用户", - "default-dashboard": "默认面板", - "delete": "删除用户", - "delete-user-text": "请注意:确认后,用户和所有相关数据将不可恢复。", - "delete-user-title": "确定要删除用户 '{{userEmail}}' 吗?", - "delete-users-action-title": "删除 { count, plural, 1 {# 个用户} other {# 个用户} }", - "delete-users-text": "请注意:确认后,所有选定的用户将被删除,所有相关数据将不可恢复。", - "delete-users-title": "确定要删除 { count, plural, 1 {# 个用户} other {# 个用户} } 吗?", - "description": "说明", - "details": "详情", - "disable-account": "禁用用户帐户", - "disable-account-message": "已成功禁用用户帐户!", - "display-activation-link": "显示激活链接", - "email": "电子邮件", - "email-required": "电子邮件必填。", - "enable-account": "启用用户帐户", - "enable-account-message": "已成功启用用户帐户!", - "first-name": "名字", - "invalid-email-format": "无效的邮件格式。", - "last-name": "姓氏", - "login-as-customer-user": "以客户用户身份登录", - "login-as-tenant-admin": "以租户管理员身份登录", - "no-users-matching": "未找到匹配 '{{entity}}' 的用户。", - "no-users-text": "未找到用户", - "resend-activation": "重新发送激活", - "search": "查找用户", - "select-user": "选择用户", - "selected-users": "已选择 { count, plural, 1 {# 个用户} other {# 个用户} }", - "send-activation-mail": "发送激活邮件", - "sys-admin": "系统管理员", - "tenant-admin": "租户管理员", - "tenant-admins": "租户管理员", - "user": "用户", - "user-details": "用户详细信息", - "user-required": "用户必填", - "users": "用户" - }, - "value": { - "boolean": "布尔值", - "boolean-value": "布尔值", - "double": "双精度小数", - "double-value": "双精度小数值", - "double-value-required": "需要双精度值", - "false": "假", - "integer": "数字", - "integer-value": "数字值", - "integer-value-required": "整数值必填", - "invalid-integer-value": "整数值无效", - "json": "JSON", - "json-value": "JSON值", - "json-value-invalid": "JSON值的格式无效", - "json-value-required": "JSON值必填。", - "long": "Long", - "string": "字符串", - "string-value": "字符串值", - "string-value-required": "字符串值必填", - "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模板)", - "header-button": "部件顶部按钮", - "open-dashboard": "切换到另一个仪表板", - "open-dashboard-state": "切换到新仪表板状态", - "open-right-layout": "打开右侧布局 (移动端视图)", - "set-entity-from-widget": "从部件中设置实体", - "target-dashboard": "目标仪表板", - "target-dashboard-state": "目标仪表板状态", - "target-dashboard-state-required": "目标仪表板状态必填", - "update-dashboard-state": "更新当前仪表板状态" - }, - "widget-config": { - "action": "Action", - "action-icon": "图标", - "action-name": "名称", - "action-name-not-unique": "Action 名称已经存在。
相同 Action Source 的 Action 名称必须唯一。", - "action-name-required": "Action 名称必填。", - "action-source": "Action Source", - "action-source-required": "Action Source 必填", - "action-type": "类型", - "action-type-required": "类型必填", - "actions": "Actions", - "add-action": "添加 Action", - "add-datasource": "添加数据源", - "advanced": "高级", - "alarm-source": "告警源", - "background-color": "背景颜色", - "data": "数据", - "datasource-parameters": "参数", - "datasource-type": "类型", - "datasources": "数据源", - "decimals": "浮点数后的位数", - "delete-action": "删除 Action", - "delete-action-text": "确定要删除部件Action '{{actionName}}' 吗?", - "delete-action-title": "删除 Widget Action", - "display-icon": "显示标题图标", - "display-legend": "显示图例", - "display-timewindow": "显示时间窗口", - "display-title": "显示标题", - "drop-shadow": "阴影", - "edit-action": "编辑 Action", - "enable-fullscreen": "启用全屏", - "general-settings": "基本设置", - "height": "高度", - "icon-color": "图标颜色", - "icon-size": "图标大小", - "margin": "边缘", - "maximum-datasources": "最大允许 { count, plural, 1 {# 个数据源。} other {# 个数据源。} }", - "mobile-mode-settings": "移动端设置", - "no-actions-text": "未找到 Action", - "order": "顺序", - "padding": "填充", - "remove-datasource": "移除数据源", - "search-actions": "Action 检索", - "settings": "设置", - "target-device": "目标设备", - "text-color": "文字颜色", - "timewindow": "时间窗口", - "title": "标题", - "title-style": "标题风格", - "title-tooltip": "标题工具提示", - "units": "特殊符号展示值", - "use-dashboard-timewindow": "使用仪表板的时间窗口", - "widget-style": "部件风格" - }, - "widget-type": { - "create-new-widget-type": "创建新的部件类型", - "export": "导出部件类型", - "export-failed-error": "无法导出部件类型: {{error}}", - "import": "导入部件类型", - "invalid-widget-type-file-error": "无法导入部件类型:无效的部件类型数据结构。", - "widget-type-file": "部件类型文件" - }, - "widget": { - "add": "添加部件", - "add-resource": "添加资源", - "add-widget-type": "添加新的部件类型", - "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", - "datakey-settings-schema": "数据键设置模式", - "edit": "编辑部件", - "editor": "部件编辑器", - "export": "导出部件", - "html": "HTML", - "javascript": "Javascript", - "js": "JS", - "latest": "最新值", - "management": "管理部件", - "missing-widget-title-error": "部件标题必须指定!", - "no-data": "小部件上没有要显示的数据", - "no-data-found": "未找到数据", - "remove": "删除部件", - "remove-resource": "删除资源", - "remove-widget-text": "确认后,控件和所有相关数据将变得不可恢复。", - "remove-widget-title": "确定要删除 '{{widgetTitle}}'部件吗?", - "remove-widget-type": "删除部件类型", - "remove-widget-type-text": "确认后,窗口部件类型和所有相关数据将不可恢复。", - "remove-widget-type-title": "确定要删除部件类型 '{{widgetName}}'吗?", - "resource-is-module": "Is module", - "resource-url": "JavaScript/CSS URL", - "resources": "资源", - "rpc": "控件部件", - "run": "运行部件", - "save": "保存部件", - "save-widget-type-as": "部件类型另存为", - "save-widget-type-as-text": "请输入新的部件标题或选择目标部件包", - "saveAs": "部件另存为", - "search-data": "查找数据", - "select-widget-type": "选择窗口部件类型", - "select-widgets-bundle": "选择部件包", - "settings-schema": "设置模式", - "static": "静态部件", - "tidy": "Tidy", - "timeseries": "Timeseries", - "title": "部件标题", - "title-required": "部件标题必填。", - "toggle-fullscreen": "切换全屏", - "type": "部件类型", - "unable-to-save-widget-error": "无法保存部件!控件有错误!", - "undo": "撤消部件更改", - "widget-bundle": "部件包", - "widget-library": "部件库", - "widget-saved": "部件已保存", - "widget-template-load-failed-error": "无法加载部件模板!", - "widget-type-load-error": "由于以下错误未加载小部件:", - "widget-type-load-failed-error": "无法加载部件类型!", - "widget-type-not-found": "加载部件配置出错。
可能关联的部件已经删除了。" - }, - "widgets-bundle": { - "add": "添加部件包", - "add-widgets-bundle-text": "添加新的部件包", - "create-new-widgets-bundle": "创建新的部件包", - "current": "当前组", - "delete": "删除部件包", - "delete-widgets-bundle-text": "请注意:确认后,部件包和所有相关数据将不可恢复。", - "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 {# 个部件包} } 吗?", - "description": "描述", - "details": "详情", - "empty": "部件包是空的", - "export": "导出部件包", - "export-failed-error": "无法导出部件包: {{error}}", - "image-preview": "图片预览", - "import": "导入部件包", - "invalid-widgets-bundle-file-error": "无法导入部件包:无效的部件包数据结构。", - "loading-widgets-bundles": "加载部件包...", - "no-widgets-bundles-matching": "未找到与 '{{widgetsBundle}}' 匹配的部件包。", - "no-widgets-bundles-text": "未找到部件包", - "open-widgets-bundle": "打开部件包", - "search": "查找部件包", - "selected-widgets-bundles": "已选择 { count, plural, 1 {# 个部件包} other {# 个部件包} }", - "system": "系统", - "title": "标题", - "title-required": "标题必填。", - "widgets-bundle-details": "部件包详细信息", - "widgets-bundle-file": "部件包文件", - "widgets-bundle-required": "部件包必填。", - "widgets-bundles": "部件包" - }, - "widgets": { - "date-range-navigator": { - "localizationMap": { - "2 weeks": "2周", - "3 months": "3个月", - "6 months": "6个月", - "Apr": "4月", - "April": "四月", - "Aug": "8月", - "August": "八月", - "Custom Date Range": "自定义日期范围", - "Custom interval": "自定义间隔", - "Date Range Template": "日期范围模板", - "Date picker": "日期选择器", - "Day": "天", - "Dec": "12月", - "December": "十二月", - "Feb": "2月", - "February": "二月", - "Fri": "周五", - "Hour": "小时", - "Interval": "间隔", - "Jan": "1月", - "January": "一月", - "Jul": "7月", - "July": "七月", - "Jun": "6月", - "June": "六月", - "Last Month": "上个月", - "Last Week": "上个星期", - "Last Year": "去年", - "Mar": "3月", - "March": "三月", - "May": "5月", - "Mon": "周一", - "Month": "月", - "Nov": "11月", - "November": "十一月", - "Oct": "10月", - "October": "十月", - "Ok": "Ok", - "Sat": "周六", - "Sep": "9月", - "September": "九月", - "Step size": "步长", - "Sun": "周日", - "This Month": "这个月", - "This Week": "本星期", - "This Year": "今年", - "Thu": "周四", - "Today": "今天", - "Tue": "周二", - "Wed": "周三", - "Week": "周", - "Year": "年", - "Yesterday": "昨天" - } - }, - "input-widgets": { - "attribute-not-allowed": "属性参数不能在此小部件中使用", - "blocked-location": "在浏览器中阻止地理位置", - "claim-device": "声明设备", - "claim-failed": "声明设备失败!", - "claim-not-found": "未找到设备!", - "claim-successful": "设备已成功申领!", - "date": "日期", - "device-name": "设备名称", - "device-name-required": "设备名称必填", - "discard-changes": "放弃更改", - "enable-https-use-widget": "请启用HTTPS以使用此部件", - "entity-attribute-required": "实体属性必填", - "entity-coordinate-required": "纬度和经度两个字段都是必需的", - "entity-timeseries-required": "实体 Timeseries 必填", - "get-location": "获取当前位置", - "invalid-date": "无效日期", - "latitude": "纬度", - "longitude": "经度", - "max-value-error": "最大值是{{value}}", - "min-value-error": "最小值是{{value}}", - "no-attribute-selected": "未选择任何属性", - "no-coordinate-specified": "未指定纬度/经度的数据键", - "no-datakey-selected": "未选择数据键", - "no-entity-selected": "未选择实体", - "no-found-your-camera": "未找到摄像机", - "no-image": "没有图像", - "no-permission-camera": "权限被用户拒绝/此站点无权使用摄像机", - "no-support-geolocation": "您的浏览器不支持地理定位", - "no-support-web-camera": "您的浏览器不支持摄像头", - "no-timeseries-selected": "未选择 Timeseries", - "not-allowed-entity": "所选实体不能具有共享属性", - "secret-key": "密钥", - "secret-key-required": "密钥必填", - "switch-attribute-value": "切换实体属性值", - "switch-camera": "切换摄像机", - "switch-timeseries-value": "切换实体 Timeseries 值", - "take-photo": "拍照", - "time": "时间", - "timeseries-not-allowed": "Timeseries 参数不能用于此部件", - "update-attribute": "更新 attribute", - "update-failed": "更新失败", - "update-successful": "更新成功", - "update-timeseries": "更新 Timeseries", - "value": "数值" + "sms-messages": "短信信息", + "sms-messages-daily-activity": "每天的短信息", + "sms-messages-monthly-activity": "每小时的短信息", + "successful": "${entityName}成功", + "telemetry": "遥测数据", + "telemetry-persistence": "遥测持久化", + "telemetry-persistence-daily-activity": "每天的遥测持久化", + "telemetry-persistence-hourly-activity": "每小时的遥测持久化", + "telemetry-persistence-monthly-activity": "每月的遥测持久化", + "transport": "传输", + "transport-daily-activity": "每天传输数据量", + "transport-data-points": "传输数据点", + "transport-hourly-activity": "每小时传输数据量", + "transport-messages": "传输消息", + "transport-monthly-activity": "每月传输数据量", + "view-details": "查看详细信息", + "view-statistics": "查看统计信息" + }, + "audit-log": { + "audit": "审计", + "audit-logs": "审计日志", + "timestamp": "时间戳", + "entity-type": "实体类型", + "entity-name": "实体名称", + "user": "用户", + "type": "类型", + "status": "状态", + "details": "详情", + "type-added": "添加", + "type-deleted": "删除", + "type-updated": "更新", + "type-attributes-updated": "更新属性", + "type-attributes-deleted": "删除属性", + "type-rpc-call": "RPC调用", + "type-credentials-updated": "更新凭据", + "type-assigned-to-customer": "分配给客户", + "type-unassigned-from-customer": "未分配给客户", + "type-assigned-to-edge": "分配给边缘", + "type-unassigned-from-edge": "未分配给边缘", + "type-activated": "激活", + "type-suspended": "暂停", + "type-credentials-read": "读取凭据", + "type-attributes-read": "读取属性", + "type-relation-add-or-update": "关联已更新", + "type-relation-delete": "关联已删除", + "type-relations-delete": "删除所有关联", + "type-alarm-ack": "已确认", + "type-alarm-clear": "已清除", + "type-login": "登录", + "type-logout": "注销", + "type-lockout": "锁定", + "status-success": "成功", + "status-failure": "失败", + "audit-log-details": "审计日志详情", + "no-audit-logs-prompt": "未找到日志", + "action-data": "活动数据", + "failure-details": "失败详情", + "search": "查找审计日志", + "clear-search": "清空查找", + "type-assigned-from-tenant": "从租户分配", + "type-assigned-to-tenant": "分配给租户", + "type-provision-success": "设备已预配置", + "type-provision-failure": "设备预配置失败", + "type-timeseries-updated": "遥测数据已更新", + "type-timeseries-deleted": "遥测数据已删除" + }, + "confirm-on-exit": { + "message": "有未保存的更改,确定要离开此页吗?", + "html-message": "有未保存的更改。
确定要离开此页面吗?", + "title": "未保存的更改" + }, + "contact": { + "country": "国家", + "city": "城市", + "state": "州", + "postal-code": "邮政编码", + "postal-code-invalid": "只允许数字。", + "address": "地址", + "address2": "地址二", + "phone": "电话", + "email": "电子邮件", + "no-address": "无地址", + "state-max-length": "省份名称长度应小于256", + "phone-max-length": "电话号码长度应小于256", + "city-max-length": "城市名称长度应小于256" + }, + "common": { + "username": "用户名", + "password": "密码", + "enter-username": "输入用户名", + "enter-password": "输入密码", + "enter-search": "输入检索条件", + "created-time": "创建时间", + "loading": "正在加载中...", + "proceed": "继续", + "open-details-page": "打开详情页" + }, + "content-type": { + "json": "Json", + "text": "Text", + "binary": "Binary (Base64)" + }, + "customer": { + "customer": "客户", + "customers": "客户", + "management": "客户管理", + "dashboard": "客户仪表板", + "dashboards": "客户仪表板", + "devices": "客户设备", + "entity-views": "客户实体视图", + "assets": "客户资产", + "public-dashboards": "公共仪表板", + "public-devices": "公共设备", + "public-assets": "公共资产", + "public-edges": "公共边缘", + "public-entity-views": "公共实体视图", + "add": "添加客户", + "delete": "删除此客户", + "manage-customer-users": "管理客户用户", + "manage-customer-devices": "管理客户设备", + "manage-customer-dashboards": "管理客户仪表板", + "manage-public-devices": "管理公共设备", + "manage-public-dashboards": "管理公共仪表板", + "manage-customer-assets": "管理客户资产", + "manage-public-assets": "管理公共资产", + "manage-customer-edges": "管理客户边缘", + "manage-public-edges": "管理公共边缘", + "add-customer-text": "添加新客户", + "no-customers-text": "未找到客户", + "customer-details": "客户详情", + "delete-customer-title": "确定要删除客户'{{customerTitle}}'吗?", + "delete-customer-text": "请注意:确认后,客户及其所有相关数据将不可恢复。", + "delete-customers-title": "确定要删除 { count, plural, 1 {# 个客户} other {# 个客户} }吗?", + "delete-customers-action-title": "删除 { count, plural, 1 {# 个客户} other {# 个客户} }", + "delete-customers-text": "请注意:确认后,所有选定的客户将被删除,所有相关数据将不可恢复。", + "manage-users": "管理用户", + "manage-assets": "管理资产", + "manage-devices": "管理设备", + "manage-dashboards": "管理仪表板", + "title": "标题", + "title-required": "标题必填。", + "title-max-length": "标题长度必须少于256个字符", + "description": "说明", + "details": "详情", + "events": "事件", + "copyId": "复制客户ID", + "idCopiedMessage": "客户ID已复制到粘贴板", + "select-customer": "选择客户", + "no-customers-matching": "未找到匹配 '{{entity}}' 的客户。", + "customer-required": "客户必填", + "select-default-customer": "选择默认的客户", + "default-customer": "默认客户", + "default-customer-required": "为了调试租户级别上的仪表板,需要默认客户。", + "search": "查找客户", + "selected-customers": "已选择 { count, plural, 1 {# 个客户} other {# 个客户} }", + "edges": "客户边缘实例", + "manage-edges": "管理边缘" + }, + "datetime": { + "date-from": "开始日期", + "time-from": "开始时间", + "date-to": "结束日期", + "time-to": "结束时间" + }, + "dashboard": { + "dashboard": "仪表板", + "dashboards": "仪表板库", + "management": "仪表板管理", + "view-dashboards": "查看仪表板", + "add": "添加仪表板", + "assign-dashboard-to-customer": "将仪表板分配给客户", + "assign-dashboard-to-customer-text": "请选择要分配给客户的仪表板", + "assign-dashboard-to-edge-title": "将仪表板分配给边缘", + "assign-to-customer-text": "请选择客户分配仪表板", + "assign-to-customer": "分配给客户", + "unassign-from-customer": "取消分配客户", + "make-public": "仪表板设为公开", + "make-private": "仪表板设为私有", + "manage-assigned-customers": "管理已分配的客户", + "assigned-customers": "已分配的客户", + "assign-to-customers": "将仪表板分配给客户", + "assign-to-customers-text": "请选择客户指定仪表板", + "unassign-from-customers": "客户未分配仪表板", + "unassign-from-customers-text": "请选择从仪表板中取消分配的客户", + "no-dashboards-text": "未找到仪表板", + "no-widgets": "没有配置部件", + "add-widget": "添加新的部件", + "title": "标题", + "image": "仪表板图片", + "mobile-app-settings": "移动端应用设置", + "mobile-order": "移动端应用中的仪表板排序", + "mobile-hide": "在移动端应用中隐藏仪表板", + "update-image": "更新缩略图", + "take-screenshot": "截图", + "select-widget-title": "选择部件", + "select-widget-value": "{{title}}: 选择部件", + "select-widget-subtitle": "可用的部件类型列表", + "delete": "删除仪表板", + "title-required": "标题必填。", + "title-max-length": "标题长度必须少于256个字符", + "description": "说明", + "details": "详情", + "dashboard-details": "仪表板详情", + "add-dashboard-text": "添加新的仪表板", + "assign-dashboards": "分配仪表板", + "assign-new-dashboard": "分配新的仪表板", + "assign-dashboards-text": "分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户", + "unassign-dashboards-action-text": "取消分配 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 给客户", + "delete-dashboards": "删除仪表板", + "unassign-dashboards": "取消分配仪表板", + "unassign-dashboards-action-title": "取消分配此客户 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", + "delete-dashboard-title": "确定要删除仪表板 '{{dashboardTitle}}'吗?", + "delete-dashboard-text": "请注意:确认后,仪表板及其所有相关数据将不可恢复。", + "delete-dashboards-title": "确定要删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }吗?", + "delete-dashboards-action-title": "删除 { count, plural, 1 {# 个仪表板} other {# 个仪表板} }", + "delete-dashboards-text": "请注意:确认后,所有选定的仪表板将被删除,所有相关数据将不可恢复。", + "unassign-dashboard-title": "确定要取消分配仪表板 '{{dashboardTitle}}'吗?", + "unassign-dashboard-text": "确认后,面板将被取消分配,客户将无法访问。", + "unassign-dashboard": "取消分配仪表板", + "unassign-dashboards-title": "确定要取消分配仪表板 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 吗?", + "unassign-dashboards-text": "确认后,所有选定的仪表板将被取消分配,客户将无法访问。", + "public-dashboard-title": "仪表板现已公开", + "public-dashboard-text": "仪表板 {{dashboardTitle}} 已被公开,可通过如下 链接访问:", + "public-dashboard-notice": "提示: 不要忘记将相关设备公开以访问其数据。", + "make-private-dashboard-title": "确定要将仪表板 '{{dashboardTitle}}' 设为私有吗?", + "make-private-dashboard-text": "确认后,仪表板将被设为私有,不能被其他人访问。", + "make-private-dashboard": "仪表板设为私有", + "socialshare-text": "'{{dashboardTitle}}' 由Thingsboard提供支持", + "socialshare-title": "'{{dashboardTitle}}' 由Thingsboard提供支持", + "select-dashboard": "选择仪表板", + "no-dashboards-matching": "未找到匹配 '{{entity}}' 的仪表板。", + "dashboard-required": "仪表板必填。", + "select-existing": "选择现有仪表板", + "create-new": "创建新的仪表板", + "new-dashboard-title": "新仪表板标题", + "open-dashboard": "打开仪表板", + "set-background": "设置背景", + "background-color": "背景颜色", + "background-image": "背景图片", + "background-size-mode": "背景大小模式", + "no-image": "无图像选择", + "empty-image": "No image", + "drop-image": "拖拽图像或单击以选择要上传的文件。", + "maximum-upload-file-size": "最大上传文件大小: {{ size }}", + "cannot-upload-file": "无法上传文件", + "settings": "设置", + "layout-settings": "Layout settings", + "columns-count": "列数", + "columns-count-required": "需要列数。", + "min-columns-count-message": "只允许最少10列", + "max-columns-count-message": "只允许最多1000列", + "widgets-margins": "部件间边距", + "margin-required": "边距值必填。", + "min-margin-message": "最小边距值只允许为0。", + "max-margin-message": "仅允许50作为最大边距值。", + "horizontal-margin": "水平边距", + "horizontal-margin-required": "需要水平边距值。", + "min-horizontal-margin-message": "只允许0作为最小水平边距值。", + "max-horizontal-margin-message": "只允许50作为最大水平边距值。", + "vertical-margin": "垂直边距", + "vertical-margin-required": "需要垂直边距值。", + "min-vertical-margin-message": "只允许0作为最小垂直边距值。", + "max-vertical-margin-message": "只允许50作为最大垂直边距值。", + "autofill-height": "自动填充布局高度", + "mobile-layout": "移动端布局设置", + "mobile-row-height": "移动端行高距(px)", + "mobile-row-height-required": "移动端行高距必填。", + "min-mobile-row-height-message": "移动端行高距至少5px。", + "max-mobile-row-height-message": "移动端行高距最多200px。", + "title-settings": "Title settings", + "display-title": "显示仪表板标题", + "title-color": "标题颜色", + "toolbar-settings": "Toolbar settings", + "hide-toolbar": "Hide toolbar", + "toolbar-always-open": "工具栏常驻", + "display-dashboards-selection": "显示仪表板选项", + "display-entities-selection": "显示实体选项", + "display-filters": "显示筛选器", + "display-dashboard-timewindow": "显示时间窗口", + "display-dashboard-export": "显示导出", + "display-update-dashboard-image": "显示仪表板缩略图", + "dashboard-logo-settings": "Logo设置", + "display-dashboard-logo": "在仪表板全屏模式下显示 Logo", + "dashboard-logo-image": "仪表板 Logo 图片", + "advanced-settings": "高级设置", + "dashboard-css": "仪表板样式", + "import": "导入仪表板", + "export": "导出仪表板", + "export-failed-error": "无法导出仪表板: {{error}}", + "create-new-dashboard": "创建新的仪表板", + "dashboard-file": "仪表板文件", + "invalid-dashboard-file-error": "无法导入仪表板: 仪表板数据结构无效。", + "dashboard-import-missing-aliases-title": "配置导入仪表板使用的别名", + "create-new-widget": "创建新部件", + "import-widget": "导入部件", + "widget-file": "部件文件", + "invalid-widget-file-error": "无法导入窗口部件: 窗口部件数据结构无效。", + "widget-import-missing-aliases-title": "配置导入的窗口部件使用的别名", + "open-toolbar": "打开仪表板工具栏", + "close-toolbar": "关闭工具栏", + "configuration-error": "配置错误", + "alias-resolution-error-title": "仪表板别名配置错误", + "invalid-aliases-config": "无法找到与某些别名筛选器匹配的任何设备。
请联系您的管理员以解决此问题。", + "select-devices": "选择设备", + "assignedToCustomer": "分配给客户", + "assignedToCustomers": "分配给客户", + "public": "公开", + "copyId": "复制仪表板Id", + "idCopiedMessage": "仪表板ID已经复制到粘贴板", + "public-link": "公共链接", + "copy-public-link": "复制公共链接", + "public-link-copied-message": "仪表板的公共链接已被复制到剪贴板", + "manage-states": "仪表板状态管理", + "states": "仪表板状态", + "search-states": "仪表板状态检索", + "selected-states": "已选择 { count, plural, 1 {# 个仪表板状态} other {# 个仪表板状态} }", + "edit-state": "仪表板状态编辑", + "delete-state": "删除仪表板状态", + "add-state": "添加仪表板状态", + "no-states-text": "未找到状态", + "state": "仪表板状态", + "state-name": "名称", + "state-name-required": "仪表板状态名必填。", + "state-id": "状态ID", + "state-id-required": "仪表板状态ID必填。", + "state-id-exists": "仪表板状态ID已经存在。", + "is-root-state": "根状态", + "delete-state-title": "删除仪表板状态", + "delete-state-text": "确定要删除仪表板状态 '{{stateName}}' 吗?", + "show-details": "显示详情", + "hide-details": "隐藏详情", + "select-state": "选择目标状态", + "state-controller": "状态控制", + "search": "查找仪表板", + "selected-dashboards": "已选择 { count, plural, 1 {# 个仪表盘} other {# 个仪表盘} }", + "home-dashboard": "首页仪表板", + "home-dashboard-hide-toolbar": "隐藏首页仪表板工具栏", + "unassign-dashboard-from-edge-text": "确认后,所有选定的仪表板将被取消分配,边缘将无法访问。", + "unassign-dashboards-from-edge-title": "确定要取消分配仪表板 { count, plural, 1 {# 个仪表板} other {# 个仪表板} } 吗?", + "unassign-dashboards-from-edge-text": "确认后,所有选定的仪表板将被取消分配,边缘将无法访问。", + "assign-dashboard-to-edge": "将仪表板分配给边缘", + "assign-dashboard-to-edge-text": "请选择要分配给边缘的仪表板", + "non-existent-dashboard-state-error": "仪表盘\"{{ stateId }}\"未找到" + }, + "datakey": { + "settings": "设置", + "advanced": "高级", + "label": "标签", + "color": "颜色", + "units": "单位符号", + "decimals": "小数位数", + "data-generation-func": "数据生成功能", + "use-data-post-processing-func": "使用数据后处理功能", + "configuration": "数据键配置", + "timeseries": "Timeseries", + "attributes": "属性", + "entity-field": "实体字段", + "alarm": "告警字段", + "timeseries-required": "实体 Timeseries 必填。", + "timeseries-or-attributes-required": "实体 Timeseries/属性必填。", + "alarm-fields-timeseries-or-attributes-required": "告警字段或实体 Timeseries/属性必填。", + "maximum-timeseries-or-attributes": "最多允许 { count, plural, 1 {# 个 timeseries/属性。} other {# 个 timeseries/属性。} }", + "alarm-fields-required": "告警字段必填。", + "function-types": "函数类型", + "function-type": "函数类型", + "function-types-required": "需要函数类型。", + "alarm-keys": "报警数据键", + "alarm-key": "报警数据键", + "alarm-key-functions": "报警数据函数", + "alarm-key-function": "报警数据函数", + "latest-keys": "最新数据键", + "latest-key": "最新数据键", + "latest-key-functions": "最新数据函数", + "latest-key-function": "最新数据函数", + "timeseries-keys": "时序数据键", + "timeseries-key": "时序数据键", + "timeseries-key-functions": "时序数据函数", + "timeseries-key-function": "时序数据函数", + "maximum-function-types": "最多允许 { count, plural, 1 {# 个函数类型} other {# 个函数类型} }", + "time-description": "当前值的时间戳;", + "value-description": "当前值;", + "prev-value-description": "上一次函数调用的结果;", + "time-prev-description": "上一个值的时间戳;", + "prev-orig-value-description": "先前的原始值;", + "aggregation": "聚合", + "aggregation-type-hint-common": "出于性能原因聚合值计算仅适用于“当前日期”、“当前月份”等固定时间间隔,而不适用于“最近30分钟”或“最近24小时”等滑动窗口间隔。", + "aggregation-type-none-hint": "取最新值。", + "aggregation-type-min-hint": "在选定时间窗口内的数据点中查找最小值。", + "aggregation-type-max-hint": "在选定时间窗口内的数据点中查找最大值。", + "aggregation-type-avg-hint": "计算选定时间窗口内数据点之间的平均值。", + "aggregation-type-sum-hint": "对选定时间窗口内所有数据点的值求和。", + "aggregation-type-count-hint": "选定时间窗口内的数据点总数。", + "delta-calculation": "增量计算", + "enable-delta-calculation": "启用增量计算", + "enable-delta-calculation-hint": "启用后将根据选定时间窗口和指定比较时段的聚合值计算数据键值。出于性能原因,增量计算仅适用于历史时间窗口,而不适用于实时值。例如,您可以计算昨天的能耗与前天的能耗之间的差值。", + "delta-calculation-result": "增量计算结果", + "delta-calculation-result-previous-value": "上个值", + "delta-calculation-result-delta-absolute": "绝对值", + "delta-calculation-result-delta-percent": "百分比" + }, + "datasource": { + "type": "数据源类型", + "name": "名称", + "label": "标签", + "add-datasource-prompt": "请添加数据源" + }, + "details": { + "details": "详情", + "edit-mode": "编辑模式", + "edit-json": "编辑JSON", + "toggle-edit-mode": "切换编辑模式" + }, + "device": { + "device": "设备", + "device-required": "设备必填", + "devices": "设备", + "management": "设备管理", + "view-devices": "查看设备", + "device-alias": "设备别名", + "device-type-max-length": "设备类型长度必须少于256个字符", + "aliases": "设备别名", + "no-alias-matching": "'{{alias}}' 未找到。", + "no-aliases-found": "未找到别名。", + "no-key-matching": "'{{key}}' 未找到。", + "no-keys-found": "未找到密钥。", + "create-new-alias": "创建一个新的!", + "create-new-key": "创建一个新的!", + "duplicate-alias-error": "找到重复别名 '{{alias}}'。
设备别名必须是唯一的。", + "configure-alias": "配置 '{{alias}}' 别名", + "no-devices-matching": "未找到与 '{{entity}}' 匹配的设备。", + "alias": "别名", + "alias-required": "设备别名必填。", + "remove-alias": "删除设备别名", + "add-alias": "添加设备别名", + "name-starts-with": "名称前缀", + "help-text": "根据需要可以使用'%'进行匹配,例如:'%device_name_contains%','%device_name_ends','device_starts_with'。", + "device-list": "设备列表", + "use-device-name-filter": "使用筛选器", + "device-list-empty": "没有被选中的设备", + "device-name-filter-required": "设备名称筛选器必填。", + "device-name-filter-no-device-matched": "未找到以'{{device}}' 开头的设备。", + "add": "添加设备", + "assign-to-customer": "分配给客户", + "assign-device-to-customer": "将设备分配给客户", + "assign-device-to-customer-text": "请选择要分配给客户的设备", + "assign-device-to-edge-title": "将设备分配给边缘", + "assign-device-to-edge-text": "请选择要分配给边缘的设备", + "make-public": "公开", + "make-private": "私有", + "no-devices-text": "未找到设备", + "assign-to-customer-text": "请选择客户分配设备", + "device-details": "设备详细信息", + "add-device-text": "添加新设备", + "credentials": "凭据", + "manage-credentials": "管理凭据", + "delete": "删除设备", + "assign-devices": "分配设备", + "assign-devices-text": "将 {count,plural,1 {# 个设备} other {# 个设备} }分配给客户", + "delete-devices": "删除设备", + "unassign-from-customer": "取消分配客户", + "unassign-devices": "取消分配设备", + "unassign-devices-action-title": "取消分配此客户 {count,plural,1 {# 个设备} other {# 个设备} }", + "unassign-device-from-edge-title": "确定要取消分配设备 '{{deviceName}}' 吗?", + "unassign-device-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。", + "unassign-devices-from-edge": "取消分配边缘", + "assign-new-device": "分配新设备", + "make-public-device-title": "确定要将设备 '{{deviceName}}' 设为公开吗?", + "make-public-device-text": "确认后,设备及其所有数据将被设为公开并可被其他人访问。", + "make-private-device-title": "确定要将设备 '{{deviceName}}' 设为私有吗?", + "make-private-device-text": "确认后,设备及其所有数据将被设为私有,不被其他人访问。", + "view-credentials": "查看凭据", + "delete-device-title": "确定要删除设备的{{deviceName}}吗?", + "delete-device-text": "请注意:确认后,设备及其所有相关数据将不可恢复。", + "delete-devices-title": "确定要删除{count,plural,1 {# 个设备} other {# 个设备} } 吗?", + "delete-devices-action-title": "删除 {count,plural,1 {# 个设备} other {# 个设备} }", + "delete-devices-text": "请注意:确认后,所有选定的设备将被删除,所有相关数据将不可恢复。", + "unassign-device-title": "确定要取消分配设备 '{{deviceName}}' 吗?", + "unassign-device-text": "确认后,设备将被取消分配,客户将无法访问。", + "unassign-device": "取消分配设备", + "unassign-devices-title": "确定要取消分配 {count,plural,1 {# 个设备} other {# 个设备} } 吗?", + "unassign-devices-text": "确认后,所有选定的设备将被取消分配,并且客户将无法访问。", + "device-credentials": "设备凭据", + "loading-device-credentials": "加载设备凭证...", + "credentials-type": "凭据类型", + "access-token": "访问令牌", + "access-token-required": "访问令牌必填", + "access-token-invalid": "访问令牌长度必须为1到32个字符。", + "certificate-pem-format": "PEM证书格式", + "certificate-pem-format-required": "PEM证书格式必填。", + "lwm2m-security-config": { + "identity": "客户端标识", + "identity-required": "客户端标识必填。", + "identity-tooltip": "PSK标识符是多达128字节的任意PSK标识符,如标准[RFC7925]所述。", + "client-key": "客户端密钥", + "client-key-required": "客户端密钥必填。", + "client-key-tooltip-prk": "RPK公钥必须满足标准[RFC7250]并且Base64编码!", + "client-key-tooltip-psk": "PSK密钥必须是[RFC4279]标准和HexDec格式: 32, 64, 128字符!", + "endpoint": "客户端名称", + "endpoint-required": "客户端名称必填。", + "client-public-key": "客户端公钥", + "client-public-key-hint": "如果客户端公钥为空则使用信任证书", + "client-public-key-tooltip": "X509公钥必须是DER-encoded X509v3格式并支持EC算法编码Base64!", + "mode": "安全配置模式", + "client-tab": "客户端安全配置", + "client-certificate": "客户端证书", + "bootstrap-tab": "Bootstrap Client", + "bootstrap-server": "Bootstrap Server", + "lwm2m-server": "LwM2M Server", + "client-publicKey-or-id": "客户端公钥或ID", + "client-publicKey-or-id-required": "客户端公钥或ID必填。", + "client-publicKey-or-id-tooltip-psk": "SK标识符是最多128字节,如标准[RFC7925]所述。", + "client-publicKey-or-id-tooltip-rpk": "RPK必须满足标准[RFC7250]而且编码为 Base64!", + "client-publicKey-or-id-tooltip-x509": "X509公钥必须是X509v3格式而且支持EC算法", + "client-secret-key": "客户端密钥", + "client-secret-key-required": "客户端密钥必填。", + "client-secret-key-tooltip-psk": "PSK键必须是[RFC4279]标准和HexDec格式: 32, 64, 128i字符!", + "client-secret-key-tooltip-prk": "RPK密钥必须是PKCS_8格式(DER编码, 标准[RFC5958]) 并编码为Base64!", + "client-secret-key-tooltip-x509": "X509密钥必须是PKCS_8格式(DER编码, 标准[RFC5958])!" + }, + "client-id": "客户端ID", + "client-id-pattern": "包含无效字符。", + "user-name": "用户名", + "user-name-required": "用户名必填。", + "client-id-or-user-name-necessary": "客户端ID和/或用户名是必需的", + "password": "密码", + "secret": "密钥", + "secret-required": "密钥必填", + "device-type": "设备类型", + "device-type-required": "设备类型必填。", + "select-device-type": "选择设备类型", + "enter-device-type": "输入设备类型", + "any-device": "任意设备", + "no-device-types-matching": "未找到匹配 '{{entitySubtype}}' 的设备类型。", + "device-type-list-empty": "未选择设备类型", + "device-types": "设备类型", + "name": "名称", + "name-required": "名称必填。", + "name-max-length": "名称长度必须少于256个字符", + "label-max-length": "标签长度必须少于256个字符", + "description": "说明", + "label": "标签", + "events": "事件", + "details": "详情", + "copyId": "复制设备ID", + "copyAccessToken": "复制访问令牌", + "copy-mqtt-authentication": "复制MQTT凭据", + "idCopiedMessage": "设备ID已复制到剪贴板", + "accessTokenCopiedMessage": "设备访问令牌已复制到剪贴板", + "mqtt-authentication-copied-message": "设备MQTT身份验证已复制到剪贴板", + "assignedToCustomer": "分配给客户", + "unable-delete-device-alias-title": "无法删除设备别名", + "unable-delete-device-alias-text": "设备别名 '{{deviceAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", + "is-gateway": "是否网关", + "overwrite-activity-time": "覆盖已连接设备的活动时间", + "public": "公开", + "device-public": "设备公开", + "select-device": "选择设备", + "import": "导入设备", + "device-file": "设备文件", + "search": "查找设备", + "selected-devices": "已选择 { count, plural, 1 {# 个设备} other {# 个设备} }", + "device-configuration": "设备配置", + "transport-configuration": "传输配置", + "wizard": { + "device-wizard": "设备向导", + "device-details": "设备详细信息", + "new-device-profile": "新建设备配置", + "existing-device-profile": "选择已有设备配置", + "specific-configuration": "指定配置", + "customer-to-assign-device": "客户分配设备", + "add-credentials": "添加凭据" + }, + "unassign-devices-from-edge-title": "确定要取消分配 { count, plural, 1 {1 个设备} other {# 个设备} } 吗?", + "unassign-devices-from-edge-text": "确认后,设备将被取消分配,边缘将无法访问。" + }, + "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": { + "device-profile": "设备配置", + "device-profiles": "设备配置", + "all-device-profiles": "全部", + "add": "添加设备配置", + "edit": "编辑设备配置", + "device-profile-details": "设备配置详情", + "no-device-profiles-text": "未找到设备配置", + "search": "查找设备配置", + "selected-device-profiles": "已选择 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }", + "no-device-profiles-matching": "未找到与 '{{entity}}' 匹配的设备配置。", + "device-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": "名称是必需的。", + "type": "配置类型", + "type-required": "配置类型必填。", + "type-default": "默认", + "image": "设备配置图片", + "transport-type": "传输方式", + "transport-type-required": "传输方式必填。", + "transport-type-default": "默认", + "transport-type-default-hint": "支持基本MQTT、HTTP和CoAP传输", + "transport-type-mqtt": "MQTT", + "transport-type-mqtt-hint": "启用高级MQTT传输设置", + "transport-type-coap": "CoAP", + "transport-type-coap-hint": "启用高级 CoAP 传输设置", + "transport-type-lwm2m": "LWM2M", + "transport-type-lwm2m-hint": "LWM2M传输类型", + "transport-type-snmp": "SNMP", + "transport-type-snmp-hint": "指定 SNMP 传输配置", + "description": "说明", + "default": "默认", + "profile-configuration": "配置", + "transport-configuration": "传输配置", + "default-rule-chain": "默认规则链", + "mobile-dashboard": "移动端仪表板", + "mobile-dashboard-hint": "被移动端应用用作设备详情仪表板", + "select-queue-hint": "从下拉列表中选择或添加自定义名称。", + "delete-device-profile-title": "确定要删除 '{{deviceProfileName}}' 设备配置吗?", + "delete-device-profile-text": "请注意:确认后,设备配置和所有相关数据将不可恢复。", + "delete-device-profiles-title": "确定要删除 { count, plural, 1 {# 个设备配置} other {# 个设备配置} }吗?", + "delete-device-profiles-text": "请注意:确认后,所有选定的设备配置将被删除,所有相关数据将不可恢复。", + "set-default-device-profile-title": "确定要将设备配置 '{{deviceProfileName}}' 设为默认值吗?", + "set-default-device-profile-text": "确认后,设备配置将被标记为默认,并将用于未指定配置的新设备。", + "no-device-profiles-found": "未找到设备配置。", + "create-new-device-profile": "创建一个新的!", + "mqtt-device-topic-filters": "MQTT 设备 Topic 筛选器", + "mqtt-device-topic-filters-unique": "MQTT设备 Topic 筛选器必须唯一。", + "mqtt-device-payload-type": "MQTT 设备 Payload", + "mqtt-device-payload-type-json": "JSON", + "mqtt-device-payload-type-proto": "Protobuf", + "mqtt-enable-compatibility-with-json-payload-format": "启用与其他payload格式兼容。", + "mqtt-enable-compatibility-with-json-payload-format-hint": "启用后平台将默认使用Protobuf的payload格式,如果解析失败平台将尝试使用JSON的payload格式。对于固件更新期间的向后兼容性很有用,例如固件的初始版本使用Json而新版本使用Protobuf在设备队列的固件更新过程中,需要同时支持Protobuf和JSON。兼容模式会导致一点的性能下降,因此建议在所有设备更新后禁用此模式。", + "mqtt-use-json-format-for-default-downlink-topics": "缺省下行主题采用json格式", + "mqtt-use-json-format-for-default-downlink-topics-hint": "启用后平台将使用Json的playload格式通过以下主题推送属性和RPC:v1/devices/me/attributes/response/$request_id、v1/devices/me/attributes 、v1/devices/me/rpc/request/$request_id、v1/devices/me/rpc/response/$request_id。此设置不会影响使用新(v2)主题发送的属性和rpc订阅:v2/a/res/$request_id、v2/a、v2/r /req/$request_id,v2/r/res/$request_id。其中$request_id是一个整数请求标识符。", + "mqtt-send-ack-on-validation-exception": "发布消息验证失败时发送PUBACK", + "mqtt-send-ack-on-validation-exception-hint": "默认情况下平台将关闭相关消息验证失败的MQTT会话,启用后平台将发布确认而不是关闭会话。", + "snmp-add-mapping": "添加SNMP映射", + "snmp-mapping-not-configured": "没有配置时间序列/属性的OID映射", + "snmp-timseries-or-attribute-name": "映射时间序列/属性名称", + "snmp-timseries-or-attribute-type": "映射时间序列/属性名称", + "snmp-method-pdu-type-get-request": "GetRequest", + "snmp-method-pdu-type-get-next-request": "GetNextRequest", + "snmp-oid": "OID", + "transport-device-payload-type-json": "JSON", + "transport-device-payload-type-proto": "Protobuf", + "mqtt-payload-type-required": "Payload 类型必填。", + "coap-device-type": "CoAP 设备类型", + "coap-device-payload-type": "CoAP 设备消息 Payload", + "coap-device-type-required": "CoAP 设备类型必填。", + "coap-device-type-default": "默认", + "coap-device-type-efento": "Efento NB-IoT", + "support-level-wildcards": "支持单[+]和多级[#]通配符。", + "telemetry-topic-filter": "遥测数据 topic 筛选器", + "telemetry-topic-filter-required": "遥测数据 topic 筛选器必填。", + "attributes-topic-filter": "Attributes topic filter", + "attributes-topic-filter-required": "Attributes topic 筛选器必填。", + "telemetry-proto-schema": "遥测数据 proto schema", + "telemetry-proto-schema-required": "遥测数据 proto schema 必填。", + "attributes-proto-schema": "Attributes proto schema", + "attributes-proto-schema-required": "Attributes proto schema 必填。", + "rpc-response-proto-schema": "RPC 响应 proto schema", + "rpc-response-proto-schema-required": "RPC 响应 proto schema 必填。", + "rpc-response-topic-filter": "RPC响应 Topic 筛选器", + "rpc-response-topic-filter-required": "RPC响应 Topic 筛选器必填。", + "rpc-request-proto-schema": "RPC 请求 proto schema", + "rpc-request-proto-schema-required": "RPC 请求 proto schema 必填。", + "rpc-request-proto-schema-hint": "RPC 请求消息应始终包含字段:string method = 1; int32 requestId = 2; 和params = 3的任何数据类型。", + "not-valid-pattern-topic-filter": "无效的 Topic 筛选器模式", + "not-valid-single-character": "单级通配符的使用无效", + "not-valid-multi-character": "多级通配符的使用无效", + "single-level-wildcards-hint": "[+] is suitable for any topic filter level。例如:v1/devices/+/telemetry or +/devices/+/attributes。", + "multi-level-wildcards-hint": "[#]可以替换 topic filter 本身,并且必须是 topic 的最后一个符号。例如:# or v1/devices/me/#。", + "alarm-rules": "告警规则", + "alarm-rules-with-count": "告警规则 ({{count}})", + "no-alarm-rules": "未配置告警规则", + "add-alarm-rule": "添加告警规则", + "edit-alarm-rule": "编辑告警规则", + "alarm-type": "告警类型", + "alarm-type-required": "告警类型必填。", + "alarm-type-unique": "警报类型在设备配置警报规则中必须唯一。", + "alarm-type-max-length": "Alarm type should be less than 256", + "create-alarm-pattern": "创建 {{alarmType}} 告警", + "create-alarm-rules": "创建告警规则", + "no-create-alarm-rules": "未配置创建条件", + "add-create-alarm-rule-prompt": "请添加创建告警规则", + "clear-alarm-rule": "清除告警规则", + "no-clear-alarm-rule": "未配置明确条件", + "add-create-alarm-rule": "添加创建条件", + "add-clear-alarm-rule": "添加清除条件", + "select-alarm-severity": "选择告警严重性", + "alarm-severity-required": "告警严重级别必填。", + "condition-duration": "条件持续时间", + "condition-duration-value": "持续时间值", + "condition-duration-time-unit": "时间单位", + "condition-duration-value-range": "持续时间值应在1到2147483647之间。", + "condition-duration-value-pattern": "持续时间值应为整数。", + "condition-duration-value-required": "持续时间值必填。", + "condition-duration-time-unit-required": "时间单位必填。", + "advanced-settings": "高级设置", + "alarm-rule-details": "详情", + "alarm-rule-details-hint": "提示:使用${keyName}警报条件中属性值或报文关键字。", + "add-alarm-rule-details": "详情模板:", + "alarm-rule-mobile-dashboard": "移动仪表盘", + "alarm-rule-mobile-dashboard-hint": "用于警报详细报告", + "alarm-rule-no-mobile-dashboard": "未选择仪表板", + "propagate-alarm": "传递警报", + "alarm-rule-relation-types-list": "要传递的关联类型", + "alarm-rule-relation-types-list-hint": "如果未选择传递关联类型,则将不按关联类型过滤而传递告警。", + "propagate-alarm-to-owner": "向实体所有者(客户或租户)传播警报", + "propagate-alarm-to-tenant": "向租户传播警报", + "alarm-details": "告警详细信息", + "alarm-rule-condition": "告警规则条件", + "enter-alarm-rule-condition-prompt": "请添加告警规则条件", + "edit-alarm-rule-condition": "编辑告警规则条件", + "device-provisioning": "设备预配置", + "provision-strategy": "预配置策略", + "provision-strategy-required": "预配置策略必填。", + "provision-strategy-disabled": "禁用", + "provision-strategy-created-new": "允许创建新设备", + "provision-strategy-check-pre-provisioned": "检查预配置的设备", + "provision-device-key": "预配置设备密钥名", + "provision-device-key-required": "预配置设备密钥名必填。", + "copy-provision-key": "复制预配置密钥名", + "provision-key-copied-message": "预配置密钥名已复制到剪贴板", + "provision-device-secret": "预配置设备密钥", + "provision-device-secret-required": "预配置设备密钥必填。", + "copy-provision-secret": "复制预配置密钥", + "provision-secret-copied-message": "预配置密钥已复制到剪贴板", + "condition": "条件", + "condition-type": "条件类型", + "condition-type-simple": "简单", + "condition-type-duration": "持续时间", + "condition-during": "在 {{during}} 期间", + "condition-during-dynamic": "在\"{{ attribute }}\" ({{during}})", + "condition-type-repeating": "重复", + "condition-type-required": "条件类型必填。", + "condition-repeating-value": "事件计数", + "condition-repeating-value-range": "事件计数应在1到2147483647之间。", + "condition-repeating-value-pattern": "事件计数应为整数。", + "condition-repeating-value-required": "事件计数值必填。", + "condition-repeat-times": "重复 { count, plural, 1 {# 次} other {# 次} }", + "condition-repeat-times-dynamic": "重复\"{ attribute }\" ({ count, plural, 1 {1 时间} other {# 时间} })", + "schedule-type": "计划程序类型", + "schedule-type-required": "计划类型必填。", + "schedule": "启用规则:", + "edit-schedule": "编辑告警日程表", + "schedule-any-time": "始终启用", + "schedule-specific-time": "定时启用", + "schedule-custom": "自定义启用", + "schedule-day": { + "monday": "星期一", + "tuesday": "星期二", + "wednesday": "星期三", + "thursday": "星期四", + "friday": "星期五", + "saturday": "星期六", + "sunday": "星期日" + }, + "schedule-days": "天", + "schedule-time": "时间", + "schedule-time-from": "从", + "schedule-time-to": "到", + "schedule-days-of-week-required": "每周至少选择一天。", + "create-device-profile": "创建设备配置", + "import": "导入设备配置", + "export": "导出设备配置", + "export-failed-error": "无法导出设备配置文件: {{error}}", + "device-profile-file": "设备配置文件", + "invalid-device-profile-file-error": "无法导入设备配置:无效的设备配置数据结构。", + "power-saving-mode": "节能模式", + "power-saving-mode-type": { + "default": "使用设备配置的节能模式", + "psm": "节能模式(PSM)", + "drx": "非连续接收(DRX)", + "edrx": "连续接收(eDRX)" + }, + "edrx-cycle": "eDRX循环", + "edrx-cycle-required": "eDRX循环必填。", + "edrx-cycle-pattern": "eDRX循环必须是一个正整数。", + "edrx-cycle-min": "eDRX循环的最小值{{ min }}秒。", + "paging-transmission-window": "分页传输窗口", + "paging-transmission-window-required": "分页传输窗口必填。", + "paging-transmission-window-pattern": "分页传输窗口必须是正整数。", + "paging-transmission-window-min": "分页传输窗口的最小值{{ min }}秒。", + "psm-activity-timer": "PSM活动计时器", + "psm-activity-timer-required": "PSM活动计时器必填。", + "psm-activity-timer-pattern": "PSM活动计时器必须是正整数", + "psm-activity-timer-min": "PSM活动计时器的最小数量为{{ min }}秒。", + "lwm2m": { + "object-list": "Object列表", + "object-list-empty": "没有选择边object。", + "no-objects-found": "没有选择边object。", + "no-objects-matching": "没有找到匹配的object'{{object}}'。", + "model-tab": "LWM2M模式", + "add-new-instances": "添加新实例", + "instances-list": "实例列表", + "instances-list-required": "实例列表必填。", + "instance-id-pattern": "实例id必须是一个正整数。", + "instance-id-max": "实例id最大值是{{max}}", + "instance": "实例", + "resource-label": "#I资源D名称", + "observe-label": "观察者", + "attribute-label": "属性", + "telemetry-label": "遥测", + "edit-observe-select": "选择观察者编辑遥测或属性", + "edit-attributes-select": "选择要编辑的遥测或属性", + "no-attributes-set": "没有设置属性", + "key-name": "键名", + "key-name-required": "键名必填", + "attribute-name": "属性名称", + "attribute-name-required": "属性名称必填。", + "attribute-value": "属性值", + "attribute-value-required": "属性值必填。", + "attribute-value-pattern": "属性值必须是一个正整数。", + "edit-attributes": "编辑属性:{{ name }}", + "view-attributes": "查看属性:{{ name }}", + "add-attribute": "添加属性", + "edit-attribute": "编辑属性", + "view-attribute": "查看属性", + "remove-attribute": "移除属性", + "delete-server-text": "请注意确认删除后服务器配置将无法恢复。", + "delete-server-title": "你确定要删除服务器吗?", + "mode": "安全配置模式", + "bootstrap-tab": "Bootstrap", + "bootstrap-server-legend": "Bootstrap Server (ShortId...)", + "lwm2m-server-legend": "LwM2M Server (ShortId...)", + "server": "服务器", + "short-id": "服务器ID", + "short-id-tooltip": "服务器ID用作关联服务器对象实例的链接。", + "short-id-required": "服务器ID必填。", + "short-id-range": "服务器ID应在1到65534范围内。", + "short-id-pattern": "服务器ID必须是一个正整数。", + "lifetime": "客户端注册生命周期", + "lifetime-required": "客户端注册生命周期必填。", + "lifetime-pattern": "客户端注册生命周期必须是一个正整数。", + "default-min-period": "最小期限", + "default-min-period-tooltip": "LWM2M客户端在观察中不包含此参数时使用的默认值。", + "default-min-period-required": "最小期限必填。", + "default-min-period-pattern": "最小期限必须是一个正整数。", + "notification-storing": "禁用或离线时通知存储", + "binding": "绑定", + "binding-type": { + "u": "U: 客户端通过UDP绑定。", + "m": "M: 客户端通过MQTT绑定。", + "h": "H: 客户端通过HTTP绑定。", + "t": "T: 客户端通过TCP绑定。", + "s": "S: 客户端通过SMS绑定。", + "n": "N: 客户端通过非IP绑定将响应发送到请求(支持LWM2M 1.1)。", + "uq": "UQ: 通过UDP队列模式连接(不支持LWM2M 1.1)。", + "uqs": "UQS: 通过UDP和SMS活动连接(不支持LWM2M 1.1)。", + "tq": "TQ: 通过TCP队列模式连接(不支持LWM2M 1.1)。", + "tqs": "TQS: 通过TCP和SMS活动连接(不支持LWM2M 1.1)。", + "sq": "SQ: 通过队列模式的SMS连接(不支持LWM2M 1.1)。" + }, + "binding-tooltip": "这是LwM2M服务器对象的\"绑定\"资源列表 - /1/x/7。\n表示LwM2M客户端中支持的绑定模式。\n此值应与设备对象(/3/0/16)中的\"支持的绑定和模式\"资源。\n虽然支持多个传输但在整个传输会话期间只能使用一个传输绑定。\n 例如:当UDP和SMS都受支持,LwM2M客户端和LwM2M服务器可以选择在整个传输会话期间通过UDP或SMS进行通信。", + "bootstrap-server": "Bootstrap Server", + "lwm2m-server": "LwM2M Server", + "include-bootstrap-server": "包含Bootstrap Server更新", + "bootstrap-update-title": "你已经配置了Bootstrap Server,您确定要排除更新吗?", + "bootstrap-update-text": "请注意确认更新后Bootstrap Server配置数据将无法恢复。", + "server-host": "主机", + "server-host-required": "主机必填。", + "server-port": "端口", + "server-port-required": "端口必填。", + "server-port-pattern": "端口必须是一个正整数。", + "server-port-range": "端口应在1到65535范围内。", + "server-public-key": "服务器公钥", + "server-public-key-required": "服务器公钥必填。", + "client-hold-off-time": "停留时间", + "client-hold-off-time-required": "停留时间必填。", + "client-hold-off-time-pattern": "停留时间必须是一个正整数。", + "client-hold-off-time-tooltip": "客户端仅与Bootstrap-Server共用停留时间", + "account-after-timeout": "帐户超时", + "account-after-timeout-required": "帐户超时必填。", + "account-after-timeout-pattern": "帐户超时必须是一个正整数。", + "account-after-timeout-tooltip": "Bootstrap-Server帐户资源的超时值。", + "server-type": "Server type", + "add-new-server-title": "添加新的服务器配置", + "add-server-config": "添加服务器配置", + "add-lwm2m-server-config": "添加LwM2M服务器", + "no-config-servers": "没有服务器配置", + "others-tab": "其它设置", + "client-strategy": "客户端连接策略", + "client-strategy-label": "策略", + "client-strategy-only-observe": "只在初始连接后观察对客户的请求", + "client-strategy-read-all": "注册后阅读所有资源并观察对客户的请求", + "fw-update": "固件升级", + "fw-update-strategy": "固件升级策略", + "fw-update-strategy-data": "发布固件升级二制文件使用Object 19和Resource 0数据。", + "fw-update-strategy-package": "发布固件升级二制文件使用Object 5和Resource 0包", + "fw-update-strategy-package-uri": "自动生成唯一的CoAP地址下载包和发布软件更新作为Object 5和Resource 1(软件包URI)。", + "sw-update": "软件更新", + "sw-update-strategy": "软件更新策略", + "sw-update-strategy-package": "发布二制文件使用Object 9和Resource 2(包)", + "sw-update-strategy-package-uri": "自动生成唯一的CoAP地址下载包和发布软件更新作为Object 9和Resource 3(软件包URI)。", + "fw-update-resource": "固件更新COAP资源", + "fw-update-resource-required": "固件更新COAP资源必填。", + "sw-update-resource": "软件更新COAP资源", + "sw-update-resource-required": "软件更新COAP资源必填。", + "config-json-tab": "设备配置JSON", + "attributes-name": { + "min-period": "最小期限", + "max-period": "最大期限", + "greater-than": "大于", + "less-than": "少于", + "step": "停止", + "min-evaluation-period": "最小期限评估", + "max-evaluation-period": "最大期限评估" + }, + "composite-operations-support": "支持Read/Write/Observe操作" + }, + "snmp": { + "add-communication-config": "添加通信配置", + "add-mapping": "添加映射", + "authentication-passphrase": "身份验证密码", + "authentication-passphrase-required": "身份验证密码必填。", + "authentication-protocol": "身份验证协议", + "authentication-protocol-required": "身份验证协议必填。", + "communication-configs": "通信配置", + "community": "Community字符串", + "community-required": "Community字符串必填。", + "context-name": "上下文名称", + "data-key": "数据键", + "data-key-required": "数据键必填。", + "data-type": "数据类型", + "data-type-required": "数据类型必填。", + "engine-id": "引擎ID", + "host": "主机", + "host-required": "主机必填。", + "oid": "OID", + "oid-pattern": "无效OID格式", + "oid-required": "OID必填。", + "please-add-communication-config": "请添加通信配置", + "please-add-mapping-config": "请添加映射配置", + "port": "端口", + "port-format": "端口格式无效", + "port-required": "端口必填。", + "privacy-passphrase": "私有密码", + "privacy-passphrase-required": "私有密码必填。", + "privacy-protocol": "私有协议", + "privacy-protocol-required": "私有协议必填。", + "protocol-version": "协议版本", + "protocol-version-required": "协议版本必填。", + "querying-frequency": "查询频率(ms)", + "querying-frequency-invalid-format": "查询频率必须是一个正整数。", + "querying-frequency-required": "查询频率必填。", + "retries": "重试", + "retries-invalid-format": "重试必须是一个正整数。", + "retries-required": "重试必填。", + "scope": "范围", + "scope-required": "范围必填。", + "security-name": "Security名称", + "security-name-required": "Security名称必填。", + "timeout-ms": "超时(ms)", + "timeout-ms-invalid-format": "超时必须是一个正整数。", + "timeout-ms-required": "超时必填。", + "user-name": "用户名", + "user-name-required": "用户名必填。" + } + }, + "dialog": { + "close": "关闭对话框" + }, + "direction": { + "column": "列", + "row": "排" + }, + "edge": { + "edge": "边缘", + "edge-instances": "边缘实例", + "edge-file": "边缘文件", + "name-max-length": "名称长度必须少于256个字符", + "label-max-length": "标签长度必须少于256个字符", + "type-max-length": "类型长度必须少于256个字符", + "management": "边缘管理", + "no-edges-matching": "未找到匹配的边缘 '{{entity}}'。", + "add": "增加边缘", + "no-edges-text": "未找到边缘", + "edge-details": "边缘详情", + "add-edge-text": "增加新的边缘", + "delete": "删除边缘", + "delete-edge-title": "确定删除边缘 '{{edgeName}}'吗?", + "delete-edge-text": "当心, 确认后,边缘以及所有关联数据将不可恢复。", + "delete-edges-title": "确定删除 { count, plural, 1 {1 个边缘} other {# 个边缘} }吗?", + "delete-edges-text": "当心, 确认后,选定的边缘以及所有关联数据将不可恢复。", + "name": "名称", + "name-starts-with": "边缘名称前缀", + "name-required": "名称必填。", + "description": "说明", + "details": "详情", + "events": "事件", + "copy-id": "复制边缘编号", + "id-copied-message": "边缘编号已经复制到剪切板", + "sync": "同步边缘", + "edge-required": "边缘必填。", + "edge-type": "边缘类型", + "edge-type-required": "边缘类型必填。", + "event-action": "事件行动", + "entity-id": "实体编号", + "select-edge-type": "选择边缘类型", + "assign-to-customer": "分配给客户", + "assign-to-customer-text": "请选择需要分配给边缘的客户", + "assign-edge-to-customer": "分配边缘给客户", + "assign-edge-to-customer-text": "请选择需要分配给边缘的客户", + "assignedToCustomer": "分配给客户", + "edge-public": "边缘公开", + "assigned-to-customer": "分配给: {{customerTitle}}", + "unassign-from-customer": "取消分配客户", + "unassign-edge-title": "确定取消分配边缘 '{{edgeName}}' 吗?", + "unassign-edge-text": "确定后,边缘将被取消分配,并且客户将无法访问。", + "unassign-edges-title": "确定要取消分配 {count,plural,1 {1 个边缘} other {# 个边缘} } 吗?", + "unassign-edges-text": "确定后,所有选定的边缘将被取消分配,并且客户将无法访问。", + "make-public": "公开", + "make-public-edge-title": "确定要将边缘 '{{edgeName}}' 设为公开吗?", + "make-public-edge-text": "确认后,边缘及其所有数据将被设为公开并可被其他人访问。", + "make-private": "私有", + "public": "公开", + "make-private-edge-title": "确定要将边缘 '{{edgeName}}' 设为私有吗?", + "make-private-edge-text": "确认后,边缘及其所有数据将被设为私有,不被其他人访问。", + "import": "导入边缘", + "label": "标签", + "load-entity-error": "加载数据失败,实体已经被删除。", + "assign-new-edge": "分配新边缘", + "unassign-from-edge": "取消分配边缘", + "edge-key": "边缘键", + "copy-edge-key": "复制边缘键", + "edge-key-copied-message": "边缘键已经被复制到剪切板", + "edge-secret": "边缘密钥", + "copy-edge-secret": "复制边缘密钥", + "edge-secret-copied-message": "边缘密钥已经被复制到剪切板", + "edge-assets": "边缘资产", + "edge-devices": "边缘设备", + "edge-entity-views": "边缘实体视图", + "edge-dashboards": "边缘仪表板", + "edge-rulechains": "边缘规则链", + "assets": "边缘资产", + "devices": "边缘设备", + "entity-views": "边缘实体视图", + "dashboard": "边缘仪表板", + "dashboards": "边缘仪表板", + "rulechain-templates": "规则链模版", + "rulechains": "规则链", + "search": "搜索边缘", + "selected-edges": "{ count, plural, 1 {1 个边缘} other {# 个边缘} } 被选中", + "any-edge": "任何边缘", + "no-edge-types-matching": "未找到匹配的边缘类型 '{{entitySubtype}}'。", + "edge-type-list-empty": "没有选择边缘类型。", + "edge-types": "边缘类型", + "enter-edge-type": "输入边缘类型", + "deployed": "已部署", + "pending": "待定", + "downlinks": "下行", + "no-downlinks-prompt": "未找到下行", + "sync-process-started-successfully": "同步处理开始成功!", + "missing-related-rule-chains-title": "边缘缺少关联规则链", + "missing-related-rule-chains-text": "分配给边缘的规则链使用规则节点将消息转发给未分配给当前边缘的规则链。

缺少的规则链列表:
{{missingRuleChains}}", + "widget-datasource-error": "组件只支持边缘实体数据源" + }, + "edge-event": { + "type-dashboard": "仪表板", + "type-asset": "资产", + "type-device": "设备", + "type-device-profile": "设备概要", + "type-asset-profile": "Asset Profile", + "type-entity-view": "实体视图", + "type-alarm": "告警", + "type-rule-chain": "规则链", + "type-rule-chain-metadata": "规则链元数据", + "type-edge": "边缘", + "type-user": "用户", + "type-customer": "客户", + "type-relation": "关联", + "type-widgets-bundle": "部件包", + "type-widgets-type": "部件类型", + "type-admin-settings": "管理员设置", + "action-type-added": "增加", + "action-type-deleted": "删除", + "action-type-updated": "更新", + "action-type-post-attributes": "推送属性", + "action-type-attributes-updated": "属性更新", + "action-type-attributes-deleted": "属性删除", + "action-type-timeseries-updated": "时序更新", + "action-type-credentials-updated": "认证更新", + "action-type-assigned-to-customer": "分配给客户", + "action-type-unassigned-from-customer": "取消分配客户", + "action-type-relation-add-or-update": "关联增加或更新", + "action-type-relation-deleted": "关联删除", + "action-type-rpc-call": "RPC调用", + "action-type-alarm-ack": "告警确认", + "action-type-alarm-clear": "告警清除", + "action-type-assigned-to-edge": "分配给边缘", + "action-type-unassigned-from-edge": "取消分配边缘", + "action-type-credentials-request": "认证请求", + "action-type-entity-merge-request": "实体合并请求" + }, + "error": { + "unable-to-connect": "无法连接到服务器!请检查您的互联网连接。", + "unhandled-error-code": "未处理的错误代码: {{errorCode}}", + "unknown-error": "未知错误" + }, + "entity": { + "entity": "实体", + "entities": "实体", + "entities-count": "实体数量", + "aliases": "实体别名", + "entity-alias": "实体别名", + "unable-delete-entity-alias-title": "无法删除实体别名", + "unable-delete-entity-alias-text": "实体别名 '{{entityAlias}}' 被以下部件使用不能删除:
{{widgetsList}}", + "duplicate-alias-error": "别名 '{{alias}}' 重复。
同一仪表板别名必须唯一。", + "missing-entity-filter-error": "别名 '{{alias}}' 缺少筛选器", + "configure-alias": "别名 '{{alias}}' 配置", + "alias": "别名", + "alias-required": "实体别名必填。", + "remove-alias": "移除实体别名", + "add-alias": "添加实体别名", + "entity-list": "实体列表", + "entity-type": "实体类型", + "entity-types": "实体类型", + "entity-type-list": "实体类型列表", + "any-entity": "任意实体", + "enter-entity-type": "输入实体类型", + "no-entities-matching": "未找到匹配 '{{entity}}' 的实体。", + "no-entity-types-matching": "未找到匹配 '{{entityType}}' 类型的实体。", + "name-starts-with": "名称开始于", + "help-text": "根据需要可以使用'%'进行匹配,例如:'%entity_name_contains%', '%entity_name_ends', 'entity_starts_with'。", + "use-entity-name-filter": "用户筛选器", + "entity-list-empty": "没有选择实体。", + "entity-type-list-empty": "没有选择实体类型。", + "entity-name-filter-required": "实体名筛选器必填。", + "entity-name-filter-no-entity-matched": "未找到以 '{{entity}}' 开头的实体", + "all-subtypes": "全部", + "select-entities": "选择实体", + "no-aliases-found": "未找到别名", + "no-alias-matching": "未找到 '{{alias}}'", + "create-new-alias": "创建一个新的!", + "key": "键名", + "key-name": "键名", + "no-keys-found": "未找到键名", + "no-key-matching": "未找到键名 '{{key}}'", + "create-new-key": "创建一个新的!", + "type": "类型", + "type-required": "实体类型必填。", + "type-device": "设备", + "type-devices": "设备", + "list-of-devices": "{ count, plural, 1 {1 个设备} other {# 个设备} }", + "device-name-starts-with": "以 '{{prefix}}' 开头的设备", + "type-device-profile": "设备配置", + "type-device-profiles": "设备配置", + "list-of-device-profiles": "{ count, plural, 1 {1 个设备配置} other {# 个设备配置} }", + "device-profile-name-starts-with": "名称以 '{{prefix}}' 开头的设备配置", + "type-asset-profile": "资产配置", + "type-asset-profiles": "资产配置", + "list-of-asset-profiles": "{ count, plural, 1 {One asset profile} other {List of # asset profiles} }", + "asset-profile-name-starts-with": "Asset profiles whose names start with '{{prefix}}'", + "type-asset": "资产", + "type-assets": "资产", + "list-of-assets": "{ count, plural, 1 {1 个资产} other {# 个资产} }", + "asset-name-starts-with": "以 '{{prefix}}' 开头的资产", + "type-entity-view": "实体视图", + "type-entity-views": "实体视图", + "list-of-entity-views": "{ count, plural, 1 {1 个实体视图} other {# 个实体视图} }", + "entity-view-name-starts-with": "以 '{{prefix}}' 开头的实体视图", + "type-rule": "规则", + "type-rules": "规则", + "list-of-rules": "{ count, plural, 1 {1 个规则} other {# 个规则} }", + "rule-name-starts-with": "以 '{{prefix}}' 开头的规则", + "type-plugin": "插件", + "type-plugins": "插件", + "list-of-plugins": "{ count, plural, 1 {1 个插件} other {# 个插件} }", + "plugin-name-starts-with": "以 '{{prefix}}' 开头的插件", + "type-tenant": "租户", + "type-tenants": "租户", + "list-of-tenants": "{ count, plural, 1 {1 个租户} other {# 个租户} }", + "tenant-name-starts-with": "以 '{{prefix}}' 开头的租户", + "type-tenant-profile": "租户简介", + "type-tenant-profiles": "租户配置", + "list-of-tenant-profiles": "{ count, plural, 1 {1 个租户配置} other {# 个租户配置} }", + "tenant-profile-name-starts-with": "名称以 '{{prefix}}' 开头的租户配置", + "type-customer": "客户", + "type-customers": "客户", + "list-of-customers": "{ count, plural, 1 {1 个客户} other {# 个客户} }", + "customer-name-starts-with": "以 '{{prefix}}' 开头的客户", + "type-user": "用户", + "type-users": "用户", + "list-of-users": "{ count, plural, 1 {1 个用户} other {# 个用户} }", + "user-name-starts-with": "以 '{{prefix}}' 开头的用户", + "type-dashboard": "仪表板", + "type-dashboards": "仪表板", + "list-of-dashboards": "{ count, plural, 1 {1 个仪表板} other {# 个仪表板} }", + "dashboard-name-starts-with": "以 '{{prefix}}' 开头的仪表板", + "type-alarm": "告警", + "type-alarms": "告警", + "list-of-alarms": "{ count, plural, 1 {1 个告警} other {# 个告警} }", + "alarm-name-starts-with": "以 '{{prefix}}' 开头的告警", + "type-rulechain": "规则链", + "type-rulechains": "规则链库", + "list-of-rulechains": "{ count, plural, 1 {1 个规则链} other {# 个规则链} }", + "rulechain-name-starts-with": "规则链前缀名称 '{{prefix}}'", + "type-rulenode": "规则节点", + "type-rulenodes": "规则节点", + "list-of-rulenodes": "{ count, plural, 1 {1 个规则节点} other {# 个规则节点} }", + "rulenode-name-starts-with": "名称以 '{{prefix}}' 开头的规则节点", + "type-current-customer": "当前客户", + "type-current-tenant": "当前租户", + "type-current-user": "当前用户", + "type-current-user-owner": "当前用户所有者", + "type-widgets-bundle": "Widgets bundle", + "type-widgets-bundles": "Widgets bundles", + "list-of-widgets-bundles": "{ count, plural, 1 {1 个部件包} other {列表 # 部件包} }", + "search": "实体检索", + "selected-entities": "已选择 { count, plural, 1 {# 个实体} other {# 个实体} }", + "entity-name": "实体名", + "entity-label": "实体标签", + "details": "实体详情", + "no-entities-prompt": "未找到实体", + "no-data": "无数据", + "columns-to-display": "要显示的列", + "type-api-usage-state": "Api使用状态", + "type-edge": "边缘", + "type-edges": "边缘", + "list-of-edges": "{ count, plural, 1 {1 个边缘} other {列表 # 个边缘} }", + "edge-name-starts-with": "以 '{{prefix}}' 开头的边缘", + "type-tb-resource": "资源", + "type-ota-package": "OTA包" + }, + "entity-field": { + "created-time": "创建时间", + "name": "名称", + "type": "类型", + "first-name": "名字", + "last-name": "姓氏", + "email": "电子邮件", + "title": "标题", + "country": "国家", + "state": "省/州", + "city": "城市", + "address": "地址", + "address2": "地址二", + "zip": "邮政编码", + "phone": "电话", + "label": "标签" + }, + "entity-view": { + "entity-view": "实体视图", + "entity-view-required": "实体视图必填。", + "entity-views": "实体视图", + "management": "实体视图管理", + "view-entity-views": "查看实体视图", + "entity-view-alias": "实体视图别名", + "aliases": "实体视图别名", + "no-alias-matching": "未找到匹配 '{{alias}}' 的别名。", + "no-aliases-found": "未找到别名。", + "no-key-matching": "'{{key}}' 未找到。", + "no-keys-found": "未找到密钥。", + "create-new-alias": "创建一个新的!", + "create-new-key": "创建一个新的!", + "duplicate-alias-error": "找到重复别名 '{{alias}}'。
实体视图别名必须是唯一的。", + "configure-alias": "配置 '{{alias}}' 别名", + "no-entity-views-matching": "未找到与 '{{entity}}' 匹配的实体视图。", + "public": "公开", + "alias": "别名", + "alias-required": "实体视图别名必填。", + "remove-alias": "删除实体视图别名", + "add-alias": "添加实体视图别名", + "name-starts-with": "名称前缀", + "help-text": "根据需要可以使用'%'进行匹配,例如:'%entity-view_name_contains%', '%entity-view_name_ends', 'entity-view_starts_with'。", + "entity-view-list": "实体视图列表", + "use-entity-view-name-filter": "使用筛选器", + "entity-view-list-empty": "没有被选中的实体视图", + "entity-view-name-filter-required": "实体视图名称筛选器必填。", + "entity-view-name-filter-no-entity-view-matched": "未找到以'{{entityView}}' 开头的实体视图。", + "add": "添加实体视图", + "entity-view-public": "实体视图是公共的", + "assign-to-customer": "分配给客户", + "assign-entity-view-to-customer": "将实体视图分配给客户", + "assign-entity-view-to-customer-text": "请选择要分配给客户的实体视图", + "assign-entity-view-to-edge-title": "将实体视图分配给边缘", + "no-entity-views-text": "未找到实体视图", + "assign-to-customer-text": "请选择客户分配实体视图", + "entity-view-details": "实体视图详细信息", + "add-entity-view-text": "添加新实体视图", + "delete": "删除实体视图", + "assign-entity-views": "分配实体视图", + "assign-entity-views-text": "分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 给客户", + "delete-entity-views": "删除实体视图", + "unassign-from-customer": "取消分配客户", + "unassign-entity-views": "取消分配实体视图", + "unassign-entity-views-action-title": "从客户处取消分配{count,plural,1 {# 实体视图} other {# 实体视图} }", + "assign-new-entity-view": "分配新实体视图", + "delete-entity-view-title": "确定要删除实体视图 '{{entityViewName}}'吗?", + "delete-entity-view-text": "请注意:确认后实体视图及其所有相关数据将不可恢复。", + "delete-entity-views-title": "确定要删除 { count, plural, 1 {# 实体视图} other {# 实体视图} }吗?", + "delete-entity-views-action-title": "删除 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }", + "delete-entity-views-text": "请注意:确认后,所有选定的实体视图将被删除,所有相关的数据将不可恢复。", + "unassign-entity-view-title": "确定要取消对 '{{entityViewName}}' 实体视图的分配吗?", + "unassign-entity-view-text": "确认后,实体视图将未分配,客户无法访问。", + "unassign-entity-view": "未分配实体视图", + "unassign-entity-views-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 吗?", + "unassign-entity-views-text": "确认后,所有选定的实体视图将被分配,客户无法访问。", + "entity-view-type": "实体视图类型", + "entity-view-type-required": "实体视图类型必填。", + "select-entity-view-type": "选择实体视图类型", + "enter-entity-view-type": "输入实体视图类型", + "any-entity-view": "任何实体视图", + "no-entity-view-types-matching": "未找到匹配 '{{entitySubtype}}' 的实体视图类型。", + "entity-view-type-list-empty": "实体视图类型未选择。", + "entity-view-types": "实体视图类型", + "created-time": "创建时间", + "name": "名称", + "name-required": "名称必填。", + "name-max-length": "名称长度必须少于256个字符", + "type-max-length": "视图类型长度必须少于256个字符", + "description": "说明", + "events": "事件", + "details": "详情", + "copyId": "复制实体视图ID", + "idCopiedMessage": "实体视图ID已复制到剪贴板", + "assignedToCustomer": "分配给客户", + "unable-entity-view-device-alias-title": "无法删除实体视图别名", + "unable-entity-view-device-alias-text": "实体视图别名 '{{entityViewAlias}}' 不能够被删除,因为它被下列部件所使用:
{{widgetsList}}", + "select-entity-view": "选择实体视图", + "make-public": "实体视图设为公开", + "make-private": "实体视图设为私有", + "start-date": "开始日期", + "start-ts": "开始时间", + "end-date": "结束日期", + "end-ts": "结束时间", + "date-limits": "日期限制", + "client-attributes": "客户端属性", + "shared-attributes": "共享属性", + "server-attributes": "服务端属性", + "timeseries": "时间序列", + "client-attributes-placeholder": "客户端属性", + "shared-attributes-placeholder": "共享属性", + "server-attributes-placeholder": "服务端属性", + "timeseries-placeholder": "时间序列", + "target-entity": "目标实体", + "attributes-propagation": "属性传播", + "attributes-propagation-hint": "每次保存或更新这个实体视图时将自动从目标实体复制指定的属性。由于性能原因目标实体属性不会在每次属性更改时传递到实体视图。你可以通过配置\"copy to view\"规则链中的规则节点,并将\"Post attributes\"和\"attributes Updated\"消息链接到新规则节点,从而启用自动传递。", + "timeseries-data": "时间序列数据", + "timeseries-data-hint": "配置目标实体的 Timeseries 数据键,以便实体视图可以访问这些键。此 Timeseries 数据是只读的。", + "search": "查找实体视图", + "selected-entity-views": "已选择 { count, plural, 1 {# 个实体视图} other {# 个实体视图} }", + "make-public-entity-view-title": "确定要将实体视图 '{{entityViewName}}' 设为公开吗?", + "make-public-entity-view-text": "确认后,实体视图及其所有数据将被公开并被他人访问。", + "make-private-entity-view-title": "确定要将实体视图 '{{entityViewName}}' 设为私有吗?", + "make-private-entity-view-text": "确认后,实体视图及其所有数据将被私有化,无法被他人访问。", + "assign-entity-view-to-edge": "将实体视图分配给边缘", + "assign-entity-view-to-edge-text": "请选择要分配给边缘的实体视图", + "unassign-entity-view-from-edge-title": "确定要取消对 '{{entityViewName}}' 实体视图的分配吗?", + "unassign-entity-view-from-edge-text": "确认后,实体视图将未分配,边缘无法访问。", + "unassign-entity-views-from-edge-action-title": "从边缘处取消分配{count,plural,1 {# 实体视图} other {# 实体视图} }", + "unassign-entity-view-from-edge": "未分配实体视图", + "unassign-entity-views-from-edge-title": "确定要取消分配 { count, plural, 1 {# 个实体视图} other {# 个实体视图} } 吗?", + "unassign-entity-views-from-edge-text": "确认后,所有选定的实体视图将被分配,边缘无法访问。" + }, + "event": { + "event-type": "事件类型", + "events-filter": "事件筛选器", + "clean-events": "Clear Events", + "type-error": "错误", + "type-lc-event": "生命周期事件", + "type-stats": "类型统计", + "type-debug-rule-node": "调试", + "type-debug-rule-chain": "调试", + "no-events-prompt": "未找到事件", + "error": "错误", + "alarm": "告警", + "event-time": "事件时间", + "server": "服务器", + "body": "整体", + "method": "方法", + "type": "类型", + "message": "Message", + "message-id": "消息ID", + "copy-message-id": "Copy message Id", + "message-type": "消息类型", + "data-type": "数据类型", + "relation-type": "关联类型", + "metadata": "元数据", + "data": "数据", + "event": "事件", + "status": "状态", + "success": "成功", + "failed": "失败", + "messages-processed": "消息处理", + "max-messages-processed": "最多处理消息", + "min-messages-processed": "最少处理消息", + "errors-occurred": "错误发生", + "max-errors-occurred": "最多错误", + "min-errors-occurred": "最少错误", + "min-value": "最小值是0。", + "all-events": "全部", + "has-error": "有错误", + "entity-id": "实体ID", + "copy-entity-id": "复制实体ID", + "entity-type": "实体类型", + "clear-filter": "清除过滤器", + "clear-request-title": "清除所有事件", + "clear-request-text": "确除清空所有事件?" + }, + "extension": { + "extensions": "扩展", + "selected-extensions": "已选择 { count, plural, 1 {# 个扩展} other {# 个扩展} }", + "type": "类型", + "key": "键名", + "value": "价值", + "id": "ID", + "extension-id": "扩展ID", + "extension-type": "扩展类型", + "transformer-json": "JSON *", + "unique-id-required": "当前扩展ID已经存在。", + "delete": "删除扩展", + "add": "添加扩展", + "edit": "编辑扩展", + "delete-extension-title": "确定要删除扩展 '{{extensionId}}'吗?", + "delete-extension-text": "请注意:确认后,扩展和所有相关数据将不可恢复。", + "delete-extensions-title": "确定要删除 { count, plural, 1 {# 个扩展} other {# 个扩展} }吗?", + "delete-extensions-text": "请注意:确认后,所有选定的扩展将被删除,所有相关数据将不可恢复。", + "converters": "转换器", + "converter-id": "转换器序号", + "configuration": "配置", + "converter-configurations": "转换器的配置", + "token": "安全令牌", + "add-converter": "添加转换器", + "add-config": "添加转换器配置", + "device-name-expression": "设备名称表达式", + "device-type-expression": "设备类型表达式", + "custom": "定制", + "to-double": "加倍", + "transformer": "转换器", + "json-required": "Transformer JSON 必填。", + "json-parse": "无法解析转换器JSON。", + "attributes": "属性", + "add-attribute": "添加属性", + "add-map": "添加映射元素", + "timeseries": "Timeseries", + "add-timeseries": "添加 Timeseries", + "field-required": "必填字段", + "brokers": "代理服务器组", + "add-broker": "添加代理服务器", + "host": "主机", + "port": "端口", + "port-range": "端口应该在1到65535的范围内。", + "ssl": "SSL", + "credentials": "证书", + "username": "用户名", + "password": "密码", + "retry-interval": "重试间隔(毫秒)", + "anonymous": "匿名", + "basic": "Basic", + "pem": "PEM", + "ca-cert": "CA certificate file *", + "private-key": "Private key file *", + "cert": "Certificate file *", + "no-file": "没有选择文件。", + "drop-file": "删除文件或单击以选择要上载的文件。", + "mapping": "映射", + "topic-filter": "Topic筛选器", + "converter-type": "转换类型", + "converter-json": "Json", + "json-name-expression": "设备名称JSON表达式", + "topic-name-expression": "设备名称主题表达式", + "json-type-expression": "设备类型JSON表达式", + "topic-type-expression": "设备类型主题表达式", + "attribute-key-expression": "属性键名表达式", + "attr-json-key-expression": "属性键JSON表达式", + "attr-topic-key-expression": "属性键名Topic表达式", + "request-id-expression": "请求ID表达式", + "request-id-json-expression": "请求ID JSON表达式", + "request-id-topic-expression": "请求ID主题表达式", + "response-topic-expression": "响应主题表达式", + "value-expression": "值表达式", + "topic": "主题", + "timeout": "超时时间(毫秒)", + "converter-json-required": "Converter JSON 必填。", + "converter-json-parse": "无法解析转换JSON。", + "filter-expression": "筛选条件表达式", + "connect-requests": "连接请求", + "add-connect-request": "添加连接请求", + "disconnect-requests": "断开请求", + "add-disconnect-request": "添加断开请求", + "attribute-requests": "属性请求", + "add-attribute-request": "添加属性请求", + "attribute-updates": "属性更新", + "add-attribute-update": "添加属性更新", + "server-side-rpc": "服务端RPC", + "add-server-side-rpc-request": "添加服务端RPC请求", + "device-name-filter": "设备名称筛选器", + "attribute-filter": "属性筛选器", + "method-filter": "方法筛选器", + "request-topic-expression": "请求主题表达式", + "response-timeout": "响应超时(毫秒)", + "topic-expression": "主题表达", + "client-scope": "客户范围", + "add-device": "添加服务器", + "opc-server": "服务器组", + "opc-add-server": "添加服务器", + "opc-add-server-prompt": "请添加服务器", + "opc-application-name": "应用名称", + "opc-application-uri": "应用URI", + "opc-scan-period-in-seconds": "秒级扫描周期", + "opc-security": "安全性", + "opc-identity": "身份", + "opc-keystore": "密钥库", + "opc-type": "类型", + "opc-keystore-type": "类型", + "opc-keystore-location": "Location *", + "opc-keystore-password": "密码", + "opc-keystore-alias": "别名", + "opc-keystore-key-password": "密钥密码", + "opc-device-node-pattern": "设备节点模式", + "opc-device-name-pattern": "设备名称模式", + "modbus-server": "Servers/slaves", + "modbus-add-server": "添加 server/slave", + "modbus-add-server-prompt": "请添加 server/slave", + "modbus-transport": "Transport", + "modbus-tcp-reconnect": "自动重新连接", + "modbus-rtu-over-tcp": "RTU over TCP", + "modbus-port-name": "串口名称", + "modbus-encoding": "编码", + "modbus-parity": "奇偶性", + "modbus-baudrate": "波特率", + "modbus-databits": "数据位", + "modbus-stopbits": "停止位", + "modbus-databits-range": "数据位应该在7到8的范围内。", + "modbus-stopbits-range": "停止位应该在1到2的范围内。", + "modbus-unit-id": "单位编号", + "modbus-unit-id-range": "单位ID应该在1到247的范围内", + "modbus-device-name": "设备名称", + "modbus-poll-period": "轮询周期 (毫秒)", + "modbus-attributes-poll-period": "轮询属性周期 (毫秒)", + "modbus-timeseries-poll-period": "Timeseries 轮询周期 (毫秒)", + "modbus-poll-period-range": "轮询周期应为正值。", + "modbus-tag": "标签", + "modbus-function": "函数", + "modbus-register-address": "寄存器地址", + "modbus-register-address-range": "寄存器地址应该在0到65535的范围内。", + "modbus-register-bit-index": "位索引", + "modbus-register-bit-index-range": "位索引应该在0到15的范围内。", + "modbus-register-count": "寄存器计数", + "modbus-register-count-range": "寄存器计数应该是一个正值。", + "modbus-byte-order": "字节顺序", + "sync": { + "status": "状态", + "sync": "同步", + "not-sync": "不同步", + "last-sync-time": "最后同步时间", + "not-available": "无法使用" + }, + "export-extensions-configuration": "导出扩展配置", + "import-extensions-configuration": "导入扩展配置", + "import-extensions": "导入扩展", + "import-extension": "导入扩展", + "export-extension": "导出扩展", + "file": "扩展文件", + "invalid-file-error": "无效的扩展文件" + }, + "filter": { + "add": "添加筛选器", + "edit": "编辑筛选器", + "name": "筛选器名称", + "name-required": "筛选器名称必填。", + "duplicate-filter": "同名筛选器已存在。", + "filters": "筛选器", + "unable-delete-filter-title": "无法删除筛选器", + "unable-delete-filter-text": "无法删除筛选器 '{{filter}}' ,因为它由以下小部件使用:
{{widgetsList}}", + "duplicate-filter-error": "找到重复的筛选器 '{{filter}}'。
筛选器在仪表板中必须是唯一的。", + "missing-key-filters-error": "筛选器 '{{filter}}' 的键名筛选条件缺失。", + "filter": "筛选器", + "editable": "可编辑", + "no-filters-found": "未找到筛选器。", + "no-filter-text": "未指定筛选器", + "add-filter-prompt": "请添加筛选器", + "no-filter-matching": "未找到 '{{filter}}' 。", + "create-new-filter": "请新增!", + "filter-required": "筛选器必填。", + "operation": { + "operation": "操作", + "equal": "等于", + "not-equal": "不等于", + "starts-with": "开始于", + "ends-with": "结束于", + "contains": "包含", + "not-contains": "不包含", + "greater": "大于", + "less": "小于", + "greater-or-equal": "大于或等于", + "less-or-equal": "小于或等于", + "and": "和", + "or": "或", + "in": "匹配", + "not-in": "不匹配" + }, + "ignore-case": "忽略大小写", + "value": "值", + "remove-filter": "删除筛选器", + "preview": "筛选器预览", + "no-filters": "未配置筛选器", + "add-filter": "添加筛选器", + "add-complex-filter": "添加复合筛选器", + "add-complex": "添加复合", + "complex-filter": "复合筛选器", + "edit-complex-filter": "编辑复合筛选器", + "edit-filter-user-params": "编辑筛选器谓词用户参数", + "filter-user-params": "过滤谓词用户参数", + "user-parameters": "用户参数", + "display-label": "要显示的标签", + "autogenerated-label": "自动生成标签", + "order-priority": "字段顺序优先级", + "key-filter": "键名筛选器", + "key-filters": "键名筛选器", + "key-name": "键名", + "key-name-required": "键名必填。", + "key-type": { + "key-type": "键类型", + "attribute": "属性", + "timeseries": "Timeseries", + "entity-field": "实体", + "constant": "常量" + }, + "value-type": { + "value-type": "值类型", + "string": "字符串", + "numeric": "数字", + "boolean": "布尔值", + "date-time": "日期时间" + }, + "value-type-required": "键值类型是必需的。", + "key-value-type-change-title": "确定要更改键值类型吗?", + "key-value-type-change-message": "如果您确认新的值类型,所有输入的键过滤器将被删除。", + "no-key-filters": "未配置键名筛选器", + "add-key-filter": "添加键名筛选器", + "remove-key-filter": "删除键名筛选器", + "edit-key-filter": "编辑键名筛选器", + "date": "日期", + "time": "时间", + "current-tenant": "当前租户", + "current-customer": "当前客户", + "current-user": "当前用户", + "current-device": "当前设备", + "default-value": "默认值", + "dynamic-source-type": "动态源类型", + "dynamic-value": "动态值", + "no-dynamic-value": "无动态值", + "source-attribute": "源属性", + "switch-to-dynamic-value": "切换到动态值", + "switch-to-default-value": "切换到默认值", + "inherit-owner": "从所有者继承", + "source-attribute-not-set": "如果未设置源属性" + }, + "fullscreen": { + "expand": "展开到全屏", + "exit": "退出全屏", + "toggle": "切换全屏模式", + "fullscreen": "全屏" + }, + "function": { + "function": "函数" + }, + "gateway": { + "add-entry": "添加配置", + "connector-add": "添加新连接器", + "connector-enabled": "启用连接器", + "connector-name": "连接器名称", + "connector-name-required": "连接器名称名称必填。", + "connector-type": "连接器类型", + "connector-type-required": "连接器名称类型必填。", + "connectors": "连接器配置", + "create-new-gateway": "创建新网关", + "create-new-gateway-text": "确定要创建名为 '{{gatewayName}}' 的新网关?", + "delete": "删除配置", + "download-tip": "下载配置", + "gateway": "网关", + "gateway-exists": "同名设备已存在。", + "gateway-name": "网关名称", + "gateway-name-required": "网关名称必填。", + "gateway-saved": "已成功保存网关配置。", + "json-parse": "无效的JSON。", + "json-required": "字段不能为空。", + "no-connectors": "无连接器", + "no-data": "没有配置", + "no-gateway-found": "未找到网关。", + "no-gateway-matching": "未找到 '{{item}}' 。", + "path-logs": "日志文件的路径", + "path-logs-required": "路径是必需的。", + "remote": "远程配置", + "remote-logging-level": "日志记录级别", + "remove-entry": "删除配置", + "save-tip": "保存配置", + "security-type": "安全类型", + "security-types": { + "access-token": "访问令牌", + "tls": "TLS" + }, + "storage": "存储", + "storage-max-file-records": "文件中的最大记录数", + "storage-max-files": "最大文件数", + "storage-max-files-min": "最小值为1。", + "storage-max-files-pattern": "数字无效。", + "storage-max-files-required": "数字是必需的。", + "storage-max-records": "存储中的最大记录数", + "storage-max-records-min": "最小记录数为1。", + "storage-max-records-pattern": "数字无效。", + "storage-max-records-required": "最大记录项必填。", + "storage-pack-size": "最大事件包大小", + "storage-pack-size-min": "最小值为1。", + "storage-pack-size-pattern": "数字无效。", + "storage-pack-size-required": "最大事件包大小必填。", + "storage-path": "存储路径", + "storage-path-required": "存储路径必填。", + "storage-type": "存储类型", + "storage-types": { + "file-storage": "文件存储", + "memory-storage": "内存存储" + }, + "thingsboard": "ThingsBoard", + "thingsboard-host": "ThingsBoard主机", + "thingsboard-host-required": "主机必填。", + "thingsboard-port": "ThingsBoard端口", + "thingsboard-port-max": "最大端口号为65535。", + "thingsboard-port-min": "最小端口号为1。", + "thingsboard-port-pattern": "端口无效。", + "thingsboard-port-required": "端口必填。", + "tidy": "整洁", + "tidy-tip": "整理配置JSON", + "title-connectors-json": "连接器 {{typeName}} 配置", + "tls-path-ca-certificate": "网关上CA证书的路径", + "tls-path-client-certificate": "网关上客户端证书的路径", + "tls-path-private-key": "网关上私钥的路径", + "toggle-fullscreen": "切换全屏", + "transformer-json-config": "配置JSON*", + "update-config": "添加/更新配置JSON" + }, + "grid": { + "delete-item-title": "确定要删除此项吗?", + "delete-item-text": "请注意,确认后,项目及其所有相关数据将不可恢复。", + "delete-items-title": "确定删除 { count, plural, 1 {# 项} other {# 项} }吗?", + "delete-items-action-title": "删除 { count, plural, 1 {# 个元素} other {# 个元素} }", + "delete-items-text": "请注意,确认后所有选择的项目将被删除,所有相关数据将不可恢复。", + "add-item-text": "添加新项目", + "no-items-text": "未找到项目", + "item-details": "项目详细信息", + "delete-item": "删除项目", + "delete-items": "删除项目", + "scroll-to-top": "滚动到顶部" + }, + "help": { + "goto-help-page": "查看帮助", + "show-help": "显示帮助" + }, + "home": { + "home": "首页", + "profile": "属性", + "logout": "注销", + "menu": "菜单", + "avatar": "头像", + "open-user-menu": "打开用户菜单" + }, + "file-input": { + "browse-file": "浏览文件", + "browse-files": "浏览文件" + }, + "image-input": { + "drop-image-or": "拖拽图片或", + "drop-images-or": "拖拽图片或", + "no-images": "没有图片", + "images": "图片" + }, + "import": { + "no-file": "没有选择文件", + "drop-file": "拖动一个JSON文件或者单击以选择要上传的文件。", + "drop-json-file-or": "拖拽JSON文件或", + "drop-file-csv": "拖动一个CSV文件或单击以选择要上载的文件。", + "drop-file-csv-or": "拖拽CSV文件或", + "column-value": "数值", + "column-title": "标题", + "column-example": "示例值数据", + "column-key": "属性/遥测键", + "credentials": "Credentials", + "csv-delimiter": "CSV分隔符", + "csv-first-line-header": "第一行包含列名", + "csv-update-data": "更新属性/遥测", + "details": "Details", + "import-csv-number-columns-error": "一个文件至少应该包含两列", + "import-csv-invalid-format-error": "文件格式无效。行: '{{line}}'", + "column-type": { + "name": "名称", + "type": "类型", + "label": "标签", + "column-type": "列类型", + "client-attribute": "客户端属性", + "shared-attribute": "共享属性", + "server-attribute": "服务器属性", + "timeseries": "Timeseries", + "entity-field": "实体字段", + "access-token": "访问令牌", + "x509": "X.509", + "mqtt": { + "client-id": "MQTT客户端ID", + "user-name": "MQTT用户名", + "password": "MQTT密码" + }, + "lwm2m": { + "client-endpoint": "客户端终节点名称", + "security-config-mode": "安全配置模式", + "client-identity": "客户标识", + "client-key": "客户端公钥", + "client-cert": "客户端证书", + "bootstrap-server-security-mode": "LwM2M bootstrap server安全模式", + "bootstrap-server-secret-key": "LwM2M bootstrap server密钥", + "bootstrap-server-public-key-id": "LwM2M bootstrap server公钥", + "lwm2m-server-security-mode": "LwM2M server安全模式", + "lwm2m-server-secret-key": "LwM2M server证书密钥", + "lwm2m-server-public-key-id": "LwM2M server公钥" + }, + "isgateway": "是否网关", + "activity-time-from-gateway-device": "网关设备活动时间", + "description": "说明", + "routing-key": "边缘键", + "secret": "边缘密钥" + }, + "stepper-text": { + "select-file": "选择一个文件", + "configuration": "导入配置", + "column-type": "选择列类型", + "creat-entities": "创建新实体" + }, + "message": { + "create-entities": "{{count}} 个新实体已成功创建。", + "update-entities": "{{count}} 个实体已成功更新。", + "error-entities": "创建 {{count}} 个实体时出错。" + } + }, + "item": { + "selected": "选择" + }, + "js-func": { + "no-return-error": "函数必须返回值!", + "return-type-mismatch": "函数必须返回 '{{type}}' 类型的值!", + "tidy": "整洁", + "mini": "迷你" + }, + "key-val": { + "key": "键名", + "value": "数值", + "remove-entry": "删除条目", + "add-entry": "增加条目", + "no-data": "没有条目" + }, + "layout": { + "layout": "布局", + "manage": "布局管理", + "settings": "布局设置", + "color": "颜色", + "main": "主体", + "right": "右侧", + "left": "左", + "select": "选择目标布局", + "percentage-width": "百分比宽度(%)", + "fixed-width": "固定(px)", + "left-width": "左列(%)", + "right-width": "右列(%)", + "pick-fixed-side": "固定侧: ", + "layout-fixed-width": "固定宽度(px)", + "value-min-error": "值是必须大于{{min}}{{unit}}", + "value-max-error": "值是必须小于{{max}}{{unit}}", + "layout-fixed-width-required": "固定宽度必填", + "right-width-percentage-required": "右侧百分比宽度必填", + "left-width-percentage-required": "左侧百分比宽度必填", + "divider": "分隔线", + "right-side": "右侧布局", + "left-side": "左侧布局" + }, + "legend": { + "direction": "图例方向", + "position": "图例位置", + "sort-legend": "在图例中排序数据键", + "show-max": "显示最大值", + "show-min": "显示最小值", + "show-avg": "显示平均值", + "show-total": "显示总数", + "show-latest": "显示最新数据值", + "settings": "图例设置", + "min": "最小值", + "max": "最大值", + "avg": "平均值", + "total": "总数", + "latest": "latest", + "comparison-time-ago": { + "previousInterval": "(历史间隔)", + "customInterval": "(自定义间隔)", + "days": "(一天前)", + "weeks": "(一周前)", + "months": "(一个月前)", + "years": "(一年前)" + } + }, + "login": { + "login": "登录", + "request-password-reset": "请求密码重置", + "reset-password": "重置密码", + "create-password": "创建密码", + "two-factor-authentication": "双重因素身份验证", + "passwords-mismatch-error": "输入的密码必须相同!", + "password-again": "再次输入密码", + "sign-in": "登录 ", + "username": "用户名(电子邮件)", + "remember-me": "记住我", + "forgot-password": "忘记密码?", + "password-reset": "密码重置", + "expired-password-reset-message": "您的凭据已过期!请创建新密码。", + "new-password": "新密码", + "new-password-again": "再次输入新密码", + "password-link-sent-message": "密码重置链接已成功发送!", + "email": "电子邮件", + "login-with": "使用 {{name}} 登录", + "or": "或", + "error": "登录错误", + "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": "备份验证码" + }, + "markdown": { + "edit": "编辑", + "preview": "预览", + "copy-code": "单击拷贝", + "copied": "已拷贝!" + }, + "ota-update": { + "add": "添加包", + "assign-firmware": "分配的固件", + "assign-firmware-required": "分配的固件必填", + "assign-software": "分配的软件", + "assign-software-required": "分配的软件必填", + "auto-generate-checksum": "自动生成校验和", + "checksum": "校验和", + "checksum-hint": "如果校验和为空,会自动生成", + "checksum-algorithm": "校验和算法", + "checksum-copied-message": "包校验和已复制到剪贴板", + "change-firmware": "固件的更改可能会导致 { count, plural, 1 {1 个设备} other {# 个设备} } 的更新。", + "change-software": "软件的更改可能会导致 { count, plural, 1 {1 个设备} other {# 个设备} } 的更新。", + "chose-compatible-device-profile": "上传的包仅适用于具有所选配置文件的设备。", + "chose-firmware-distributed-device": "选择将分发到设备的固件", + "chose-software-distributed-device": "选择将分发到设备的软件", + "content-type": "内容类型", + "copy-checksum": "复制校验和", + "copy-direct-url": "复制直接URL", + "copyId": "复制包ID", + "copied": "已复制!", + "delete": "删除包", + "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必填", + "download": "下载包", + "drop-file": "拖放打包文件或点击选择要上传的文件。", + "drop-package-file-or": "拖拽文件或", + "file-name": "文件名", + "file-size": "文件大小", + "file-size-bytes": "文件大小(以字节为单位)", + "idCopiedMessage": "包ID已被复制到剪贴板", + "no-firmware-matching": "未找到与'{{entity}}'匹配的兼容固件OTA升级包。", + "no-firmware-text": "没有提供兼容的固件OTA升级包。", + "no-packages-text": "未找到包", + "no-software-matching": "未找到匹配 '{{entity}}' 的兼容软件OTA升级包。", + "no-software-text": "没有提供兼容的软件OTA升级包。", + "ota-update": "OTA 升级", + "ota-update-details": "OTA 升级详情", + "ota-updates": "OTA 升级", + "package-type": "包类型", + "packages-repository": "包仓库", + "search": "搜索包", + "selected-package": "{ count, plural, 1 {1 个包} other {# 个包} } 选中", + "title": "标题", + "title-required": "标题必填。", + "title-max-length": "标题长度必须少于256个字符", + "types": { + "firmware": "固件", + "software": "软件" + }, + "upload-binary-file": "上传二进制文件", + "use-external-url": "使用外部URL", + "version": "版本", + "version-required": "版本必填。", + "version-tag": "版本标签", + "version-tag-hint": "自定义标签应与您设备报告的软件包版本相匹配。", + "version-max-length": "版本长度必须少于256个字符", + "warning-after-save-no-edit": "上传包后,您将无法修改标题、版本、设备配置文件和包类型。" + }, + "position": { + "top": "顶部", + "bottom": "底部", + "left": "左侧", + "right": "右侧" + }, + "profile": { + "profile": "属性", + "last-login-time": "最后登录", + "change-password": "更改密码", + "current-password": "当前密码", + "copy-jwt-token": "复制 JWT 令牌", + "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 {# 位大写字母} }" + } + }, + "relation": { + "relations": "关联", + "direction": "方向", + "search-direction": { + "FROM": "从", + "TO": "到" + }, + "direction-type": { + "FROM": "从", + "TO": "到" + }, + "from-relations": "向外的关联", + "to-relations": "向内的关联", + "selected-relations": "已选择{ count, plural, 1 {# 个关联} other {# 个关联} }", + "type": "类型", + "to-entity-type": "到实体类型", + "to-entity-name": "到实体名称", + "from-entity-type": "从实体类型", + "from-entity-name": "从实体类型", + "to-entity": "到实体", + "from-entity": "从实体", + "delete": "删除关联", + "relation-type": "关联类型", + "relation-type-required": "关联类型必填", + "relation-type-max-length": "关联类型长度必须少于256个字符", + "any-relation-type": "任何类型", + "add": "添加关联", + "edit": "编辑关联", + "delete-to-relation-title": "确定要删除实体 '{{entityName}}' 的关联吗?", + "delete-to-relation-text": "确定删除后实体 '{{entityName}}' 将取消与当前实体的关联关系。", + "delete-to-relations-title": "确定要删除 { count, plural, 1 {# 个关联} other {# 个关联} }吗?", + "delete-to-relations-text": "确定删除所有选择的关联关系后,与当前实体对应的所有关联关系将被移除。", + "delete-from-relation-title": "确定要从实体 '{{entityName}}' 删除关联吗?", + "delete-from-relation-text": "确定删除后,当前实体将与实体 '{{entityName}}' 取消关联", + "delete-from-relations-title": "确定删除 { count, plural, 1 {# 个关联} other {# 个关联} } 吗?", + "delete-from-relations-text": "确定删除所有选择的关联关系后,当前实体将与对应的实体取消关联", + "remove-relation-filter": "移除关联筛选器", + "add-relation-filter": "添加关联筛选器", + "any-relation": "任意关联", + "relation-filters": "关联筛选器", + "additional-info": "附加信息 (JSON)", + "invalid-additional-info": "无法解析附加信息JSON。", + "no-relations-text": "未找到关联" + }, + "resource": { + "add": "添加资源", + "copyId": "复制资源ID", + "delete": "删除资源", + "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 {# 个资源} }吗?", + "download": "Download resource", + "drop-file": "拖拽资源文件或单击以选择要上传的文件。", + "drop-resource-file-or": "Drag and drop a resource file or", + "empty": "资源为空", + "file-name": "File name", + "idCopiedMessage": "拖拽资源文件或", + "no-resource-matching": "未找到与 '{{widgetsBundle}}' 匹配的资源。", + "no-resource-text": "未找到资源", + "open-widgets-bundle": "打开部件库", + "resource": "资源", + "resource-library-details": "资源库详情", + "resource-type": "资源类型", + "resources-library": "资源库", + "search": "查找资源", + "selected-resources": "已选择{ count, plural, 1 {# 个资源} other {# 个资源} }", + "system": "系统", + "title": "标题", + "title-required": "标题是必填项。", + "title-max-length": "标题长度必须少于256个字符" + }, + "rulechain": { + "rulechain": "规则链", + "rulechains": "规则链库", + "root": "是否根链", + "delete": "删除规则链", + "name": "名称", + "name-required": "名称必填。", + "name-max-length": "名称长度必须少于256个字符", + "description": "说明", + "add": "添加规则链", + "set-root": "设置为根规则链", + "set-root-rulechain-title": "确定要设置'{{ruleChainName}}'为根规则链吗?", + "set-root-rulechain-text": "确认后,规则链将变为根规格链,并将处理所有传入的传输消息。", + "delete-rulechain-title": " 确定要删除规则链'{{ruleChainName}}'吗?", + "delete-rulechain-text": "请注意,确认后,规则链和所有相关数据将不可恢复。", + "delete-rulechains-title": "确定要删除{count, plural, 1 { 1 个规则链} other {# 个规则链} }吗?", + "delete-rulechains-action-title": "删除 { count, plural, 1 {# 个规则链} other {# 个规则链} }", + "delete-rulechains-text": "请注意:确认后,所有选定的规则链将被删除,所有相关的数据将不可恢复。", + "add-rulechain-text": "添加新的规则链", + "no-rulechains-text": "未找到规则链", + "rulechain-details": "规则链详情", + "details": "详情", + "events": "事件", + "system": "系统", + "import": "导入规则链", + "export": "导出规则链", + "export-failed-error": "无法导出规则链:{{error}}", + "create-new-rulechain": "创建新的规则链", + "rulechain-file": "规则链文件", + "invalid-rulechain-file-error": "不能导入规则链:无效的规则链数据格式。", + "copyId": "复制规则链ID", + "idCopiedMessage": "规则ID已经复制到粘贴板", + "select-rulechain": "选择规则链", + "no-rulechains-matching": "没有发现匹配'{{entity}}'的规则链。", + "rulechain-required": "规则链必填", + "management": "规则集管理", + "debug-mode": "调试模式", + "search": "查找规则链", + "selected-rulechains": "已选择 { count, plural, 1 {# 个规则链} other {# 个规则链} }", + "open-rulechain": "打开规则链", + "assign-new-rulechain": "Assign new rulechain", + "edge-template-root": "模版根链", + "assign-to-edge": "分配给边缘", + "edge-rulechain": "边缘规则链", + "unassign-rulechain-from-edge-text": "确认后,规则链将会取消分配,边缘无法访问。", + "unassign-rulechains-from-edge-title": "确定要取消分配 { count, plural, 1 {1 个规则链} other {# 个规则链} }吗?", + "unassign-rulechains-from-edge-text": "确认后,选定的规则链将会取消分配,边缘无法访问。", + "assign-rulechain-to-edge-title": "分配规则链给边缘", + "assign-rulechain-to-edge-text": "请选择要分配给边缘的规则链", + "set-edge-template-root-rulechain": "设置为边缘模版根规则链", + "set-edge-template-root-rulechain-title": "确定将 '{{ruleChainName}}' 设置为边缘模版根规则链吗?", + "set-edge-template-root-rulechain-text": "确认后,将会成为边缘模版根规则链,且它会成为新创建边缘的根规则链。", + "invalid-rulechain-type-error": "不能导入规则链:无效的规则链类型。期望类型为{{expectedRuleChainType}}。", + "set-auto-assign-to-edge": "将规则链分配给新创建的边缘", + "set-auto-assign-to-edge-title": "确定将规则链'{{ruleChainName}}'自动分配给新创建的边缘吗?", + "set-auto-assign-to-edge-text": "确认后,将自动分配规则链给新创建的边缘。", + "unset-auto-assign-to-edge": "不将规则链分配给边缘", + "unset-auto-assign-to-edge-title": "确定不再将规则链'{{ruleChainName}}'自动分配给新创建的边缘吗?", + "unset-auto-assign-to-edge-text": "确认后,将不再自动分配规则链给新创建的边缘。", + "unassign-rulechain-title": "你确定要取消分配规则链'{{ruleChainName}}'?", + "unassign-rulechains": "取消分配规则链" + }, + "rulenode": { + "details": "详情", + "events": "事件", + "search": "查找节点", + "open-node-library": "打开节点库", + "add": "添加规则节点", + "name": "名称", + "name-required": "名称必填。", + "name-max-length": "名称长度必须少于256个字符", + "type": "类型", + "description": "说明", + "delete": "删除规则节点", + "select-all-objects": "选择所有节点和连接", + "deselect-all-objects": "取消选择所有节点和连接", + "delete-selected-objects": "删除选定的节点和连接", + "delete-selected": "删除选定", + "create-nested-rulechain": "创建嵌套规则链", + "select-all": "选择全部", + "copy-selected": "选择副本", + "deselect-all": "取消选择", + "rulenode-details": "规则节点详情", + "debug-mode": "调试模式", + "configuration": "配置", + "link": "链接", + "link-details": "规则节点链接详情", + "add-link": "添加链接", + "link-label": "链接标签", + "link-label-required": "链接标签必填", + "custom-link-label": "自定义链接标签", + "custom-link-label-required": "自定义链接标签必填", + "link-labels": "链接标签", + "link-labels-required": "链接标签必填。", + "no-link-labels-found": "未找到链接标签", + "no-link-label-matching": "未找到匹配 '{{label}}' 的链接标签。", + "create-new-link-label": "创建一个新的!", + "type-filter": "筛选器", + "type-filter-details": "使用配置条件筛选传入消息", + "type-enrichment": "属性集", + "type-enrichment-details": "向消息元数据中添加附加信息", + "type-transformation": "变换", + "type-transformation-details": "更改消息 Payload 和元数据", + "type-action": "动作", + "type-action-details": "执行特别动作", + "type-external": "外部的", + "type-external-details": "与外部系统交互", + "type-rule-chain": "规则链", + "type-rule-chain-details": "将传入消息转发到指定的规则链", + "type-flow": "流", + "type-flow-details": "组织消息流", + "type-input": "输入", + "type-input-details": "规则链的逻辑输入,将传入消息转发到下一个相关规则节点", + "type-unknown": "未知", + "type-unknown-details": "未解析的规则节点", + "directive-is-not-loaded": "定义的配置指令 '{{directiveName}}' 不可用。", + "ui-resources-load-error": "加载配置UI资源失败。", + "invalid-target-rulechain": "无法解析目标规则链!", + "test-script-function": "测试脚本功能", + "script-lang-java-script": "Java Script", + "script-lang-tbel": "TBEL", + "message": "消息", + "message-type": "消息类型", + "select-message-type": "选择消息类型", + "message-type-required": "消息类型必填", + "metadata": "元数据", + "metadata-required": "元数据项不能为空。", + "output": "输出", + "test": "测试", + "help": "帮助", + "reset-debug-mode": "重置所有节点中的调试模式" + }, + "timezone": { + "timezone": "时区", + "select-timezone": "选择时区", + "no-timezones-matching": "找不到匹配的'{{timezone}}'时区。", + "timezone-required": "时区必填。", + "browser-time": "浏览器时间" + }, + "queue": { + "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": "Strategy type *", + "grouping-parameter": "分组参数", + "processing-settings": "重试处理设置", + "processing-strategy": "Processing type *", + "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": "重试处理包中所有失败和超时的消息" + } + }, + "server-error": { + "general": "一般服务器错误", + "authentication": "授权错误", + "jwt-token-expired": "JWT令牌已过期", + "tenant-trial-expired": "租户过期", + "credentials-expired": "凭据过期", + "permission-denied": "没有权限", + "invalid-arguments": "无效参数", + "bad-request-params": "请求无效", + "item-not-found": "找不到项目", + "too-many-requests": "请求过于频繁", + "too-many-updates": "更新过于频繁" + }, + "tenant": { + "tenant": "租户", + "tenants": "租户", + "management": "租户管理", + "add": "添加租户", + "admins": "管理员", + "manage-tenant-admins": "管理租户管理员", + "delete": "删除租户", + "add-tenant-text": "添加新租户", + "no-tenants-text": "未找到租户", + "tenant-details": "租客详情", + "title-max-length": "标题长度必须少于256个字符", + "delete-tenant-title": "确定要删除租户'{{tenantTitle}}'吗?", + "delete-tenant-text": "请注意:确认后,租户和所有相关数据将不可恢复。", + "delete-tenants-title": "确定要删除 {count,plural,1 {# 个租户} other {# 个租户} } 吗?", + "delete-tenants-action-title": "删除 { count, plural, 1 {# 个租户} other {# 个租户} }", + "delete-tenants-text": "请注意:确认后,所有选定的租户将被删除,所有相关数据将不可恢复。", + "title": "标题", + "title-required": "标题必填。", + "description": "说明", + "details": "详情", + "events": "事件", + "copyId": "复制租户ID", + "idCopiedMessage": "租户ID已经复制到粘贴板", + "select-tenant": "选择租户", + "no-tenants-matching": "未找到匹配 '{{entity}}' 的租户", + "tenant-required": "租户必填", + "search": "查找租户", + "selected-tenants": "已选择 { count, plural, 1 {# 个租户} other {# 个租户} }", + "isolated-tb-rule-engine": "使用独立的规则引擎服务", + "isolated-tb-rule-engine-details": "每个独立租户需要单独的规则引擎微服务" + }, + "tenant-profile": { + "tenant-profile": "租户配置", + "tenant-profiles": "租户配置", + "add": "添加租户配置", + "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": "删除租户配置", + "copyId": "复制租户配置ID", + "name": "名称", + "name-required": "名称必填。", + "data": "配置数据", + "profile-configuration": "配置设置", + "description": "说明", + "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-assets-range": "最大资产数不能为负数", + "maximum-customers": "最大客户数", + "maximum-customers-required": "最大客户数必填。", + "maximum-customers-range": "最大客户数不能为负数", + "maximum-users": "最大用户数", + "maximum-users-required": "最大用户数必填。", + "maximum-users-range": "最大用户数不能为负数", + "maximum-dashboards": "最大仪表板数", + "maximum-dashboards-required": "最大仪表板数必填。", + "maximum-dashboards-range": "最大仪表板数不能为负数", + "maximum-rule-chains": "最大规则链数", + "maximum-rule-chains-required": "最大规则链数必填。", + "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": "秒" + } + }, + "timeinterval": { + "seconds-interval": "{ seconds, plural, 1 {# 秒} other {# 秒} }", + "minutes-interval": "{ minutes, plural, 1 {# 分} other {# 分} }", + "hours-interval": "{ hours, plural, 1 {# 小时} other {# 小时} }", + "days-interval": "{ days, plural, 1 {# 天} other {# 天} }", + "days": "天", + "hours": "小时", + "minutes": "分钟", + "seconds": "秒", + "advanced": "高级", + "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": { + "milliseconds": "毫秒", + "seconds": "秒", + "minutes": "分钟", + "hours": "小时", + "days": "天" + }, + "timewindow": { + "days": "{ days, plural, 1 {# 天 } other {# 天 } }", + "hours": "{ hours, plural, 0 {- 小时 } 1 {# 小时 } other {# 小时 } }", + "minutes": "{ minutes, plural, 0 {- 分 } 1 {# 分 } other {# 分 } }", + "seconds": "{ seconds, plural, 0 {- 秒 } 1 {# 秒 } other {# 秒 } }", + "realtime": "实时", + "history": "历史", + "last-prefix": "最后", + "period": "从 {{ startTime }} 到 {{ endTime }}", + "edit": "编辑时间窗口", + "date-range": "日期范围", + "last": "最后", + "time-period": "时间段", + "hide": "隐藏", + "interval": "区间" + }, + "user": { + "user": "用户", + "users": "用户", + "customer-users": "客户用户", + "tenant-admins": "租户管理员", + "sys-admin": "系统管理员", + "tenant-admin": "租户管理员", + "customer": "客户", + "anonymous": "匿名", + "add": "添加用户", + "delete": "删除用户", + "add-user-text": "添加新用户", + "no-users-text": "未找到用户", + "user-details": "用户详细信息", + "delete-user-title": "确定要删除用户 '{{userEmail}}' 吗?", + "delete-user-text": "请注意:确认后,用户和所有相关数据将不可恢复。", + "delete-users-title": "确定要删除 { count, plural, 1 {# 个用户} other {# 个用户} } 吗?", + "delete-users-action-title": "删除 { count, plural, 1 {# 个用户} other {# 个用户} }", + "delete-users-text": "请注意:确认后,所有选定的用户将被删除,所有相关数据将不可恢复。", + "activation-email-sent-message": "激活电子邮件已成功发送!", + "resend-activation": "重新发送激活", + "email": "电子邮件", + "email-required": "电子邮件必填。", + "invalid-email-format": "无效的邮件格式。", + "first-name": "名字", + "last-name": "姓氏", + "description": "说明", + "default-dashboard": "默认面板", + "always-fullscreen": "始终全屏", + "select-user": "选择用户", + "no-users-matching": "未找到匹配 '{{entity}}' 的用户。", + "user-required": "用户必填", + "activation-method": "激活方式", + "display-activation-link": "显示激活链接", + "send-activation-mail": "发送激活邮件", + "activation-link": "用户激活链接", + "activation-link-text": "使用该链接 激活 激活用户:", + "copy-activation-link": "复制用户激活链接", + "activation-link-copied-message": "用户激活链接已经复制到粘贴板", + "details": "详情", + "login-as-tenant-admin": "以租户管理员身份登录", + "login-as-customer-user": "以客户用户身份登录", + "search": "查找用户", + "selected-users": "已选择 { count, plural, 1 {# 个用户} other {# 个用户} }", + "disable-account": "禁用用户帐户", + "enable-account": "启用用户帐户", + "enable-account-message": "已成功启用用户帐户!", + "disable-account-message": "已成功禁用用户帐户!", + "copyId": "复制用户Id", + "idCopiedMessage": "用户ID已经复制到粘贴板" + }, + "value": { + "type": "值类型", + "string": "字符串", + "string-value": "字符串值", + "string-value-required": "字符串值必填", + "integer": "数字", + "integer-value": "数字值", + "integer-value-required": "整数值必填", + "invalid-integer-value": "整数值无效", + "double": "双精度小数", + "double-value": "双精度小数值", + "double-value-required": "需要双精度值", + "boolean": "布尔值", + "boolean-value": "布尔值", + "false": "假", + "true": "真", + "long": "Long", + "json": "JSON", + "json-value": "JSON值", + "json-value-invalid": "JSON值的格式无效", + "json-value-required": "JSON值必填。" + }, + "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}}", + "auto-commit-settings-read-only-hint": "自动提交功能不适用于存储库设置中启用的只读选项。" + }, + "widget": { + "widget-library": "部件库", + "widget-bundle": "部件包", + "all-bundles": "All bundles", + "select-widgets-bundle": "选择部件包", + "management": "管理部件", + "editor": "部件编辑器", + "widget-type-not-found": "加载部件配置出错。
可能关联的部件已经删除了。", + "widget-type-load-error": "由于以下错误未加载小部件:", + "remove": "删除部件", + "edit": "编辑部件", + "remove-widget-title": "确定要删除 '{{widgetTitle}}'部件吗?", + "remove-widget-text": "确认后,控件和所有相关数据将变得不可恢复。", + "timeseries": "Timeseries", + "search-data": "查找数据", + "no-data-found": "未找到数据", + "latest": "最新值", + "rpc": "控件部件", + "alarm": "告警部件", + "static": "静态部件", + "select-widget-type": "选择窗口部件类型", + "missing-widget-title-error": "部件标题必须指定!", + "widget-saved": "部件已保存", + "unable-to-save-widget-error": "无法保存部件!控件有错误!", + "save": "保存部件", + "saveAs": "部件另存为", + "save-widget-type-as": "部件类型另存为", + "save-widget-type-as-text": "请输入新的部件标题或选择目标部件包", + "toggle-fullscreen": "切换全屏", + "run": "运行部件", + "title": "部件标题", + "title-required": "部件标题必填。", + "type": "部件类型", + "resources": "资源", + "resource-url": "JavaScript/CSS URL", + "resource-is-module": "是否模块", + "remove-resource": "删除资源", + "add-resource": "添加资源", + "html": "HTML", + "tidy": "Tidy", + "css": "CSS", + "settings-schema": "设置模式", + "datakey-settings-schema": "数据键设置模式", + "latest-datakey-settings-schema": "最新数据键设置模式", + "widget-settings": "部件设置", + "description": "描述", + "image-preview": "图片预览", + "settings-form-selector": "设置表单选择器", + "data-key-settings-form-selector": "数据键设置表单选择器", + "latest-data-key-settings-form-selector": "最新数据键设置表单选择器", + "javascript": "Javascript", + "js": "JS", + "remove-widget-type-title": "确定要删除部件类型 '{{widgetName}}'吗?", + "remove-widget-type-text": "确认后,窗口部件类型和所有相关数据将不可恢复。", + "remove-widget-type": "删除部件类型", + "add-widget-type": "添加新的部件类型", + "widget-type-load-failed-error": "无法加载部件类型!", + "widget-template-load-failed-error": "无法加载部件模板!", + "add": "添加部件", + "undo": "撤消部件更改", + "export": "导出部件", + "no-data": "小部件上没有要显示的数据", + "data-overflow": "部件显示 {{count}}之外{{total}} 实体", + "alarm-data-overflow": "窗口部件显示{{allowedEntities}}(最大允许的)实的警报{{totalEntities}}实体", + "search": "搜索部件", + "filter": "部件类型过滤", + "loading-widgets": "加载部件..." + }, + "widget-action": { + "header-button": "顶部按钮clcik", + "open-dashboard-state": "切换到新仪表板状态", + "update-dashboard-state": "更新当前仪表板状态", + "open-dashboard": "切换到另一个仪表板", + "custom": "自定义动作", + "custom-pretty": "自定义操作(使用HTML模板)", + "mobile-action": "移动端动作", + "target-dashboard-state": "目标仪表板状态", + "target-dashboard-state-required": "目标仪表板状态必填", + "set-entity-from-widget": "从部件中设置实体", + "target-dashboard": "目标仪表板", + "open-right-layout": "打开右侧布局 (移动端视图)", + "state-display-type": "显示仪表板状态选项", + "open-normal": "普通", + "open-in-separate-dialog": "在单独的对话框中打开", + "open-in-popover": "在popover打开", + "dialog-title": "对话框标题", + "dialog-hide-dashboard-toolbar": "在对话框中隐藏仪表板工具栏", + "dialog-width": "对话框宽度相对视图宽度", + "dialog-height": "对话框高度相对视图高度", + "dialog-size-range-error": "对话框尺寸比值必须是1至100范围内。", + "popover-preferred-placement": "首选弹出位置", + "popover-placement-top": "上", + "popover-placement-topLeft": "左上", + "popover-placement-topRight": "右上", + "popover-placement-right": "右", + "popover-placement-rightTop": "右上", + "popover-placement-rightBottom": "右下", + "popover-placement-bottom": "下", + "popover-placement-bottomLeft": "左下", + "popover-placement-bottomRight": "右下", + "popover-placement-left": "左", + "popover-placement-leftTop": "左上", + "popover-placement-leftBottom": "左下", + "popover-hide-on-click-outside": "点击隐藏外面的弹出窗口", + "popover-hide-dashboard-toolbar": "隐藏仪表板工具栏", + "popover-width": "浏览器中的弹出宽度(例如100px, 25vw)单位", + "popover-height": "浏览器中的弹出高度(例如:100px,25vh)单位", + "popover-style": "弹出样式", + "open-new-browser-tab": "在新的浏览器选项卡中打开", + "mobile": { + "action-type": "手机端动作类型", + "action-type-required": "手机端动作类型必填。", + "take-picture-from-gallery": "画册拍照", + "take-photo": "拍照", + "map-direction": "打开地图说明", + "map-location": "打开地图位置", + "scan-qr-code": "扫描二维码", + "make-phone-call": "拨打电话", + "get-location": "获得位置", + "take-screenshot": "截图" + } + }, + "widgets-bundle": { + "current": "当前组", + "widgets-bundles": "部件包", + "add": "添加部件包", + "delete": "删除部件包", + "title": "标题", + "title-required": "标题必填。", + "title-max-length": "标题长度必须少于256个字符", + "description": "描述", + "image-preview": "图片预览", + "add-widgets-bundle-text": "添加新的部件包", + "no-widgets-bundles-text": "未找到部件包", + "empty": "部件包是空的", + "details": "详情", + "widgets-bundle-details": "部件包详细信息", + "delete-widgets-bundle-title": "确定要删除部件包 '{{widgetsBundleTitle}}'吗?", + "delete-widgets-bundle-text": "请注意:确认后,部件包和所有相关数据将不可恢复。", + "delete-widgets-bundles-title": "确定要删除 { count, plural, 1 {# 个部件包} other {# 个部件包} } 吗?", + "delete-widgets-bundles-action-title": "删除 { count, plural, 1 {# 个部件包} other {# 个部件包} }", + "delete-widgets-bundles-text": "请注意:确认后,所有选定的部件包将被删除,所有相关数据将不可恢复。", + "no-widgets-bundles-matching": "未找到与 '{{widgetsBundle}}' 匹配的部件包。", + "widgets-bundle-required": "部件包必填。", + "system": "系统", + "import": "导入部件包", + "export": "导出部件包", + "export-failed-error": "无法导出部件包: {{error}}", + "create-new-widgets-bundle": "创建新的部件包", + "widgets-bundle-file": "部件包文件", + "invalid-widgets-bundle-file-error": "无法导入部件包:无效的部件包数据结构。", + "search": "查找部件包", + "selected-widgets-bundles": "已选择 { count, plural, 1 {# 个部件包} other {# 个部件包} }", + "open-widgets-bundle": "打开部件包", + "loading-widgets-bundles": "加载部件包..." + }, + "widget-config": { + "data": "数据", + "settings": "设置", + "advanced": "高级", + "title": "标题", + "title-tooltip": "标题提示框", + "general-settings": "基础设置", + "display-title": "显示标题", + "drop-shadow": "阴影", + "enable-fullscreen": "启用全屏", + "background-color": "背景颜色", + "text-color": "文字颜色", + "padding": "内边距", + "margin": "外边距", + "widget-style": "部件风格", + "widget-css": "部件CSS", + "title-style": "标题风格", + "mobile-mode-settings": "移动端设置", + "order": "顺序", + "height": "高度", + "mobile-hide": "移动端隐藏部件", + "units": "特殊符号展示值", + "decimals": "浮点数后的位数", + "timewindow": "时间窗口", + "use-dashboard-timewindow": "使用仪表板的时间窗口", + "display-timewindow": "显示时间窗口", + "legend": "图例", + "display-legend": "显示图例", + "datasources": "数据源", + "maximum-datasources": "最大允许{ count, plural, 1 {1个数据源。} other {#个数据源。} }", + "timeseries-key-error": "应至少指定一个时间序列数据密钥", + "datasource-type": "类型", + "datasource-parameters": "参数", + "remove-datasource": "移除数据源", + "add-datasource": "添加数据源", + "target-device": "目标设备", + "alarm-source": "警报源", + "actions": "动作", + "action": "动作", + "add-action": "添加动作", + "search-actions": "搜索动作", + "no-actions-text": "找不到动作", + "action-source": "动作源", + "action-source-required": "动作源必填", + "action-name": "名称", + "action-name-required": "动作名称必填。", + "action-name-not-unique": "动作名称已经存在。
相同动作源的动作名称必须唯一。", + "action-icon": "图标", + "show-hide-action-using-function": "使用函数显示/隐藏操作", + "action-type": "类型", + "action-type-required": "类型必填", + "edit-action": "编辑动作", + "delete-action": "删除删除", + "delete-action-title": "删除部件动作", + "delete-action-text": "确定要删除部件动作'{{actionName}}'吗?", + "title-icon": "标题图标", + "display-icon": "显示标题图标", + "icon-color": "图标颜色", + "icon-size": "图标大小", + "advanced-settings": "高级设置", + "data-settings": "数据设置", + "no-data-display-message": "\"无数据显示\" 替代消息", + "data-page-size": "每个数据源的最大实体数", + "settings-component-not-found": "找不到选择器'{{selector}}'的每个数据源设置窗体组件的最大实体数" + }, + "widget-type": { + "import": "导入部件类型", + "export": "导出部件类型", + "export-failed-error": "无法导出部件类型: {{error}}", + "create-new-widget-type": "创建新的部件类型", + "widget-type-file": "部件类型文件", + "invalid-widget-type-file-error": "无法导入部件类型:无效的部件类型数据结构。" + }, + "widgets": { + "chart": { + "common-settings": "通用设置", + "enable-stacking-mode": "启用堆叠模式", + "line-shadow-size": "线条阴影大小", + "display-smooth-lines": "显示光滑(弯曲)线条", + "default-bar-width": "默认条形宽度不聚合数据(毫秒)", + "bar-alignment": "条形对齐", + "bar-alignment-left": "左", + "bar-alignment-right": "右", + "bar-alignment-center": "中", + "default-font-size": "默认字体大小", + "default-font-color": "默认字体颜色", + "thresholds-line-width": "所有线条默认宽度", + "tooltip-settings": "提示栏设置", + "show-tooltip": "显示提示栏", + "hover-individual-points": "坐标悬停显示", + "show-cumulative-values": "在堆叠模式下显示累加数据", + "hide-zero-false-values": "隐藏提示栏中0或false值", + "tooltip-value-format-function": "提示栏格式化函数", + "grid-settings": "网格设置", + "show-vertical-lines": "显示垂直参考线", + "show-horizontal-lines": "显示水平参考线", + "grid-outline-border-width": "网格边框宽度(像系)", + "primary-color": "边框颜色", + "background-color": "背景颜色", + "ticks-color": "参考线颜色", + "xaxis-settings": "X轴设置", + "axis-title": "标题", + "xaxis-tick-labels-settings": "标签设置", + "show-tick-labels": "显示标签", + "yaxis-settings": "y轴设置", + "min-scale-value": "最小值", + "max-scale-value": "最大值", + "yaxis-tick-labels-settings": "标签设置", + "tick-step-size": "步长", + "number-of-decimals": "小数位数", + "ticks-formatter-function": "刻度格式化函数", + "comparison-settings": "比较设置", + "enable-comparison": "启用比较", + "time-for-comparison": "比较时期", + "time-for-comparison-previous-interval": "历史时间间隔(默认)", + "time-for-comparison-days": "一天前", + "time-for-comparison-weeks": "一周前", + "time-for-comparison-months": "一月前", + "time-for-comparison-years": "一年前", + "time-for-comparison-custom-interval": "自定义间隔", + "custom-interval-value": "自定义间隔值(毫秒)", + "comparison-x-axis-settings": "比较x轴设置", + "axis-position": "位置", + "axis-position-top": "上面(默认)", + "axis-position-bottom": "下面", + "custom-legend-settings": "自定义图例设置", + "enable-custom-legend": "启用自定义图例(你可以使用属性/时间序列标签)", + "key-name": "键名", + "key-name-required": "键名必填", + "key-type": "键类型", + "key-type-attribute": "属性", + "key-type-timeseries": "时间序列", + "label-keys-list": "在标签中使用键列表", + "no-label-keys": "没有配置键", + "add-label-key": "添加键", + "line-width": "线条宽度", + "color": "颜色", + "data-is-hidden-by-default": "默认隐藏数据", + "disable-data-hiding": "禁用用数据隐藏", + "remove-from-legend": "从图例中移除数据键", + "exclude-from-stacking": "从堆叠模式排除", + "line-settings": "线条设置", + "show-line": "显示线条", + "fill-line": "线条填充", + "points-settings": "点设置", + "show-points": "显示点", + "points-line-width": "宽度", + "points-radius": "圆角", + "point-shape": "形状", + "point-shape-circle": "圆形", + "point-shape-cross": "十字形", + "point-shape-diamond": "菱形", + "point-shape-square": "矩形", + "point-shape-triangle": "三角形", + "point-shape-custom": "自定义", + "point-shape-draw-function": "绘制函数", + "show-separate-axis": "显示独立轴", + "axis-position-left": "左", + "axis-position-right": "右", + "thresholds": "阈值", + "no-thresholds": "没有配置阈值", + "add-threshold": "添加阈值", + "show-values-for-comparison": "显示比较的历史值", + "comparison-values-label": "历史数什标签", + "threshold-settings": "阈值设置", + "use-as-threshold": "将值作为阈值", + "threshold-line-width": "阈值线条宽度", + "threshold-color": "阈值颜色", + "common-pie-settings": "饼图设置", + "radius": "圆角", + "inner-radius": "内圆角", + "tilt": "倾斜", + "stroke-settings": "斜线设置", + "width-pixels": "宽度(像素)", + "show-labels": "显示标签", + "animation-settings": "动画设置", + "animated-pie": "启用动画", + "border-settings": "边框设置", + "border-width": "边框宽度", + "border-color": "边框颜色", + "legend-settings": "图例设置", + "display-legend": "显示图例", + "labels-font-color": "标签字体颜色" + }, + "dashboard-state": { + "dashboard-state-settings": "仪表板状态设置", + "dashboard-state": "仪表板状态ID", + "autofill-state-layout": "自动填充状态布局高度", + "default-margin": "部件默认外边距", + "default-background-color": "默认背景色", + "sync-parent-state-params": "同步父仪表板状态参数" + }, + "date-range-navigator": { + "date-range-picker-settings": "日期范围选择设置", + "hide-date-range-picker": "隐藏日期范围选择", + "picker-one-panel": "日期范围选择板", + "picker-auto-confirm": "自动确认日期范围选择", + "picker-show-template": "日期范围选择显示模板", + "first-day-of-week": "一周的第一天", + "interval-settings": "间隔设置", + "hide-interval": "隐藏间隔", + "initial-interval": "初始间隔", + "interval-hour": "小时", + "interval-day": "天", + "interval-week": "周", + "interval-two-weeks": "2周", + "interval-month": "月", + "interval-three-months": "3个月", + "interval-six-months": "6个月", + "step-settings": "步长设置", + "hide-step-size": "步长设置", + "initial-step-size": "初始步长大小", + "hide-labels": "隐藏标签", + "use-session-storage": "使用会话存储", + "localizationMap": { + "Sun": "周日", + "Mon": "周一", + "Tue": "周二", + "Wed": "周三", + "Thu": "周四", + "Fri": "周五", + "Sat": "周六", + "Jan": "1月", + "Feb": "2月", + "Mar": "3月", + "Apr": "4月", + "May": "5月", + "Jun": "6月", + "Jul": "7月", + "Aug": "8月", + "Sep": "9月", + "Oct": "10月", + "Nov": "11月", + "Dec": "12月", + "January": "一月", + "February": "二月", + "March": "三月", + "April": "四月", + "June": "六月", + "July": "七月", + "August": "八月", + "September": "九月", + "October": "十月", + "November": "十一月", + "December": "十二月", + "Custom Date Range": "自定义日期范围", + "Date Range Template": "日期范围模板", + "Today": "今天", + "Yesterday": "昨天", + "This Week": "本星期", + "Last Week": "上星期", + "This Month": "本月", + "Last Month": "上月", + "Year": "年", + "This Year": "今年", + "Last Year": "去年", + "Date picker": "日期选择", + "Hour": "小时", + "Day": "天", + "Week": "周", + "2 weeks": "2周", + "Month": "月", + "3 months": "3个月", + "6 months": "6个月", + "Custom interval": "自定义间隔", + "Interval": "间隔", + "Step size": "步长", + "Ok": "确定" + } + }, + "entities-hierarchy": { + "hierarchy-data-settings": "层次数据设置", + "relations-query-function": "关系查询函数", + "has-children-function": "是否子级函数", + "node-state-settings": "状态设置", + "node-opened-function": "展开函数", + "node-disabled-function": "禁用函数", + "display-settings": "显示设置", + "node-icon-function": "icon函数", + "node-text-function": "文本函数", + "sort-settings": "排序设置", + "nodes-sort-function": "排序函数" + }, + "edge": { + "display-default-title": "显示默认标题" + }, + "gateway": { + "general-settings": "基础设置", + "widget-title": "部件村题", + "default-archive-file-name": "默认文件名称", + "device-type-for-new-gateway": "网关设备类型", + "messages-settings": "消息设置", + "save-config-success-message": "配置保存成功消息", + "device-name-exists-message": "设备名称已经存在消消息", + "gateway-title": "网关标题", + "read-only": "只读", + "events-title": "事件标题", + "events-filter": "事件过滤", + "event-key-contains": "包含事件key..." + }, + "gauge": { + "default-color": "默认颜色", + "radial-gauge-settings": "仪表盘设置", + "ticks-settings": "刻度设置", + "min-value": "最小值", + "max-value": "最大值", + "start-ticks-angle": "起始角度", + "ticks-angle": "结束角度", + "major-ticks-count": "主要刻度数量", + "major-ticks-color": "主要刻度颜色", + "minor-ticks-count": "次要刻度数量", + "minor-ticks-color": "次要刻度颜色", + "tick-numbers-font": "字体", + "unit-title-settings": "标题设置", + "show-unit-title": "显示标题", + "unit-title": "单位", + "title-font": "字体", + "units-settings": "单位设置", + "units-font": "字体", + "value-box-settings": "数值设置", + "show-value-box": "显示数值框", + "value-int": "长度", + "value-font": "字体", + "value-box-rect-stroke-color": "数值框矩形颜色", + "value-box-rect-stroke-color-end": "数值框矩形颜色渐变", + "value-box-background-color": "数值框背景色", + "value-box-shadow-color": "数值框阴影颜色", + "plate-settings": "表盘设置", + "show-plate-border": "显示边框", + "plate-color": "背景色", + "needle-settings": "指针设置", + "needle-circle-size": "针圈大小", + "needle-color": "颜色", + "needle-color-end": "渐变色", + "needle-color-shadow-up": "针头颜色", + "needle-color-shadow-down": "阴影颜色", + "highlights-settings": "色块设置", + "highlights-width": "色块宽度", + "highlights": "色块", + "highlight-from": "从", + "highlight-to": "到", + "highlight-color": "颜色", + "no-highlights": "没有配置色块", + "add-highlight": "添加色块", + "animation-settings": "动画设置", + "enable-animation": "启用动画", + "animation-duration": "动画时长", + "animation-rule": "动画类型", + "animation-linear": "线形", + "animation-quad": "方形", + "animation-quint": "五角形", + "animation-cycle": "环形", + "animation-bounce": "循环", + "animation-elastic": "弹跳", + "animation-dequad": "Dequad", + "animation-dequint": "Dequint", + "animation-decycle": "Decycle", + "animation-debounce": "Debounce", + "animation-delastic": "Delastic", + "linear-gauge-settings": "线型设置", + "bar-stroke-width": "量规宽度", + "bar-stroke-color": "量规颜色", + "bar-background-color": "量规背景色", + "bar-background-color-end": "量规背景渐变色", + "progress-bar-color": "进度条颜色", + "progress-bar-color-end": "进度条渐变色", + "major-ticks-names": "主要刻度名称", + "show-stroke-ticks": "显示主要刻度", + "major-ticks-font": "主要刻度字体", + "border-color": "边框颜色", + "border-width": "边框宽度", + "needle-circle-color": "指针颜色", + "animation-target": "动画触发", + "animation-target-needle": "指针", + "animation-target-plate": "表盘", + "common-settings": "通用设置", + "gauge-type": "仪表盘类型", + "gauge-type-arc": "弧形", + "gauge-type-donut": "环形", + "gauge-type-horizontal-bar": "水平", + "gauge-type-vertical-bar": "垂直", + "donut-start-angle": "角度从", + "bar-settings": "条形设置", + "relative-bar-width": "相对条宽", + "neon-glow-brightness": "霓虹灯效果亮度,(0-100),0-禁用效果", + "stripes-thickness": "条纹的厚度0是没有条纹", + "rounded-line-cap": "显示圆形线盖", + "bar-color-settings": "条颜色设置", + "use-precise-level-color-values": "使用精确的颜色值", + "bar-colors": "从下到上的条形颜色", + "color": "颜色", + "no-bar-colors": "没有配置条形颜色", + "add-bar-color": "添加条形颜色", + "from": "从", + "to": "到", + "fixed-level-colors": "连界值条形颜色", + "gauge-title-settings": "仪表盘标题设置", + "show-gauge-title": "显示仪表盘标题", + "gauge-title": "仪表盘标题", + "gauge-title-font": "仪表盘标题", + "unit-title-and-timestamp-settings": "单位标题和时间戳设置", + "show-timestamp": "显示值时间戳", + "timestamp-format": "时间戳格式化", + "label-font": "标签字体显示值", + "value-settings": "数值设置", + "show-value": "显示文本值", + "min-max-settings": "最小/最大标签设置", + "show-min-max": "显示最小值和最大值", + "min-max-font": "标签最大和最小字体", + "show-ticks": "显示刻度", + "tick-width": "刻度宽度", + "tick-color": "刻度颜色", + "tick-values": "刻度值", + "no-tick-values": "没有配置刻度值", + "add-tick-value": "添加刻度值" + }, + "gpio": { + "pin": "Pin", + "label": "标签", + "row": "行", + "column": "列", + "color": "颜色", + "panel-settings": "面板设置", + "background-color": "背景颜色", + "gpio-switches": "GPIO开关", + "no-gpio-switches": "没有配置GPIO开关", + "add-gpio-switch": "添加开关", + "gpio-status-request": "GPIO状态请求", + "method-name": "方法名", + "method-body": "方法体", + "gpio-status-change-request": "GPIO状态更改请求", + "parse-gpio-status-function": "GPIO状态解析函数", + "gpio-leds": "led", + "no-gpio-leds": "没有配置led", + "add-gpio-led": "添加LED" + }, + "html-card": { + "html": "HTML", + "css": "CSS" + }, + "input-widgets": { + "attribute-not-allowed": "属性参数不能在此小部件中使用", + "blocked-location": "在浏览器中阻止地理位置", + "claim-device": "声明设备", + "claim-failed": "声明设备失败!", + "claim-not-found": "找不到设备!", + "claim-successful": "设备已成功申领!", + "date": "日期", + "device-name": "设备名称", + "device-name-required": "设备名称必填", + "discard-changes": "放弃更改", + "entity-attribute-required": "实体属性必填", + "entity-coordinate-required": "纬度和经度两个字段都是必需的", + "entity-timeseries-required": "实体时间序列必填", + "get-location": "获取当前位置", + "invalid-date": "无效日期", + "latitude": "纬度", + "longitude": "经度", + "min-value-error": "最小值是{{value}}", + "max-value-error": "最大值是{{value}}", + "not-allowed-entity": "所选实体不能具有共享属性", + "no-attribute-selected": "未选择任何属性", + "no-datakey-selected": "未选择数据键", + "no-coordinate-specified": "未指定纬度/经度的数据键", + "no-entity-selected": "未选择实体", + "no-image": "没有图像", + "no-support-geolocation": "你的浏览器不支持地理定位", + "no-support-web-camera": "你的浏览器不支持摄像头", + "enable-https-use-widget": "请启用HTTPS以使用此部件", + "no-found-your-camera": "找不到摄像机", + "no-permission-camera": "权限被用户拒绝/此站点无权使用摄像机", + "no-timeseries-selected": "未选择时间序列", + "secret-key": "密钥", + "secret-key-required": "密钥必填", + "switch-attribute-value": "切换实体属性值", + "switch-camera": "切换摄像机", + "switch-timeseries-value": "切换实体时间序列值", + "take-photo": "拍照", + "time": "时间", + "timeseries-not-allowed": "时间序列参数不能用于此部件", + "update-failed": "更新失败", + "update-successful": "更新成功", + "update-attribute": "更新属性", + "update-timeseries": "更新时间序列", + "value": "数据值", + "general-settings": "基础设置", + "widget-title": "部件标题", + "claim-button-label": "声称按钮标签", + "show-secret-key-field": "显示“密钥”输入字段", + "labels-settings": "标签设置", + "show-labels": "显示标签", + "device-name-label": "设备名称设备名称输入字段的标签输入字段", + "secret-key-label": "设备名称设备密钥输入字段的标签输入字段", + "messages-settings": "信息设置", + "claim-device-success-message": "设备声明成功的文本信息", + "claim-device-not-found-message": "设备声明未找到信息", + "claim-device-failed-message": "设备声明失败的文本信息", + "claim-device-name-required-message": "设备声明“设备名称”必填的错误信息", + "claim-device-secret-key-required-message": "设备声明“设备密钥”必填的错误信息", + "show-label": "显示标签", + "label": "标签", + "required": "必填", + "required-error-message": "错误信息“必填”", + "show-result-message": "显示结果信息", + "integer-field-settings": "Integer字段设置", + "min-value": "最大值", + "max-value": "最小值", + "double-field-settings": "Double字段设置", + "text-field-settings": "Text字段设置", + "min-length": "最小长度", + "max-length": "最大长度", + "checkbox-settings": "Checkbox设置", + "true-label": "Checked标签", + "false-label": "Unchecked标签", + "image-input-settings": "Image字段设置", + "display-preview": "显示预览", + "display-clear-button": "显示清除按钮", + "display-apply-button": "显示应用按钮", + "display-discard-button": "显示弃用按钮", + "datetime-field-settings": "Date/time字段设置", + "display-time-input": "显示time输入", + "latitude-key-name": "Latitude键名", + "longitude-key-name": "Longitude键名", + "show-get-location-button": "显示“获取当前位置”按钮", + "use-high-accuracy": "使用高精度", + "location-fields-settings": "位置设置", + "latitude-label": "latitude标签", + "longitude-label": "longitude标签", + "input-fields-alignment": "对齐方式", + "input-fields-alignment-column": "列(默认)", + "input-fields-alignment-row": "行", + "latitude-field-required": "Latitude字段必填", + "longitude-field-required": "Longitude字段必填", + "attribute-settings": "属性设置", + "widget-mode": "部件模式", + "widget-mode-update-attribute": "更新属性", + "widget-mode-update-timeseries": "更新时间序列", + "attribute-scope": "属性范围", + "attribute-scope-server": "服务端属性", + "attribute-scope-shared": "共享属性", + "value-required": "启用数值必填", + "image-settings": "图片设置", + "image-format": "图片格式", + "image-format-jpeg": "JPEG", + "image-format-png": "PNG", + "image-format-webp": "WEBP", + "image-quality": "图片压缩", + "max-image-width": "最大宽度", + "max-image-height": "最大高度", + "action-buttons": "动作按钮", + "show-action-buttons": "显示动作按钮", + "update-all-values": "更新所有值", + "save-button-label": "“保存”按钮标签", + "reset-button-label": "“撤销”按钮标签", + "group-settings": "分组设置", + "show-group-title": "显示分组标题", + "group-title": "分组标题", + "fields-alignment": "对齐字段", + "fields-alignment-row": "行(默认)", + "fields-alignment-column": "列", + "fields-in-row": "行中的字段数", + "option-value": "值(为创建空选项编写“null”)", + "option-label": "标签", + "hide-input-field": "隐藏输入字段", + "datakey-type": "Datakey类型", + "datakey-type-server": "服务端属性(默认)", + "datakey-type-shared": "共享属性", + "datakey-type-timeseries": "时间序列", + "datakey-value-type": "Datakey值类型", + "datakey-value-type-string": "String", + "datakey-value-type-double": "Double", + "datakey-value-type-integer": "Integer", + "datakey-value-type-boolean-checkbox": "Boolean(多选)", + "datakey-value-type-boolean-switch": "Boolean(开关)", + "datakey-value-type-date-time": "Date & Time", + "datakey-value-type-date": "Date", + "datakey-value-type-time": "Time", + "datakey-value-type-select": "Select", + "value-is-required": "值是必填", + "ability-to-edit-attribute": "编辑属性的能力", + "ability-to-edit-attribute-editable": "可编辑(默认)", + "ability-to-edit-attribute-disabled": "禁用", + "ability-to-edit-attribute-readonly": "只读", + "disable-on-datakey-name": "禁用另一个datakey的错误值(指定datakey名称)", + "slide-toggle-settings": "滑块设置", + "slide-toggle-label-position": "滑动标签位置", + "slide-toggle-label-position-after": "后面", + "slide-toggle-label-position-before": "前面", + "select-options": "下拉选项", + "no-select-options": "没有配置下拉选项", + "add-select-option": "添加下接选项", + "numeric-field-settings": "数字字段设置", + "step-interval": "步长间隔", + "error-messages": "错误信息", + "min-value-error-message": "“最小值”错误信息", + "max-value-error-message": "“最大值”错误信息", + "invalid-date-error-message": "“无效日期”错误消息", + "icon-settings": "图标设置", + "use-custom-icon": "自定义图标", + "input-cell-icon": "输入单元格之前显示的图标", + "value-conversion-settings": "值转换设置", + "get-value-settings": "获取值设置", + "use-get-value-function": "启用获取值函数", + "get-value-function": "获取值函数", + "set-value-settings": "设置值设置", + "use-set-value-function": "启用值值函数", + "set-value-function": "启用函数" + }, + "invalid-qr-code-text": "无效的二维码文本。", + "qr-code": { + "use-qr-code-text-function": "启用二维码文本函数", + "qr-code-text-pattern": "二维码文本模式(例如:${entityName}|${keyName}一些文本。')", + "qr-code-text-pattern-hint": "二维码文本模式使用实体别名中第一个找到的键的值。", + "qr-code-text-pattern-required": "二维码文本必填。", + "qr-code-text-function": "二维码文本函数" + }, + "label-widget": { + "label-pattern": "模式", + "label-pattern-hint": "提示文本,如:'${keyName}单位。'或${#<key index>}单位'", + "label-pattern-required": "模式必填", + "label-position": "相对位置", + "x-pos": "X", + "y-pos": "Y", + "background-color": "背景色", + "font-settings": "字体设置", + "background-image": "背景图片", + "labels": "标签", + "no-labels": "没有配置标签", + "add-label": "添加标签" + }, + "navigation": { + "title": "标题", + "navigation-path": "导航路径", + "filter-type": "过滤类型", + "filter-type-all": "所有内容", + "filter-type-include": "包含内容", + "filter-type-exclude": "排除内容", + "items": "内容", + "enter-urls-to-filter": "输入URL进行过滤..." + }, + "persistent-table": { + "rpc-id": "RPC ID", + "message-type": "消息类型", + "method": "方法", + "params": "参数", + "created-time": "创建时间", + "expiration-time": "到期时间", + "retries": "重试", + "status": "状态", + "filter": "过滤", + "refresh": "刷新", + "add": "添加RPC请求", + "details": "详情", + "delete": "删除", + "delete-request-title": "删除持续化RPC请求", + "delete-request-text": "您确定要删除请求吗?", + "details-title": "详情RPC ID: ", + "additional-info": "附加信息", + "response": "响应", + "any-status": "任何状态", + "rpc-status-list": "RPC状态列表", + "no-request-prompt": "无显示请求", + "send-request": "发送请求", + "add-title": "创建持续化RPC请求", + "method-error": "方法名必填。", + "timeout-error": "最小超时值为5000(5秒)。", + "white-space-error": "不允许使用空白。", + "rpc-status": { + "QUEUED": "QUEUED", + "SENT": "SENT", + "DELIVERED": "DELIVERED", + "SUCCESSFUL": "SUCCESSFUL", + "TIMEOUT": "TIMEOUT", + "EXPIRED": "EXPIRED", + "FAILED": "FAILED" + }, + "rpc-search-status-all": "所有", + "message-types": { + "false": "Two-way", + "true": "One-way" + }, + "general-settings": "基础设置", + "enable-filter": "启用过滤", + "enable-sticky-header": "滚动时显示标头", + "enable-sticky-action": "滚动时显示动作列", + "display-request-details": "显示请求详情", + "allow-send-request": "允许发送RPC请求", + "allow-delete-request": "允许删除请求", + "columns-settings": "设置列", + "display-columns": "显示列", + "column": "列", + "no-columns-found": "没有配置列", + "no-columns-matching": "'{{column}}'找不到。" + }, + "rpc": { + "value-settings": "设置值", + "initial-value": "初始值", + "retrieve-value-settings": "恢复开/关设置", + "retrieve-value-method": "恢复方法", + "retrieve-value-method-none": "无需恢复", + "retrieve-value-method-rpc": "RPC方法恢复", + "retrieve-value-method-attribute": "订阅属性", + "retrieve-value-method-timeseries": "订阅时间序列", + "attribute-value-key": "属性key", + "timeseries-value-key": "时序key", + "get-value-method": "RPC获取方法", + "parse-value-function": "解析函数", + "update-value-settings": "更新值设置", + "set-value-method": "RPC获取方法", + "convert-value-function": "转换函数", + "rpc-settings": "RPC设置", + "request-timeout": "RPC请求超(毫秒)", + "persistent-rpc-settings": "持久化RPC设置", + "request-persistent": "RPC请求持久化", + "persistent-polling-interval": " RPC持久化轮询间隔(毫秒)", + "common-settings": "通用设置", + "switch-title": "开关标题", + "show-on-off-labels": "显示开/关", + "slide-toggle-label": "滑块切换标签", + "label-position": "标签位置", + "label-position-before": "前面", + "label-position-after": "后面", + "slider-color": "滑块颜色", + "slider-color-primary": "Primary", + "slider-color-accent": "Accent", + "slider-color-warn": "Warn", + "button-style": "按钮样式", + "button-raised": "按钮凸起", + "button-primary": "原色", + "button-background-color": "背景色", + "button-text-color": "文字颜色", + "widget-title": "部件标题", + "button-label": "按钮标签", + "device-attribute-scope": "设备属性范围", + "server-attribute": "服务端属性", + "shared-attribute": "共享属性", + "device-attribute-parameters": "设备属性参数", + "is-one-way-command": "是否单向命令", + "rpc-method": "RPC方法", + "rpc-method-params": "RPC参数", + "show-rpc-error": "显示RPC命令执行错误", + "led-title": "LED标题", + "led-color": "LED颜色", + "check-status-settings": "检查状态设置", + "perform-rpc-status-check": "执行RPC设备状态检查", + "retrieve-led-status-value-method": "使用方法状态值恢复LED", + "led-status-value-attribute": "设备属性包含LED状态值", + "led-status-value-timeseries": "设备时间序列包含LED状态值", + "check-status-method": "RPC设备状态检查方法", + "parse-led-status-value-function": "解析函数", + "knob-title": "旋转按钮", + "min-value": "最小值", + "max-value": "最大值" + }, + "maps": { + "select-entity": "选择实体", + "select-entity-hint": "提示:选择后单击地图以设置位置", + "tooltips": { + "placeMarker": "单击放置'{{entityName}}'实体", + "firstVertex": "单击放置多边形'{{entityName}}'的第一个点", + "firstVertex-cut": "单击放置第一个点", + "continueLine": "单击继续绘制'{{entityName}}'多边形", + "continueLine-cut": "单击继续绘制", + "finishLine": "单击任何现有标记完成", + "finishPoly": "单击第一个标记完成'{{entityName}}'并保存", + "finishPoly-cut": "单击第一个标记完成并保存", + "finishRect": "单击'{{entityName}}'多边形完成并保存", + "startCircle": "单击'{{entityName}}'放置圆圈中心", + "finishCircle": "单击'{{entityName}}'完成圆圈", + "placeCircleMarker": "单击放置圆形标记" + }, + "actions": { + "finish": "完成", + "cancel": "取消", + "removeLastVertex": "删除最后一点" + }, + "buttonTitles": { + "drawMarkerButton": "放置实体", + "drawPolyButton": "创建多边形", + "drawLineButton": "创建多边形线条", + "drawCircleButton": "创建圆形", + "drawRectButton": "创建矩形", + "editButton": "编辑模式", + "dragButton": "拖放模式", + "cutButton": "剪切多边形区域", + "deleteButton": "移除", + "drawCircleMarkerButton": "创建圆形标志", + "rotateButton": "旋转多边形" + }, + "map-provider-settings": "地图设置", + "map-provider": "提供程序", + "map-provider-google": "Google地图", + "map-provider-openstreet": "OpenStreet地图", + "map-provider-here": "HERE地图", + "map-provider-image": "Image地图", + "map-provider-tencent": "Tencent地图", + "openstreet-provider": "OpenStreet地图提供程序", + "openstreet-provider-mapnik": "OpenStreetMap.Mapnik(默认)", + "openstreet-provider-hot": "OpenStreetMap.HOT", + "openstreet-provider-esri-street": "Esri.WorldStreetMap", + "openstreet-provider-esri-topo": "Esri.WorldTopoMap", + "openstreet-provider-esri-imagery": "Esri.WorldImagery", + "openstreet-provider-cartodb-positron": "CartoDB.Positron", + "openstreet-provider-cartodb-dark-matter": "CartoDB.DarkMatter", + "use-custom-provider": "自定义提供程序", + "custom-provider-tile-url": "提供程序URL", + "google-maps-api-key": "Google地图Key", + "default-map-type": "默认地图类型", + "google-map-type-roadmap": "路线地图", + "google-map-type-satelite": "卫星", + "google-map-type-hybrid": "混合", + "google-map-type-terrain": "街道", + "map-layer": "地图", + "here-map-normal-day": "HERE.normalDay(默认)", + "here-map-normal-night": "HERE.normalNight", + "here-map-hybrid-day": "HERE.hybridDay", + "here-map-terrain-day": "HERE.terrainDay", + "credentials": "证书", + "here-app-id": "HERE地图id", + "here-app-code": "HERE地图code", + "tencent-maps-api-key": "Tencent地图Key", + "tencent-map-type-roadmap": "路线地图", + "tencent-map-type-satelite": "卫星", + "tencent-map-type-hybrid": "混合", + "image-map-background": "地图北景", + "image-map-background-from-entity-attribute": "从实体属性中获取背景图", + "image-url-source-entity-alias": "图像URL源实体别名", + "image-url-source-entity-attribute": "图像URL源实体属性", + "common-map-settings": "通用设置", + "x-pos-key-name": "X位置键名", + "y-pos-key-name": "Y位置键名", + "latitude-key-name": "Latitude键名", + "longitude-key-name": "Longitude键名", + "default-map-zoom-level": "地图缩放级别(1-20)", + "default-map-center-position": "地图中心位置(0,0)", + "disable-scroll-zooming": "地图中心位置", + "disable-double-click-zooming": "禁用双击缩放", + "disable-zoom-control-buttons": "禁用缩放按钮", + "fit-map-bounds": "拟合地图标记", + "use-default-map-center-position": "地图默认中心位置", + "entities-limit": "加载实体限制", + "markers-settings": "标记设置", + "marker-offset-x": "标记X宽度偏移量", + "marker-offset-y": "标记Y高度偏移量", + "position-function": "位置转换函数应返回x,y坐标", + "draggable-marker": "拖动标记", + "label": "标签设置", + "show-label": "显示标签", + "use-label-function": "标签函数", + "label-pattern": "标签模式(例如:'${entityName}', '${entityName}: (文本${keyName}单位)' )", + "label-function": "标签函数", + "tooltip": "提示栏设置", + "show-tooltip": "显示提示栏", + "show-tooltip-action": "提示拦动作", + "show-tooltip-action-click": "单击显示(默认)", + "show-tooltip-action-hover": "悬停显示", + "auto-close-tooltips": "自动关闭", + "use-tooltip-function": "启用函数", + "tooltip-pattern": "提示栏(例如:'文本${keyName}单位。'或 Link text')", + "tooltip-function": "提示栏函数", + "tooltip-offset-x": "提示栏x标记宽度偏移量", + "tooltip-offset-y": "提示栏y标记高度偏移量", + "color": "颜色设置", + "use-color-function": "启用函数", + "color-function": "颜色函数", + "marker-image": "图片标记设置", + "use-marker-image-function": "启用函数", + "custom-marker-image": "自定义图片标记", + "custom-marker-image-size": "自定义图片标记大小(像素)", + "marker-image-function": "自定义图片标记函数", + "marker-images": "图片标记", + "polygon-settings": "多边形设置", + "show-polygon": "启用多边形", + "polygon-key-name": "多边形键名", + "enable-polygon-edit": "启用编辑", + "polygon-label": "标签", + "show-polygon-label": "启用标签", + "use-polygon-label-function": "启用函数", + "polygon-label-pattern": "标签模式(例如: '${entityName}', '${entityName}:(文本${keyName}单位)' )", + "polygon-label-function": "标签函数", + "polygon-tooltip": "提示栏", + "show-polygon-tooltip": "启用提示栏", + "auto-close-polygon-tooltips": "自动关闭", + "use-polygon-tooltip-function": "启用函数", + "polygon-tooltip-pattern": "提示栏(例如:'文本${keyName}单位'或Link text')", + "polygon-tooltip-function": "提示栏函数", + "polygon-color": "颜色", + "polygon-opacity": "透明度”", + "use-polygon-color-function": "启用函数", + "polygon-color-function": "颜色函数", + "polygon-stroke": "斜线", + "stroke-color": "斜线颜色", + "stroke-opacity": "透明度", + "stroke-weight": "宽度", + "use-polygon-stroke-color-function": "启用函数", + "polygon-stroke-color-function": "颜色函数", + "circle-settings": "圆形设置", + "show-circle": "启用圆形", + "circle-key-name": "圆形键名", + "enable-circle-edit": "启用编辑", + "circle-label": "标签", + "show-circle-label": "启用标签", + "use-circle-label-function": "启用函数", + "circle-label-pattern": "标签模式(例如:'${entityName}', '${entityName}:(文本${keyName}单位)')", + "circle-label-function": "标签函数", + "circle-tooltip": "提示栏", + "show-circle-tooltip": "显示提示栏", + "auto-close-circle-tooltips": "自动关闭", + "use-circle-tooltip-function": "启用函数", + "circle-tooltip-pattern": "提示栏(例如:'文本${keyName}单位。'或Link text')", + "circle-tooltip-function": "提示栏函数", + "circle-fill-color": "填充色", + "circle-fill-color-opacity": "透明度", + "use-circle-fill-color-function": "启用函数", + "circle-fill-color-function": "填充颜色函数", + "circle-stroke": "斜线设置", + "use-circle-stroke-color-function": "启用函数", + "circle-stroke-color-function": "颜色函数", + "markers-clustering-settings": "标记集群设置", + "use-map-markers-clustering": "启用地图标记集群", + "zoom-on-cluster-click": "单击群集时变焦", + "max-cluster-zoom": "集群最大的缩放比例(0-18)", + "max-cluster-radius-pixels": "最大半径", + "cluster-zoom-animation": "放大动画", + "show-markers-bounds-on-cluster-mouse-over": "显示鼠标在群集上时显示标记的边界", + "spiderfy-max-zoom-level": "最大缩放比例(查看所有群集标记)", + "load-optimization": "负载优化", + "cluster-chunked-loading": "块加载集群标记", + "cluster-markers-lazy-load": "延迟加载集群标记", + "editor-settings": "编辑设置", + "enable-snapping": "启用顶点捕捉", + "init-draggable-mode": "启用拖动模式初始化地图", + "hide-all-edit-buttons": "隐藏所有按钮", + "hide-draw-buttons": "隐藏绘制按钮", + "hide-edit-buttons": "隐藏编辑按钮", + "hide-remove-button": "隐藏删除按钮", + "route-map-settings": "路由设置", + "trip-animation-settings": "旅行动画设置", + "normalization-step": "正常数据步长(ms)", + "tooltip-background-color": "工具提示背景颜色", + "tooltip-font-color": "工具提示字体颜色", + "tooltip-opacity": "工具提示不透明度(0-1)", + "auto-close-tooltip": "自动关闭工具提示", + "rotation-angle": "设置标记的附加旋转角度(度)", + "path-settings": "路径设置", + "path-color": "路径颜色", + "use-path-color-function": "使用路径颜色功能", + "path-color-function": "路径颜色功能", + "path-decorator": "路径装饰器", + "use-path-decorator": "使用路径装饰器", + "decorator-symbol": "装饰符号", + "decorator-symbol-arrow-head": "箭头", + "decorator-symbol-dash": "短跑", + "decorator-symbol-size": "装饰器符号尺寸(像素)", + "use-path-decorator-custom-color": "使用路径装饰器自定义颜色", + "decorator-custom-color": "装饰器定制颜色", + "decorator-offset": "装饰器偏移", + "end-decorator-offset": "结束装饰器偏移", + "decorator-repeat": "装饰器重复", + "points-settings": "点设置", + "show-points": "显示点", + "point-color": "点颜色", + "point-size": "点大小(像素)", + "use-point-color-function": "使用点颜色功能", + "point-color-function": "点颜色函数", + "use-point-as-anchor": "使用点作为锚", + "point-as-anchor-function": "点为锚函数", + "independent-point-tooltip": "独立点工具提示", + "clustering-markers": "集群标记", + "use-icon-create-function": "启用集群标记", + "marker-color-function": "标记颜色函数" + }, + "markdown": { + "use-markdown-text-function": "使用markdown/HTML函数", + "markdown-text-function": "Markdown/HTML函数", + "markdown-text-pattern": "Markdown/HTML模式(markdown或HTML变量, 例如:'${entityName}或${keyName}一些文本')", + "markdown-css": "Markdown/HTML CSS" + }, + "simple-card": { + "label-position": "标签位置", + "label-position-left": "左", + "label-position-top": "上" + }, + "table": { + "common-table-settings": "表格设置", + "enable-search": "启用搜索", + "enable-sticky-header": "始终显示标题", + "enable-sticky-action": "始终显示动作", + "hidden-cell-button-display-mode": "隐藏单元按钮操作显示模式", + "show-empty-space-hidden-action": "显示空空间而不是隐藏的单元按钮动作", + "dont-reserve-space-hidden-action": "不要为隐藏动作按钮保留空间", + "display-timestamp": "显示时间", + "display-milliseconds": "显示毫秒", + "display-pagination": "显示分页", + "default-page-size": "分页数量", + "use-entity-label-tab-name": "实体标签名称", + "hide-empty-lines": "隐藏空线", + "row-style": "行样式", + "use-row-style-function": "启用样式函数", + "row-style-function": "样式函数", + "cell-style": "列样式", + "use-cell-style-function": "启用样式函数", + "cell-style-function": "样式函数", + "cell-content": "内容", + "use-cell-content-function": "启用内容函数", + "cell-content-function": "内容函数", + "show-latest-data-column": "显示最新数据列", + "latest-data-column-order": "样式函数", + "entities-table-title": "实体表格标题", + "enable-select-column-display": "启用数据列", + "display-entity-name": "显示实体名称", + "entity-name-column-title": "实体名称列标题", + "display-entity-label": "显示实体标签", + "entity-label-column-title": "实体标签列标题", + "display-entity-type": "显示实体类型", + "default-sort-order": "默认排序", + "custom-title": "Custom header title", + "column-width": "列宽", + "default-column-visibility": "默认显示", + "column-visibility-visible": "显示", + "column-visibility-hidden": "隐藏", + "column-visibility-hidden-mobile": "Hidden in mobile mode", + "column-selection-to-display": "“要显示”列中的列选择", + "column-selection-to-display-enabled": "启用", + "column-selection-to-display-disabled": "禁用", + "alarms-table-title": "警报标题", + "enable-alarms-selection": "启用警报选择", + "enable-alarms-search": "启用警报搜索", + "enable-alarm-filter": "启用警报过滤器", + "display-alarm-details": "显示警报详细", + "allow-alarms-ack": "允许确认警报", + "allow-alarms-clear": "允许清除警报" + }, + "value-source": { + "value-source": "数值源", + "predefined-value": "预定义值", + "entity-attribute": "从实体属性中获取值", + "value": "数据值", + "source-entity-alias": "实体别", + "source-entity-attribute": "实体属性" + }, + "widget-font": { + "font-family": "字体", + "size": "大小", + "relative-font-size": "相对字体大小", + "font-style": "样式", + "font-style-normal": "常规", + "font-style-italic": "斜体", + "font-style-oblique": "倾斜", + "font-weight": "字形", + "font-weight-normal": "常规", + "font-weight-bold": "加粗", + "font-weight-bolder": "相对加粗", + "font-weight-lighter": "相对常规", + "color": "颜色", + "shadow-color": "阴影颜色" + } + }, + "icon": { + "icon": "图标", + "select-icon": "选择图标", + "material-icons": "素材图标", + "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}}" + }, + "custom": { + "widget-action": { + "action-cell-button": "单元格click", + "row-click": "数据行click", + "polygon-click": "多边形click", + "marker-click": "标记click", + "circle-click": "圆形click", + "tooltip-tag-action": "提示框click", + "node-selected": "节点选中click", + "element-click": "HTML元素click", + "pie-slice-click": " 饼状click", + "row-double-click": "双击clcik" + } + }, + "paginator": { + "items-per-page": "每页数量:", + "first-page-label": "第一页", + "last-page-label": "最后一页", + "next-page-label": "下一页", + "previous-page-label": "上一页", + "items-per-page-separator": "-" + }, + "language": { + "language": "语言", + "locales": { + "de_DE": "德语", + "fr_FR": "法语", + "zh_CN": "简体中文", + "zh_TW": "繁體中文", + "en_US": "英语", + "it_IT": "意大利语", + "ko_KR": "韩语", + "ru_RU": "俄罗斯语", + "es_ES": "西班牙语", + "es_CA": "Catalan", + "ja_JP": "日本語", + "tr_TR": "土耳其语", + "fa_IR": "波斯语", + "uk_UA": "乌克兰语", + "cs_CZ": "捷克语", + "el_GR": "希腊语", + "ro_RO": "罗马尼亚语", + "lv_LV": "拉脱维亚语", + "ka_GE": "格鲁吉亚语", + "pt_BR": "葡萄牙语", + "sl_SI": "斯洛文尼亚语" + } } - } }