From 27ee393028c8e114d7cf21cb28d49018f481949e Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Thu, 26 Mar 2026 12:33:17 +0200 Subject: [PATCH 01/24] CE: Added request body template support for REST API call node --- .../rule/engine/api/util/TbNodeUtils.java | 32 ++- .../rule/engine/api/util/TbNodeUtilsTest.java | 44 ++++ .../rule/engine/rest/TbHttpClient.java | 21 +- .../rule/engine/rest/TbRestApiCallNode.java | 4 +- .../rest/TbRestApiCallNodeConfiguration.java | 1 + .../rule/engine/rest/TbHttpClientTest.java | 4 +- .../engine/rest/TbRestApiCallNodeTest.java | 217 ++++++++++++++---- .../rest/TbSendRestApiCallReplyNodeTest.java | 3 +- .../rest-api-call-config.component.html | 13 ++ .../rest-api-call-config.component.ts | 1 + .../assets/locale/locale.constant-en_US.json | 5 +- 11 files changed, 280 insertions(+), 65 deletions(-) diff --git a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java index 4a0f1883c0..8fac42e029 100644 --- a/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java +++ b/rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java @@ -15,6 +15,7 @@ */ package org.thingsboard.rule.engine.api.util; +import com.fasterxml.jackson.core.io.JsonStringEncoder; import com.fasterxml.jackson.databind.JsonNode; import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.rule.engine.api.TbNodeConfiguration; @@ -57,8 +58,12 @@ public final class TbNodeUtils { } public static String processPattern(String pattern, TbMsg tbMsg) { + return processPattern(pattern, tbMsg, false); + } + + public static String processPattern(String pattern, TbMsg tbMsg, boolean escapeJsonValues) { try { - String result = processPattern(pattern, tbMsg.getMetaData()); + String result = processPattern(pattern, tbMsg.getMetaData(), escapeJsonValues); JsonNode json = JacksonUtil.toJsonNode(tbMsg.getData()); result = result.replace(ALL_DATA_TEMPLATE, JacksonUtil.toString(json)); @@ -79,7 +84,11 @@ public final class TbNodeUtils { } if (jsonNode != null && jsonNode.isValueNode()) { - result = result.replace(formatDataVarTemplate(group), jsonNode.asText()); + String value = jsonNode.asText(); + if (escapeJsonValues) { + value = escapeJsonValue(value); + } + result = result.replace(formatDataVarTemplate(group), value); } } } @@ -89,22 +98,31 @@ public final class TbNodeUtils { } } - private static String processPattern(String pattern, TbMsgMetaData metaData) { + private static String processPattern(String pattern, TbMsgMetaData metaData, boolean escapeJsonValues) { String replacement = metaData.isEmpty() ? "{}" : JacksonUtil.toString(metaData.getData()); pattern = pattern.replace(ALL_METADATA_TEMPLATE, replacement); - return processTemplate(pattern, metaData.values()); + return processTemplate(pattern, metaData.values(), escapeJsonValues); + } + + private static String processPattern(String pattern, TbMsgMetaData metaData) { + return processPattern(pattern, metaData, false); } public static String processTemplate(String template, Map data) { + return processTemplate(template, data, false); + } + + private static String processTemplate(String template, Map data, boolean escapeJsonValues) { String result = template; for (Map.Entry kv : data.entrySet()) { - result = processVar(result, kv.getKey(), kv.getValue()); + String value = escapeJsonValues ? escapeJsonValue(kv.getValue()) : kv.getValue(); + result = result.replace(formatMetadataVarTemplate(kv.getKey()), value); } return result; } - private static String processVar(String pattern, String key, String val) { - return pattern.replace(formatMetadataVarTemplate(key), val); + private static String escapeJsonValue(String value) { + return new String(JsonStringEncoder.getInstance().quoteAsString(value)); } static String formatDataVarTemplate(String key) { diff --git a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java index 18003d6b70..882e2080d7 100644 --- a/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java +++ b/rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java @@ -305,6 +305,50 @@ public class TbNodeUtilsTest { assertThat(actual, is(expected)); } + @Test + public void testProcessPatternWithJsonEscaping() { + String pattern = "{\"name\":\"${user}\",\"desc\":\"$[description]\"}"; + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("user", "John \"Doe\""); + + ObjectNode node = JacksonUtil.newObjectNode(); + node.put("description", "line1\nline2"); + + TbMsg msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(TenantId.SYS_TENANT_ID) + .copyMetaData(md) + .data(JacksonUtil.toString(node)) + .build(); + + String result = TbNodeUtils.processPattern(pattern, msg, true); + Assertions.assertEquals("{\"name\":\"John \\\"Doe\\\"\",\"desc\":\"line1\\nline2\"}", result); + + // Verify the result is valid JSON + Assertions.assertDoesNotThrow(() -> JacksonUtil.toJsonNode(result)); + } + + @Test + public void testProcessPatternWithoutJsonEscaping() { + String pattern = "Hello ${user}, desc: $[description]"; + TbMsgMetaData md = new TbMsgMetaData(); + md.putValue("user", "John \"Doe\""); + + ObjectNode node = JacksonUtil.newObjectNode(); + node.put("description", "line1\nline2"); + + TbMsg msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(TenantId.SYS_TENANT_ID) + .copyMetaData(md) + .data(JacksonUtil.toString(node)) + .build(); + + // Without escaping, raw values are substituted as-is + String result = TbNodeUtils.processPattern(pattern, msg, false); + Assertions.assertEquals("Hello John \"Doe\", desc: line1\nline2", result); + } + @Test public void testMixedAllDataMetadataAndNormalTemplates() { // GIVEN diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java index 8db6a3ccaf..4a855f1579 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java @@ -55,6 +55,7 @@ import java.nio.charset.StandardCharsets; import java.util.Base64; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Properties; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; @@ -182,10 +183,7 @@ public class TbHttpClient { } EventLoopGroup getSharedOrCreateEventLoopGroup(EventLoopGroup eventLoopGroupShared) { - if (eventLoopGroupShared != null) { - return eventLoopGroupShared; - } - return this.eventLoopGroup = new NioEventLoopGroup(); + return Objects.requireNonNullElseGet(eventLoopGroupShared, () -> this.eventLoopGroup = new NioEventLoopGroup()); } private void checkSystemProxyProperties() throws TbNodeException { @@ -243,7 +241,7 @@ public class TbHttpClient { if ((HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) || HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method)) && !config.isIgnoreRequestBody()) { - request.body(BodyInserters.fromValue(getData(msg, config.isParseToPlainText()))); + request.body(BodyInserters.fromValue(getRequestBody(msg))); } request @@ -275,7 +273,7 @@ public class TbHttpClient { if (origin instanceof WebClientResponseException restClientResponseException && restClientResponseException.getStatusCode().is2xxSuccessful()) { // return cause instead of original exception in case 2xx status code - // this will provide meaningful error message to the user + // this will provide a meaningful error message to the user return new RuntimeException(restClientResponseException.getCause()); } return origin; @@ -340,6 +338,15 @@ public class TbHttpClient { return UriComponentsBuilder.fromUriString(endpointUrl).build().encode().toUri(); } + private Object getRequestBody(TbMsg msg) { + if (StringUtils.isNotBlank(config.getRequestBodyTemplate())) { + boolean escapeJson = !config.isParseToPlainText(); + String processedTemplate = TbNodeUtils.processPattern(config.getRequestBodyTemplate(), msg, escapeJson); + return config.isParseToPlainText() ? processedTemplate : JacksonUtil.toJsonNode(processedTemplate); + } + return getData(msg, config.isParseToPlainText()); + } + private Object getData(TbMsg tbMsg, boolean parseToPlainText) { String data = tbMsg.getData(); return parseToPlainText ? JacksonUtil.toPlainText(data) : JacksonUtil.toJsonNode(data); @@ -363,7 +370,7 @@ public class TbHttpClient { headers.forEach((key, values) -> { if (values != null && !values.isEmpty()) { if (values.size() == 1) { - consumer.accept(key, values.get(0)); + consumer.accept(key, values.getFirst()); } else { consumer.accept(key, JacksonUtil.toString(values)); } diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java index 5ffe37b102..50f4ef8d9d 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java @@ -41,7 +41,9 @@ import static org.thingsboard.server.dao.service.ConstraintValidator.validateFie version = 4, nodeDescription = "Invoke REST API calls to external REST server", nodeDetails = "Will invoke REST API call GET | POST | PUT | DELETE to external REST server. " + - "Message payload added into Request body. Configured attributes can be added into Headers from Message Metadata." + + "Message payload is used as the request body by default. " + + "Optionally, a request body template can be configured with ${metadataKey} and $[messageKey] placeholders. " + + "Configured attributes can be added into Headers from Message Metadata." + " Outbound message will contain response fields " + "(status, statusCode, statusReason and response headers) in the Message Metadata." + " Response body saved in outbound Message payload. " + diff --git a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java index 9377d83949..0994ec19e3 100644 --- a/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java +++ b/rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java @@ -51,6 +51,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration data = metaData.getData(); Assertions.assertEquals(2, data.size()); - Assertions.assertEquals(data.get("Content-Type"), "binary"); - Assertions.assertEquals(data.get("Set-Cookie"), "[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]"); + Assertions.assertEquals("binary", data.get("Content-Type")); + Assertions.assertEquals("[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]", data.get("Set-Cookie")); } } diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index 3d3abde30c..fbe4aa4136 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -16,13 +16,9 @@ package org.thingsboard.rule.engine.rest; import com.datastax.oss.driver.api.core.uuid.Uuids; -import org.apache.http.HttpException; -import org.apache.http.HttpRequest; -import org.apache.http.HttpResponse; import org.apache.http.config.SocketConfig; import org.apache.http.impl.bootstrap.HttpServer; import org.apache.http.impl.bootstrap.ServerBootstrap; -import org.apache.http.protocol.HttpContext; import org.apache.http.protocol.HttpRequestHandler; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -52,6 +48,8 @@ import org.thingsboard.server.common.msg.TbMsgMetaData; import org.thingsboard.server.exception.DataValidationException; import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -79,11 +77,11 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { @Mock private TbContext ctx; - private EntityId originator = new DeviceId(Uuids.timeBased()); - private TbMsgMetaData metaData = new TbMsgMetaData(); + private final EntityId originator = new DeviceId(Uuids.timeBased()); + private final TbMsgMetaData metaData = new TbMsgMetaData(); - private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); - private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); + private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); + private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); private HttpServer server; @@ -217,22 +215,17 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { public void deleteRequestWithoutBody() throws IOException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final String path = "/path/to/delete"; - setupServer("*", new HttpRequestHandler() { - - @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { - try { - assertEquals(request.getRequestLine().getUri(), path, "Request path matches"); - assertTrue(request.containsHeader("Foo"), "Custom header included"); - assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); - response.setStatusCode(200); - latch.countDown(); - } catch (Exception e) { - System.out.println("Exception handling request: " + e.toString()); - e.printStackTrace(); - latch.countDown(); - } + setupServer("*", (request, response, _) -> { + try { + assertEquals(path, request.getRequestLine().getUri(), "Request path matches"); + assertTrue(request.containsHeader("Foo"), "Custom header included"); + assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); + response.setStatusCode(200); + latch.countDown(); + } catch (Exception e) { + System.out.println("Exception handling request: " + e); + e.printStackTrace(); + latch.countDown(); } }); @@ -269,28 +262,23 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { public void deleteRequestWithBody() throws IOException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final String path = "/path/to/delete"; - setupServer("*", new HttpRequestHandler() { - - @Override - public void handle(HttpRequest request, HttpResponse response, HttpContext context) - throws HttpException, IOException { - try { - assertEquals(path, request.getRequestLine().getUri(), "Request path matches"); - assertTrue(request.containsHeader("Content-Type"), "Content-Type included"); - assertEquals("application/json", - request.getFirstHeader("Content-Type").getValue(), "Content-Type value"); - assertTrue(request.containsHeader("Content-Length"), "Content-Length included"); - assertEquals("2", - request.getFirstHeader("Content-Length").getValue(), "Content-Length value"); - assertTrue(request.containsHeader("Foo"), "Custom header included"); - assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); - response.setStatusCode(200); - latch.countDown(); - } catch (Exception e) { - System.out.println("Exception handling request: " + e.toString()); - e.printStackTrace(); - latch.countDown(); - } + setupServer("*", (request, response, _) -> { + try { + assertEquals(path, request.getRequestLine().getUri(), "Request path matches"); + assertTrue(request.containsHeader("Content-Type"), "Content-Type included"); + assertEquals("application/json", + request.getFirstHeader("Content-Type").getValue(), "Content-Type value"); + assertTrue(request.containsHeader("Content-Length"), "Content-Length included"); + assertEquals("2", + request.getFirstHeader("Content-Length").getValue(), "Content-Length value"); + assertTrue(request.containsHeader("Foo"), "Custom header included"); + assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); + response.setStatusCode(200); + latch.countDown(); + } catch (Exception e) { + System.out.println("Exception handling request: " + e); + e.printStackTrace(); + latch.countDown(); } }); @@ -323,6 +311,143 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); } + @Test + public void postRequestWithBodyTemplate() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final String path = "/api/token"; + final String[] capturedBody = new String[1]; + setupServerWithBodyCapture(capturedBody, latch); + + TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); + config.setRequestMethod("POST"); + config.setRequestBodyTemplate("{\"grant_type\":\"client_credentials\",\"client_id\":\"${clientId}\",\"value\":\"$[token]\"}"); + config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); + initWithConfig(config); + + metaData.putValue("clientId", "my-client-123"); + TbMsg msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(originator) + .copyMetaData(metaData) + .dataType(TbMsgDataType.JSON) + .data("{\"token\":\"abc-xyz\"}") + .ruleChainId(ruleChainId) + .ruleNodeId(ruleNodeId) + .build(); + restNode.onMsg(ctx, msg); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Server handled request"); + assertEquals("{\"grant_type\":\"client_credentials\",\"client_id\":\"my-client-123\",\"value\":\"abc-xyz\"}", capturedBody[0]); + } + + @Test + public void postRequestWithBodyTemplateAndParseToPlainText() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final String path = "/api/text"; + final String[] capturedBody = new String[1]; + setupServerWithBodyCapture(capturedBody, latch); + + TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); + config.setRequestMethod("POST"); + config.setParseToPlainText(true); + config.setRequestBodyTemplate("Hello ${name}, your token is $[token]!"); + config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); + initWithConfig(config); + + metaData.putValue("name", "World"); + TbMsg msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(originator) + .copyMetaData(metaData) + .dataType(TbMsgDataType.JSON) + .data("{\"token\":\"abc-xyz\"}") + .ruleChainId(ruleChainId) + .ruleNodeId(ruleNodeId) + .build(); + restNode.onMsg(ctx, msg); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Server handled request"); + assertEquals("Hello World, your token is abc-xyz!", capturedBody[0]); + } + + @Test + public void postRequestWithBodyTemplateEscapesJsonSpecialChars() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final String path = "/api/token"; + final String[] capturedBody = new String[1]; + setupServerWithBodyCapture(capturedBody, latch); + + TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); + config.setRequestMethod("POST"); + config.setRequestBodyTemplate("{\"name\":\"${userName}\",\"desc\":\"$[description]\"}"); + config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); + initWithConfig(config); + + metaData.putValue("userName", "John \"Doe\""); + TbMsg msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(originator) + .copyMetaData(metaData) + .dataType(TbMsgDataType.JSON) + .data("{\"description\":\"line1\\nline2\"}") + .ruleChainId(ruleChainId) + .ruleNodeId(ruleNodeId) + .build(); + restNode.onMsg(ctx, msg); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Server handled request"); + assertEquals("{\"name\":\"John \\\"Doe\\\"\",\"desc\":\"line1\\nline2\"}", capturedBody[0]); + } + + @Test + public void postRequestWithEmptyBodyTemplateUsesMessageData() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final String path = "/api/data"; + final String[] capturedBody = new String[1]; + setupServerWithBodyCapture(capturedBody, latch); + + TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); + config.setRequestMethod("POST"); + // requestBodyTemplate is null by default — should use msg.getData() + config.setRestEndpointUrlPattern(String.format("http://localhost:%d%s", server.getLocalPort(), path)); + initWithConfig(config); + + TbMsg msg = TbMsg.newMsg() + .type(TbMsgType.POST_TELEMETRY_REQUEST) + .originator(originator) + .copyMetaData(metaData) + .dataType(TbMsgDataType.JSON) + .data("{\"temperature\":25}") + .ruleChainId(ruleChainId) + .ruleNodeId(ruleNodeId) + .build(); + restNode.onMsg(ctx, msg); + + assertTrue(latch.await(10, TimeUnit.SECONDS), "Server handled request"); + + ArgumentCaptor msgCaptor = ArgumentCaptor.forClass(TbMsg.class); + ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); + ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); + verify(ctx, timeout(10_000)).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); + assertEquals("{\"temperature\":25}", capturedBody[0]); + } + + private void setupServerWithBodyCapture(String[] capturedBody, CountDownLatch latch) throws IOException { + setupServer("*", (request, response, _) -> { + try { + if (request instanceof org.apache.http.HttpEntityEnclosingRequest entityRequest) { + InputStream is = entityRequest.getEntity().getContent(); + capturedBody[0] = new String(is.readAllBytes(), StandardCharsets.UTF_8); + } + response.setStatusCode(200); + latch.countDown(); + } catch (Exception e) { + e.printStackTrace(); + latch.countDown(); + } + }); + } + private static Stream givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { return Stream.of( Arguments.of(0, diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNodeTest.java index 7b00d249ff..062e2d0a2e 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNodeTest.java @@ -51,7 +51,7 @@ public class TbSendRestApiCallReplyNodeTest { private TbSendRestApiCallReplyNode node; private TbSendRestApiCallReplyNodeConfiguration config; - + @Mock private TbContext ctxMock; @Mock @@ -140,4 +140,5 @@ public class TbSendRestApiCallReplyNodeTest { TbMsg.EMPTY_STRING, "Request body is empty!") ); } + } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html index b2fdb3a14c..948089721c 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html @@ -129,6 +129,19 @@ {{ 'rule-node-config.ignore-request-body' | translate }} +
+ +
+ + rule-node-config.request-body-template + + rule-node-config.request-body-template-empty-hint + +
rule-node-config.read-timeout diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.ts index c9694ccd94..32ea3d57d9 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.ts @@ -51,6 +51,7 @@ export class RestApiCallConfigComponent extends RuleNodeConfigurationComponent { requestMethod: [configuration ? configuration.requestMethod : null, [Validators.required]], parseToPlainText: [configuration ? configuration.parseToPlainText : false, []], ignoreRequestBody: [configuration ? configuration.ignoreRequestBody : false, []], + requestBodyTemplate: [configuration ? configuration.requestBodyTemplate : null, []], enableProxy: [configuration ? configuration.enableProxy : false, []], useSystemProxyProperties: [configuration ? configuration.enableProxy : false, []], proxyHost: [configuration ? configuration.proxyHost : null, []], 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 4fdc707c1e..0dddc1a32e 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5511,8 +5511,11 @@ "endpoint-url-pattern-required": "Endpoint URL pattern is required", "request-method": "Request method", "ignore-request-body": "Without request body", + "request-body-template": "Request body template", + "request-body-template-hint": "Use ${metadataKey} for value from metadata, $[messageKey] for value from message body", + "request-body-template-empty-hint": "If empty, the incoming message payload is used as the request body. Supports any content type — use 'Parse to plain text' option to send non-JSON content", "parse-to-plain-text": "Parse to plain text", - "parse-to-plain-text-hint": "If selected, request body message payload will be transformed from JSON string to plain text, e.g. msg = \"Hello,\\t\"world\"\" will be parsed to Hello, \"world\"", + "parse-to-plain-text-hint": "If selected, the request body (message payload or body template result) will be sent as a plain text string instead of being parsed as JSON, e.g. msg = \"Hello,\\t\"world\"\" will be parsed to Hello, \"world\"", "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", From 584214ebff3b5ef8a31da8a69a14ad3078e76aa5 Mon Sep 17 00:00:00 2001 From: Vladyslav_Prykhodko Date: Tue, 31 Mar 2026 18:49:07 +0300 Subject: [PATCH 02/24] Optimize REST API call node request body template UI Consolidate body template section: use outline mat-form-field with always-floating label, merge variable syntax and empty-field hints into a single mat-hint, and remove redundant translation key. --- .../rest-api-call-config.component.html | 26 +++++++++---------- .../rest-api-call-config.component.scss | 3 +++ .../assets/locale/locale.constant-en_US.json | 3 +-- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html index 948089721c..0bb2b708f9 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html @@ -124,24 +124,22 @@ -
+
{{ 'rule-node-config.ignore-request-body' | translate }} + @if(!restApiCallConfigForm.get('ignoreRequestBody').value) { + + rule-node-config.request-body-template + + + + }
-
- -
- - rule-node-config.request-body-template - - rule-node-config.request-body-template-empty-hint - -
rule-node-config.read-timeout diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.scss b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.scss index a5ed420c88..a4ad3fe4f8 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.scss +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.scss @@ -26,4 +26,7 @@ } } } + textarea.tb-enable-vertical-resize { + resize: vertical; + } } 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 0dddc1a32e..deeb3117be 100644 --- a/ui-ngx/src/assets/locale/locale.constant-en_US.json +++ b/ui-ngx/src/assets/locale/locale.constant-en_US.json @@ -5512,8 +5512,7 @@ "request-method": "Request method", "ignore-request-body": "Without request body", "request-body-template": "Request body template", - "request-body-template-hint": "Use ${metadataKey} for value from metadata, $[messageKey] for value from message body", - "request-body-template-empty-hint": "If empty, the incoming message payload is used as the request body. Supports any content type — use 'Parse to plain text' option to send non-JSON content", + "request-body-template-hint": "Use ${metadataKey} for metadata, $[messageKey] for message values. If empty, the message payload is used as the request body. For non-JSON payloads, enable 'Parse to plain text'.", "parse-to-plain-text": "Parse to plain text", "parse-to-plain-text-hint": "If selected, the request body (message payload or body template result) will be sent as a plain text string instead of being parsed as JSON, e.g. msg = \"Hello,\\t\"world\"\" will be parsed to Hello, \"world\"", "read-timeout": "Read timeout in millis", From db1d2597093efd2bcfa5ff2722bb27cdee6f5916 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 1 Apr 2026 11:07:41 +0300 Subject: [PATCH 03/24] Fix TbRestApiCallNodeTest --- .../engine/rest/TbRestApiCallNodeTest.java | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java index fbe4aa4136..5299f9160a 100644 --- a/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java +++ b/rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java @@ -55,6 +55,7 @@ import java.util.Collections; import java.util.List; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicReference; import java.util.stream.Stream; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -69,8 +70,6 @@ import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { - private RuleNode ruleNode; - @Spy private TbRestApiCallNode restNode; @@ -106,7 +105,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { @BeforeEach public void setup() { - ruleNode = new RuleNode(); + RuleNode ruleNode = new RuleNode(); ruleNode.setId(ruleNodeId); ruleNode.setName("Test REST API call node"); lenient().when(ctx.getSelf()).thenReturn(ruleNode); @@ -315,7 +314,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { public void postRequestWithBodyTemplate() throws IOException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final String path = "/api/token"; - final String[] capturedBody = new String[1]; + final AtomicReference capturedBody = new AtomicReference<>(); setupServerWithBodyCapture(capturedBody, latch); TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); @@ -337,14 +336,14 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { restNode.onMsg(ctx, msg); assertTrue(latch.await(10, TimeUnit.SECONDS), "Server handled request"); - assertEquals("{\"grant_type\":\"client_credentials\",\"client_id\":\"my-client-123\",\"value\":\"abc-xyz\"}", capturedBody[0]); + assertEquals("{\"grant_type\":\"client_credentials\",\"client_id\":\"my-client-123\",\"value\":\"abc-xyz\"}", capturedBody.get()); } @Test public void postRequestWithBodyTemplateAndParseToPlainText() throws IOException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final String path = "/api/text"; - final String[] capturedBody = new String[1]; + final AtomicReference capturedBody = new AtomicReference<>(); setupServerWithBodyCapture(capturedBody, latch); TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); @@ -367,14 +366,14 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { restNode.onMsg(ctx, msg); assertTrue(latch.await(10, TimeUnit.SECONDS), "Server handled request"); - assertEquals("Hello World, your token is abc-xyz!", capturedBody[0]); + assertEquals("Hello World, your token is abc-xyz!", capturedBody.get()); } @Test public void postRequestWithBodyTemplateEscapesJsonSpecialChars() throws IOException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final String path = "/api/token"; - final String[] capturedBody = new String[1]; + final AtomicReference capturedBody = new AtomicReference<>(); setupServerWithBodyCapture(capturedBody, latch); TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); @@ -396,14 +395,14 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { restNode.onMsg(ctx, msg); assertTrue(latch.await(10, TimeUnit.SECONDS), "Server handled request"); - assertEquals("{\"name\":\"John \\\"Doe\\\"\",\"desc\":\"line1\\nline2\"}", capturedBody[0]); + assertEquals("{\"name\":\"John \\\"Doe\\\"\",\"desc\":\"line1\\nline2\"}", capturedBody.get()); } @Test public void postRequestWithEmptyBodyTemplateUsesMessageData() throws IOException, InterruptedException { final CountDownLatch latch = new CountDownLatch(1); final String path = "/api/data"; - final String[] capturedBody = new String[1]; + final AtomicReference capturedBody = new AtomicReference<>(); setupServerWithBodyCapture(capturedBody, latch); TbRestApiCallNodeConfiguration config = new TbRestApiCallNodeConfiguration().defaultConfiguration(); @@ -429,15 +428,15 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class); ArgumentCaptor dataCaptor = ArgumentCaptor.forClass(String.class); verify(ctx, timeout(10_000)).transformMsg(msgCaptor.capture(), metadataCaptor.capture(), dataCaptor.capture()); - assertEquals("{\"temperature\":25}", capturedBody[0]); + assertEquals("{\"temperature\":25}", capturedBody.get()); } - private void setupServerWithBodyCapture(String[] capturedBody, CountDownLatch latch) throws IOException { + private void setupServerWithBodyCapture(AtomicReference capturedBody, CountDownLatch latch) throws IOException { setupServer("*", (request, response, _) -> { try { if (request instanceof org.apache.http.HttpEntityEnclosingRequest entityRequest) { InputStream is = entityRequest.getEntity().getContent(); - capturedBody[0] = new String(is.readAllBytes(), StandardCharsets.UTF_8); + capturedBody.set(new String(is.readAllBytes(), StandardCharsets.UTF_8)); } response.setStatusCode(200); latch.countDown(); From 781391a4eb880cc19dc7c65f571eb6e3a8e0deb9 Mon Sep 17 00:00:00 2001 From: Andrii Landiak Date: Wed, 1 Apr 2026 12:17:52 +0300 Subject: [PATCH 04/24] Improved request body template UI to match headers panel style --- .../rule-node/external/rest-api-call-config.component.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html index 0bb2b708f9..bf5f29d399 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html @@ -129,6 +129,7 @@ {{ 'rule-node-config.ignore-request-body' | translate }} @if(!restApiCallConfigForm.get('ignoreRequestBody').value) { +
rule-node-config.request-body-template - }
From 9933b9eb2c326b725583d8a70ae4894e0566a576 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Wed, 1 Apr 2026 18:23:36 +0200 Subject: [PATCH 05/24] Moved part of inputs to Outline appearance --- ...-widget-to-dashboard-dialog.component.html | 1 + .../event/event-filter-panel.component.html | 8 +- .../event/event-table-header.component.html | 2 +- ...tenant-profile-autocomplete.component.html | 2 +- .../tenant-profile-autocomplete.component.ts | 4 + .../profile/tenant-profile.component.html | 6 +- ...enant-profile-configuration.component.html | 96 +++++++++---------- .../rate-limits-list.component.html | 4 +- .../queue/queue-form.component.html | 26 ++--- .../relation/relation-dialog.component.html | 4 +- .../relation/relation-table.component.html | 4 +- ...nced-processing-setting-row.component.html | 2 +- ...vanced-processing-setting-row.component.ts | 4 + ...advanced-processing-setting.component.html | 5 + .../assign-customer-config.component.html | 2 +- .../action/attributes-config.component.html | 7 +- .../action/clear-alarm-config.component.html | 2 +- .../action/create-alarm-config.component.html | 10 +- .../create-relation-config.component.html | 6 +- .../delete-attributes-config.component.html | 6 +- .../delete-relation-config.component.html | 5 +- .../action/device-state-config.component.html | 2 +- .../action/generator-config.component.html | 5 +- .../gps-geo-action-config.component.html | 27 +++--- .../math-function-config.component.html | 14 +-- .../action/msg-count-config.component.html | 4 +- .../action/msg-delay-config.component.html | 5 +- .../push-to-cloud-config.component.html | 4 +- .../action/push-to-edge-config.component.html | 4 +- .../action/rpc-reply-config.component.html | 6 +- .../action/rpc-request-config.component.html | 2 +- ...save-to-custom-table-config.component.html | 4 +- ...-rest-api-call-reply-config.component.html | 4 +- .../action/timeseries-config.component.html | 4 +- .../unassign-customer-config.component.html | 2 +- .../common/credentials-config.component.html | 6 +- ...vice-relations-query-config.component.html | 6 +- .../math-function-autocomplete.component.html | 2 +- .../math-function-autocomplete.component.ts | 4 + .../message-types-config.component.html | 2 +- ...t-message-type-autocomplete.component.html | 4 +- ...put-message-type-autocomplete.component.ts | 5 +- .../relations-query-config.component.html | 4 +- .../common/select-attributes.component.html | 4 + .../calculate-delta-config.component.html | 8 +- .../entity-details-config.component.html | 1 + ...emetry-from-database-config.component.html | 22 ++--- .../external/ai-config.component.html | 2 + .../azure-iot-hub-config.component.html | 14 +-- .../external/kafka-config.component.html | 18 ++-- .../external/lambda-config.component.html | 14 +-- .../external/mqtt-config.component.html | 10 +- .../notification-config.component.html | 2 + .../external/pubsub-config.component.html | 4 +- .../external/rabbit-mq-config.component.html | 22 ++--- .../rest-api-call-config.component.html | 18 ++-- .../external/send-email-config.component.html | 22 ++--- .../external/send-sms-config.component.html | 6 +- .../external/slack-config.component.html | 5 +- .../external/sns-config.component.html | 8 +- .../external/sqs-config.component.html | 12 +-- .../check-message-config.component.html | 2 + .../check-relation-config.component.html | 5 +- .../gps-geo-filter-config.component.html | 22 ++--- .../originator-type-config.component.html | 1 + .../flow/rule-chain-input.component.html | 1 + .../change-originator-config.component.html | 8 +- .../copy-keys-config.component.html | 1 + .../deduplication-config.component.html | 16 ++-- .../delete-keys-config.component.html | 1 + .../node-json-path-config.component.html | 2 +- .../to-email-config.component.html | 16 ++-- ...-sns-provider-configuration.component.html | 6 +- ...-sms-provider-configuration.component.html | 30 +++--- .../sms-provider-configuration.component.html | 2 +- ...-sms-provider-configuration.component.html | 6 +- .../admin/general-settings.component.html | 6 +- .../pages/admin/home-settings.component.html | 1 + .../pages/admin/mail-server.component.html | 43 +++++---- .../oauth2/clients/client.component.html | 70 +++++++------- .../oauth2/domains/domain.component.html | 6 +- .../admin/security-settings.component.html | 34 +++---- .../admin/security-settings.component.scss | 18 ++-- .../pages/admin/sms-provider.component.html | 2 +- .../admin/trendz-settings.component.html | 4 +- .../home/pages/profile/profile.component.html | 12 ++- .../rule-node-details.component.html | 3 +- .../pages/security/security.component.html | 6 +- .../home/pages/tenant/tenant.component.html | 14 +-- .../pages/widget/widget-editor.component.html | 25 ++--- .../node-script-test-dialog.component.html | 3 +- .../entity/entity-list-select.component.html | 2 + .../entity/entity-list-select.component.ts | 4 + .../image/image-dialog.component.html | 2 +- .../material-icon-select.component.html | 2 +- .../material-icon-select.component.ts | 4 + .../message-type-autocomplete.component.html | 2 +- .../message-type-autocomplete.component.ts | 4 + .../rule-chain-select.component.html | 2 +- 99 files changed, 488 insertions(+), 418 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html index 769c553635..4fc7c6a911 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html @@ -36,6 +36,7 @@
dashboard.select-existing diff --git a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html index f59cac1d7f..fa9044ed9b 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html @@ -20,7 +20,7 @@ @for (column of showColumns; track column) { @switch (column.key) { @case (isSelector(column.key)) { - + {{ column.title | translate}} {{ 'event.all-events' | translate}} @@ -33,7 +33,7 @@ } @case (isNumberFields(column.key)) { - + {{ column.title | translate}} @if (eventFilterFormGroup.get(column.key).hasError('min')) { @@ -50,13 +50,13 @@ } @case ('errorStr') { - + {{ column.title | translate}} } @default { - + {{ column.title | translate}} diff --git a/ui-ngx/src/app/modules/home/components/event/event-table-header.component.html b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.html index d78c466397..df31150835 100644 --- a/ui-ngx/src/app/modules/home/components/event/event-table-header.component.html +++ b/ui-ngx/src/app/modules/home/components/event/event-table-header.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + event.event-type diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html index 7b639f134c..4d7b127aa3 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + tenant-profile.tenant-profile (); diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html index ca553da0b2..3a53b4cbc9 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html @@ -50,7 +50,7 @@
- + tenant-profile.name @if (entityForm.get('name').hasError('required')) { @@ -103,9 +103,9 @@
- + tenant-profile.description - + diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html index 7100fce348..ddd159388a 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html @@ -21,7 +21,7 @@ {{ 'tenant-profile.entities' | translate }} tenant-profile.unlimited
- + tenant-profile.maximum-devices - + tenant-profile.maximum-dashboards
- + tenant-profile.maximum-assets - + tenant-profile.maximum-users
- + tenant-profile.maximum-customers - + tenant-profile.maximum-rule-chains
- + tenant-profile.maximum-edges tenant-profile.unlimited
- + tenant-profile.max-r-e-executions - + tenant-profile.max-transport-messages
- + tenant-profile.max-j-s-executions - + tenant-profile.max-tbel-executions
- + tenant-profile.max-rule-node-executions-per-message - + tenant-profile.max-transport-data-points tenant-profile.unlimited
- + tenant-profile.max-calculated-fields - + tenant-profile.max-data-points-per-rolling-arg
- + tenant-profile.max-arguments-per-cf
- + tenant-profile.max-state-size - + tenant-profile.max-value-argument-size
- + tenant-profile.max-related-level-per-argument - + tenant-profile.min-allowed-scheduled-update-interval
- + tenant-profile.min-allowed-aggregation-interval - + tenant-profile.min-allowed-deduplication-interval
- + tenant-profile.intermediate-aggregation-interval - + tenant-profile.reevaluation-check-interval
- + tenant-profile.relation-search-entity-limit tenant-profile.unlimited
- + tenant-profile.max-d-p-storage-days - + tenant-profile.alarms-ttl-days
- + tenant-profile.default-storage-ttl-days - + tenant-profile.rpc-ttl-days
- + tenant-profile.queue-stats-ttl-days - + tenant-profile.rule-engine-exceptions-ttl-days @if (tenantProfileConfigurationForm.get('smsEnabled').value) { + appearance="outline" subscriptSizing="dynamic"> tenant-profile.max-sms }
- + tenant-profile.max-emails - + tenant-profile.max-created-alarms
- + tenant-profile.alarms-reevaluation-interval
- + tenant-profile.maximum-debug-duration-min tenant-profile.unlimited
- + tenant-profile.maximum-resources-sum-data-size - + tenant-profile.maximum-ota-packages-sum-data-size
- + tenant-profile.maximum-resource-size tenant-profile.unlimited
- + tenant-profile.ws-limit-max-sessions-per-tenant @if (tenantProfileConfigurationForm.get('maxWsSessionsPerTenant').hasError('min')) { @@ -811,7 +811,7 @@ } - + tenant-profile.ws-limit-max-subscriptions-per-tenant @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerTenant').hasError('min')) { @@ -822,7 +822,7 @@
- + tenant-profile.ws-limit-max-sessions-per-customer @if (tenantProfileConfigurationForm.get('maxWsSessionsPerCustomer').hasError('min')) { @@ -831,7 +831,7 @@ } - + tenant-profile.ws-limit-max-subscriptions-per-customer @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerCustomer').hasError('min')) { @@ -849,7 +849,7 @@
- + tenant-profile.ws-limit-max-sessions-per-public-user @if (tenantProfileConfigurationForm.get('maxWsSessionsPerPublicUser').hasError('min')) { @@ -858,7 +858,7 @@ } - + tenant-profile.ws-limit-max-subscriptions-per-public-user @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerPublicUser').hasError('min')) { @@ -869,7 +869,7 @@
- + tenant-profile.ws-limit-max-sessions-per-regular-user @if (tenantProfileConfigurationForm.get('maxWsSessionsPerRegularUser').hasError('min')) { @@ -878,7 +878,7 @@ } - + tenant-profile.ws-limit-max-subscriptions-per-regular-user @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerRegularUser').hasError('min')) { @@ -889,7 +889,7 @@
- + tenant-profile.ws-limit-queue-per-session @if (tenantProfileConfigurationForm.get('wsMsgQueueLimitPerSession').hasError('min')) { diff --git a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html index d24cc15a4b..159dcc6540 100644 --- a/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html @@ -22,7 +22,7 @@
tenant-profile.rate-limits.and-also-less-than
}
- + tenant-profile.rate-limits.number-of-messages @@ -33,7 +33,7 @@ tenant-profile.rate-limits.number-of-messages-min } - + tenant-profile.rate-limits.per-seconds diff --git a/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html b/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html index de7b1de846..94803a8739 100644 --- a/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html +++ b/ui-ngx/src/app/modules/home/components/queue/queue-form.component.html @@ -16,7 +16,7 @@ -->
- + admin.queue-name @if (queueFormGroup.get('name').hasError('required')) { @@ -62,7 +62,7 @@ @if (hideBatchSize) {
- + queue.batch-size @if (queueFormGroup.get('submitStrategy.batchSize').hasError('required')) { @@ -107,7 +107,7 @@
- + queue.retries @@ -123,7 +123,7 @@ } - + queue.failure-percentage @@ -147,7 +147,7 @@ } - + queue.pause-between-retries @@ -163,7 +163,7 @@ } - + queue.max-pause-between-retries @@ -194,7 +194,7 @@
- + queue.poll-interval @if (queueFormGroup.get('pollInterval').hasError('required')) { @@ -209,7 +209,7 @@ } - + queue.partitions @if (queueFormGroup.get('partitions').hasError('required')) { @@ -232,7 +232,7 @@ {{ 'queue.consumer-per-partition' | translate }} - + queue.processing-timeout @if (queueFormGroup.get('packProcessingTimeout').hasError('required')) { @@ -257,14 +257,14 @@ {{ 'queue.duplicate-msg-to-all-partitions' | translate }} - + queue.custom-properties - + queue.custom-properties-hint - + queue.description - + queue.description-hint
diff --git a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html index 896adb0c46..ffbfb48cf3 100644 --- a/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html @@ -34,14 +34,16 @@
+ [required]="true"> {{(direction === entitySearchDirection.FROM ? 'relation.to-entity' : 'relation.from-entity') | translate}}
-
+
{{(direction == directions.FROM ? 'relation.from-relations' : 'relation.to-relations') | translate}} - + relation.direction diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.html index d4213b95e5..a4a57c52e7 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.html @@ -17,7 +17,7 @@ -->
{{ title }}
- + rule-node-config.save-time-series.strategy @for (strategy of processingStrategies; track strategy) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.ts index 22cdec0c82..c569d3270f 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.ts @@ -32,6 +32,7 @@ import { } from '@home/components/rule-node/action/timeseries-config.models'; import { isDefinedAndNotNull } from '@core/utils'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MatFormFieldAppearance } from '@angular/material/form-field'; @Component({ selector: 'tb-advanced-processing-setting-row', @@ -52,6 +53,9 @@ export class AdvancedProcessingSettingRowComponent implements ControlValueAccess @Input() title: string; + @Input() + appearance: MatFormFieldAppearance = 'fill'; + processingSettingRowForm = this.fb.group({ type: [defaultAdvancedProcessingConfig.type], deduplicationIntervalSecs: [{value: 60, disabled: true}] diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting.component.html index 5b403ff611..0136d08250 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting.component.html @@ -24,30 +24,35 @@ @if (timeseries) { } @if (attributes) { } @if (latest) { } @if (webSockets) { } @if (calculatedFields) { } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html index 2ac22c6284..64a23c30e6 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html @@ -17,7 +17,7 @@ -->
- + rule-node-config.customer-name-pattern @if (assignCustomerConfigForm.get('customerNamePattern').hasError('required') || diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html index 50444e3a87..51ac7c34b6 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html @@ -28,7 +28,7 @@
@if(!attributesConfigForm.get('processingSettings.isAdvanced').value) { - + rule-node-config.save-attribute.strategy @for (strategy of processingStrategies; track strategy) { @@ -39,6 +39,7 @@ @if(attributesConfigForm.get('processingSettings.type').value === ProcessingType.DEDUPLICATE) {
- + {{ 'rule-node-config.attributes-scope' | translate }} @@ -74,7 +75,7 @@ } - + {{ 'rule-node-config.attributes-scope-value' | translate }}
- + rule-node-config.alarm-type @if (clearAlarmConfigForm.get('alarmType').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html index eba427c385..eea361f6c8 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html @@ -77,7 +77,7 @@ } @if (createAlarmConfigForm.get('useMessageAlarmData').value === false) {
- + rule-node-config.alarm-type @if (createAlarmConfigForm.get('alarmType').hasError('required')) { @@ -87,11 +87,11 @@ } rule-node-config.general-pattern-hint - + {{ 'rule-node-config.use-alarm-severity-pattern' | translate }} @if (!createAlarmConfigForm.get('dynamicSeverity').value) { - + rule-node-config.alarm-severity @for (severity of alarmSeverities; track severity) { @@ -108,7 +108,7 @@ } @if (createAlarmConfigForm.get('dynamicSeverity').value) { - + rule-node-config.alarm-severity-pattern @if (createAlarmConfigForm.get('severity').hasError('required')) { @@ -124,7 +124,7 @@ @if (createAlarmConfigForm.get('propagate').value === true) {
- + rule-node-config.relation-types-list @for (key of createAlarmConfigForm.get('relationTypes').value; track key) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html index 817ff84b53..a50b62b26d 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html @@ -19,7 +19,7 @@
rule-node-config.relation-parameters
- + relation.direction @for (type of directionTypes; track type) { @@ -30,6 +30,7 @@ @@ -40,6 +41,7 @@
rule-node-config.target-entity
{{ entityTypeNamePatternTranslation.get(createRelationConfigForm.get('entityType').value) | translate }} @@ -59,6 +62,7 @@ @if (createRelationConfigForm.get('entityType').value === entityType.DEVICE || createRelationConfigForm.get('entityType').value === entityType.ASSET) { rule-node-config.profile-name diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html index cb0b89d1ed..a260f16b96 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html @@ -20,7 +20,7 @@
- + {{ 'rule-node-config.attributes-scope' | translate }} @@ -31,7 +31,7 @@ } - + {{ 'rule-node-config.attributes-scope-value' | translate }}
- + {{ 'rule-node-config.attributes-keys' | translate }} @for (key of deleteAttributesConfigForm.get('keys').value; track key) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html index 7899e4773d..5c1517804d 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html @@ -19,7 +19,7 @@
rule-node-config.relation-parameters
- + relation.direction @for (type of directionTypes; track type) { @@ -30,6 +30,7 @@ @@ -49,11 +50,13 @@ class="flex-1" showLabel required + appearance="outline" [allowedEntityTypes]="allowedEntityTypes" formControlName="entityType"> @if (deleteRelationConfigForm.get('entityType').value && deleteRelationConfigForm.get('entityType').value !== entityType.TENANT) { {{ entityTypeNamePatternTranslation.get(deleteRelationConfigForm.get('entityType').value) | translate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html index cb3d6d2b7e..bdb412f385 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html @@ -16,7 +16,7 @@ -->
- + {{ 'rule-node-config.select-device-connectivity-event' | translate }} @for (eventOption of eventOptions; track eventOption) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html index cea850d0be..d0d91e0bb1 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html @@ -19,7 +19,7 @@
rule-node-config.generation-parameters
- + rule-node-config.message-count @if (generatorConfigForm.get('msgCount').hasError('required')) { @@ -33,7 +33,7 @@ } - + rule-node-config.generation-frequency-seconds @if (generatorConfigForm.get('periodInSeconds').hasError('required')) { @@ -53,6 +53,7 @@
rule-node-config.originator
rule-node-config.coordinate-field-names
- + {{ 'rule-node-config.latitude-field-name' | translate }} @if (geoActionConfigForm.get('latitudeKeyName').hasError('required')) { @@ -29,7 +29,7 @@ } - + {{ 'rule-node-config.longitude-field-name' | translate }} @if (geoActionConfigForm.get('longitudeKeyName').hasError('required')) { @@ -45,7 +45,7 @@
rule-node-config.geofence-configuration
- + {{ 'rule-node-config.perimeter-type' | translate }} @for (type of perimeterTypes; track type) { @@ -70,7 +70,7 @@
@if (geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { - + {{ 'rule-node-config.perimeter-key-name' | translate }} @if (geoActionConfigForm.get('perimeterKeyName').hasError('required')) { @@ -83,8 +83,7 @@ } @if (geoActionConfigForm.get('perimeterType').value === perimeterType.CIRCLE && !geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { -
+
{{ 'rule-node-config.circle-center-latitude' | translate }} @@ -95,7 +94,7 @@ } - + {{ 'rule-node-config.circle-center-longitude' | translate }} @if (geoActionConfigForm.get('centerLongitude').hasError('required')) { @@ -106,7 +105,7 @@
- + {{ 'rule-node-config.range' | translate }} @if (geoActionConfigForm.get('range').hasError('required')) { @@ -115,7 +114,7 @@ } - + {{ 'rule-node-config.range-units' | translate }} @for (type of rangeUnits; track type) { @@ -136,7 +135,7 @@ @if (geoActionConfigForm.get('perimeterType').value === perimeterType.POLYGON && !geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) {
- + rule-node-config.polygon-definition
- + rule-node-config.min-inside-duration @if (geoActionConfigForm.get('minInsideDuration').hasError('required')) { @@ -193,7 +192,7 @@ } - + rule-node-config.min-inside-duration-time-unit @for (timeUnit of timeUnits; track timeUnit) { @@ -205,7 +204,7 @@
- + rule-node-config.min-outside-duration @if (geoActionConfigForm.get('minOutsideDuration').hasError('required')) { @@ -224,7 +223,7 @@ } - + rule-node-config.min-outside-duration-time-unit @for (timeUnit of timeUnits; track timeUnit) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html index bd5fad7679..302b3828ba 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html @@ -19,6 +19,7 @@
@@ -31,7 +32,7 @@
{{'rule-node-config.custom-expression-field-input' | translate }} * - +
rule-node-config.result-title
- + rule-node-config.type-field-input @@ -75,7 +76,7 @@
@if (mathFunctionConfigForm.get('result').get('type').value === ArgumentTypeResult.ATTRIBUTE) { - + rule-node-config.attribute-scope-field-input @for (scope of attributeScopeResult; track scope) { @@ -86,7 +87,7 @@ } - + rule-node-config.key-field-input
- + rule-node-config.number-floating-point-field-input
@if ([ArgumentTypeResult.ATTRIBUTE, ArgumentTypeResult.TIME_SERIES].includes(mathFunctionConfigForm.get('result').get('type').value)) { -
+
{{'rule-node-config.add-to-message-field-input' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html index 31010602c3..305ce7615b 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.interval-seconds @if (msgCountConfigForm.get('interval').hasError('required')) { @@ -30,7 +30,7 @@ } - + rule-node-config.output-timeseries-key-prefix @if (msgCountConfigForm.get('telemetryPrefix').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html index a7dc7b7b1b..1b23da7dea 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html @@ -22,6 +22,7 @@
rule-node-config.use-metadata-period-in-seconds-patterns-hint
@if (msgDelayConfigForm.get('useMetadataPeriodInSecondsPatterns').value !== true) { rule-node-config.period-seconds @@ -37,7 +38,7 @@ } } @else { - + rule-node-config.period-in-seconds-pattern @if (msgDelayConfigForm.get('periodInSecondsPattern').hasError('required')) { @@ -48,7 +49,7 @@ rule-node-config.general-pattern-hint } - + rule-node-config.max-pending-messages @if (msgDelayConfigForm.get('maxPendingMsgs').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html index 5a39dae562..f4e312fe00 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html @@ -20,7 +20,7 @@
- + {{ 'rule-node-config.attributes-scope' | translate }} @@ -31,7 +31,7 @@ } - + {{ 'rule-node-config.attributes-scope-value' | translate }}
@if (unassignCustomerConfigForm.get('unassignFromCustomer').value) { - + rule-node-config.customer-name-pattern @if (unassignCustomerConfigForm.get('customerNamePattern').hasError('required') || diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html index dd5579b230..664bc64aad 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html @@ -24,7 +24,7 @@ - + rule-node-config.credentials-type @for (credentialsType of allCredentialsTypes; track credentialsType) { @@ -44,7 +44,7 @@
@switch (credentialsConfigFormGroup.get('type').value) { @case("basic") { - + rule-node-config.username @if (credentialsConfigFormGroup.get('username').hasError('required')) { @@ -53,7 +53,7 @@ } - + rule-node-config.password diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html index 380668f9fd..6a25b5e6cb 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html @@ -17,7 +17,7 @@ -->
- + relation.direction @for (type of directionTypes; track type) { @@ -27,7 +27,7 @@ } - + rule-node-config.max-relation-level } - + rule-node-config.functions-field-input - + @if (label) { {{ label }} } diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html index 138d45700b..035a739918 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html @@ -16,7 +16,7 @@ -->
- + {{'rule-node-config.output-message-type' | translate}} @for (msgType of messageTypes; track msgType) { @@ -26,7 +26,7 @@ } - + {{'rule-node-config.message-type-value' | translate}} @if (messageTypeFormGroup.get('messageType').value) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts index 5cb9922fcb..2ded7151c8 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts @@ -24,7 +24,7 @@ import { Validator, Validators } from '@angular/forms'; -import { SubscriptSizing } from '@angular/material/form-field'; +import { MatFormFieldAppearance, SubscriptSizing } from '@angular/material/form-field'; import { coerceBoolean } from '@shared/public-api'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; @@ -61,6 +61,9 @@ export class OutputMessageTypeAutocompleteComponent implements ControlValueAcces @coerceBoolean() disabled: boolean; + @Input() + appearance: MatFormFieldAppearance = 'fill' + @Input() @coerceBoolean() set required(value) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html index 5f8754c723..01ec5c5e9a 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html @@ -19,7 +19,7 @@
rule-node-config.relations-query
- + relation.direction @for (type of directionTypes; track type) { @@ -29,7 +29,7 @@ } - + rule-node-config.max-relation-level diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html index dcc5fee728..6011ea79c5 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html @@ -17,7 +17,7 @@ -->
- + {{ 'rule-node-config.input-value-key' | translate }} @if (calculateDeltaConfigForm.get('inputValueKey').hasError('required') || @@ -27,7 +27,7 @@ } - + {{ 'rule-node-config.output-value-key' | translate }} @if (calculateDeltaConfigForm.get('outputValueKey').hasError('required') || @@ -38,7 +38,7 @@ }
- + {{ 'rule-node-config.number-of-digits-after-floating-point' | translate }} @if (calculateDeltaConfigForm.get('round').hasError('min')) { @@ -80,7 +80,7 @@
@if (calculateDeltaConfigForm.get('addPeriodBetweenMsgs').value) { - + {{ 'rule-node-config.period-value-key' | translate }} @if (calculateDeltaConfigForm.get('periodValueKey').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html index ff7939cab4..50af390ff8 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html @@ -18,6 +18,7 @@
@@ -40,10 +41,9 @@
@if (getTelemetryFromDatabaseConfigForm.get('useMetadataIntervalPatterns').value === false) { -
+
- + {{ 'rule-node-config.interval-start' | translate }} @if (getTelemetryFromDatabaseConfigForm.get('interval.startInterval').hasError('required')) { @@ -62,7 +62,7 @@ } - + {{ 'rule-node-config.time-unit' | translate }} @for (timeUnit of timeUnits; track timeUnit) { @@ -74,7 +74,7 @@
- + {{ 'rule-node-config.interval-end' | translate }} @if (getTelemetryFromDatabaseConfigForm.get('interval.endInterval').hasError('required')) { @@ -93,7 +93,7 @@ } - + {{ 'rule-node-config.time-unit' | translate }} @for (timeUnit of timeUnits; track timeUnit) { @@ -123,7 +123,7 @@
} @else {
- + {{ 'rule-node-config.start-interval' | translate }} @if (getTelemetryFromDatabaseConfigForm.get('startIntervalPattern').hasError('required') || @@ -133,7 +133,7 @@ } - + {{ 'rule-node-config.end-interval' | translate }} @if (getTelemetryFromDatabaseConfigForm.get('endIntervalPattern').hasError('required') || @@ -167,7 +167,7 @@
@if (getTelemetryFromDatabaseConfigForm.get('fetchMode').value === fetchMode.ALL) {
- {{ 'aggregation.function' | translate }} @@ -180,7 +180,7 @@ @if (getTelemetryFromDatabaseConfigForm.get('aggregation').value === aggregationTypes.NONE) {
- + {{ "rule-node-config.order-by-timestamp" | translate }} @for (order of samplingOrders; track order) { @@ -190,7 +190,7 @@ } - + {{ "rule-node-config.limit" | translate }} {{ "rule-node-config.limit-hint" | translate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html index 7167b68132..9b0a51d7ec 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html @@ -82,6 +82,7 @@
- + rule-node-config.topic @if (azureIotHubConfigForm.get('topicPattern').hasError('required')) { @@ -26,7 +26,7 @@ } rule-node-config.general-pattern-hint - + rule-node-config.hostname @if (azureIotHubConfigForm.get('host').hasError('required')) { @@ -35,7 +35,7 @@ } - + rule-node-config.device-id @if (azureIotHubConfigForm.get('clientId').hasError('required')) { @@ -44,7 +44,7 @@ } - @@ -56,7 +56,7 @@
- + rule-node-config.credentials-type @for (credentialsType of allAzureIotHubCredentialsTypes; track credentialsType) { @@ -75,7 +75,7 @@ @switch (azureIotHubConfigForm.get('credentials.type').value) { @case ('anonymous') {} @case ('sas') { - + rule-node-config.sas-key @@ -124,7 +124,7 @@ noFileText="rule-node-config.no-file" dropLabel="{{'rule-node-config.drop-file' | translate}}"> - + rule-node-config.private-key-password diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html index ffbc99fe57..29e143a2f0 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.topic-pattern @if (kafkaConfigForm.get('topicPattern').hasError('required')) { @@ -26,13 +26,13 @@ } rule-node-config.general-pattern-hint - + rule-node-config.key-pattern rule-node-config.general-pattern-hint
rule-node-config.key-pattern-hint
- + rule-node-config.bootstrap-servers @if (kafkaConfigForm.get('bootstrapServers').hasError('required')) { @@ -41,7 +41,7 @@ } - + rule-node-config.retries @if (kafkaConfigForm.get('retries').hasError('min')) { @@ -50,7 +50,7 @@ } - + rule-node-config.batch-size-bytes @if (kafkaConfigForm.get('batchSize').hasError('min')) { @@ -59,7 +59,7 @@ } - + rule-node-config.linger-ms @if (kafkaConfigForm.get('linger').hasError('min')) { @@ -68,7 +68,7 @@ } - + rule-node-config.buffer-memory-bytes @if (kafkaConfigForm.get('bufferMemory').hasError('min')) { @@ -77,7 +77,7 @@ } - + rule-node-config.acks @for (ackValue of ackValues; track ackValue) { @@ -101,7 +101,7 @@
rule-node-config.add-metadata-key-values-as-kafka-headers-hint
@if (kafkaConfigForm.get('addMetadataKeyValuesAsKafkaHeaders').value) { - + rule-node-config.charset-encoding @for (charset of ToByteStandartCharsetTypesValues; track charset) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html index fba6b87d72..4e80aad77b 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html @@ -24,7 +24,7 @@ popupHelpLink="rulenode/node-templatization-doc">
- + {{'rule-node-config.function-name' | translate}} @if (lambdaConfigForm.get('functionName').hasError('required')) { @@ -33,7 +33,7 @@ } - + {{'rule-node-config.qualifier' | translate}} rule-node-config.qualifier-hint @@ -47,7 +47,7 @@ rule-node-config.aws-credentials
- + rule-node-config.aws-access-key-id @if (lambdaConfigForm.get('accessKey').hasError('required')) { @@ -56,7 +56,7 @@ } - + rule-node-config.aws-secret-access-key @if (lambdaConfigForm.get('secretKey').hasError('required')) { @@ -65,7 +65,7 @@ } - + rule-node-config.aws-region @if (lambdaConfigForm.get('region').hasError('required')) { @@ -84,7 +84,7 @@
- + rule-node-config.connection-timeout @if (lambdaConfigForm.get('connectionTimeout').hasError('required')) { @@ -101,7 +101,7 @@ color="primary" matTooltip="{{ 'rule-node-config.connection-timeout-hint' | translate }}">help - + rule-node-config.request-timeout @if (lambdaConfigForm.get('requestTimeout').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html index e756cdb961..127e67db0d 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/mqtt-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.topic-pattern @if (mqttConfigForm.get('topicPattern').hasError('required')) { @@ -27,7 +27,7 @@ rule-node-config.general-pattern-hint
- + rule-node-config.host @if (mqttConfigForm.get('host').hasError('required')) { @@ -36,7 +36,7 @@ } - + rule-node-config.port @if (mqttConfigForm.get('port').hasError('required')) { @@ -55,7 +55,7 @@ } - + rule-node-config.connect-timeout @if (mqttConfigForm.get('connectTimeoutSec').hasError('required')) { @@ -75,7 +75,7 @@ }
- + rule-node-config.client-id {{'rule-node-config.client-id-hint' | translate}} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.html index ce937fe7d7..8101d2b2a6 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/notification-config.component.html @@ -19,12 +19,14 @@
- + rule-node-config.gcp-project-id @if (pubSubConfigForm.get('projectId').hasError('required')) { @@ -25,7 +25,7 @@ } - + rule-node-config.pubsub-topic-name @if (pubSubConfigForm.get('topicName').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.html index 19f06ef77d..eb73a662a3 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rabbit-mq-config.component.html @@ -16,15 +16,15 @@ -->
- + rule-node-config.exchange-name-pattern - + rule-node-config.routing-key-pattern - + rule-node-config.message-properties @for (property of messageProperties; track property) { @@ -35,7 +35,7 @@
- + rule-node-config.host @if (rabbitMqConfigForm.get('host').hasError('required')) { @@ -44,7 +44,7 @@ } - + rule-node-config.port @if (rabbitMqConfigForm.get('port').hasError('required')) { @@ -64,23 +64,23 @@ }
- + rule-node-config.virtual-host - + rule-node-config.username - + rule-node-config.password - + {{ 'rule-node-config.automatic-recovery' | translate }} - + rule-node-config.connection-timeout-ms @if (rabbitMqConfigForm.get('connectionTimeout').hasError('min')) { @@ -89,7 +89,7 @@ } - + rule-node-config.handshake-timeout-ms @if (rabbitMqConfigForm.get('handshakeTimeout').hasError('min')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html index 3b2a6a1cb6..7fc23ce3e2 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/rest-api-call-config.component.html @@ -18,7 +18,7 @@
rule-node-config.request-url
- + rule-node-config.url @if (restApiCallConfigForm.get('restEndpointUrlPattern').hasError('required')) { @@ -45,7 +45,7 @@
- + rule-node-config.request-method @for (requestType of httpRequestTypes; track requestType) { @@ -92,7 +92,7 @@ @if (!restApiCallConfigForm.get('useSystemProxyProperties').value) {
- + rule-node-config.proxy-host @if (restApiCallConfigForm.get('proxyHost').hasError('required')) { @@ -101,7 +101,7 @@ } - + rule-node-config.proxy-port @if (restApiCallConfigForm.get('proxyPort').hasError('required')) { @@ -116,11 +116,11 @@ }
- + rule-node-config.proxy-user - + rule-node-config.proxy-password @@ -142,7 +142,7 @@ {{ 'rule-node-config.ignore-request-body' | translate }}
- + rule-node-config.read-timeout rule-node-config.read-timeout-hint @@ -152,7 +152,7 @@ } - + rule-node-config.max-parallel-requests-count rule-node-config.max-parallel-requests-count-hint @@ -162,7 +162,7 @@ } - + rule-node-config.max-response-size rule-node-config.max-response-size-hint diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.html index 295de6912a..9f5f9a590f 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/send-email-config.component.html @@ -21,7 +21,7 @@ @if (sendEmailConfigForm.get('useSystemSmtpSettings').value === false) {
- + rule-node-config.smtp-protocol @for (smtpProtocol of smtpProtocols; track smtpProtocol) { @@ -32,7 +32,7 @@
- + rule-node-config.smtp-host @if (sendEmailConfigForm.get('smtpHost').hasError('required')) { @@ -41,7 +41,7 @@ } - + rule-node-config.smtp-port @if (sendEmailConfigForm.get('smtpPort').hasError('required')) { @@ -61,7 +61,7 @@ }
- + rule-node-config.timeout-msec @if (sendEmailConfigForm.get('timeout').hasError('required')) { @@ -79,7 +79,7 @@ {{ 'rule-node-config.enable-tls' | translate }} @if (sendEmailConfigForm.get('enableTls').value === true) { - + rule-node-config.tls-version @for (tlsVersion of tlsVersions; track tlsVersion) { @@ -96,7 +96,7 @@ @if (sendEmailConfigForm.get('enableProxy').value) {
- + rule-node-config.proxy-host @if (sendEmailConfigForm.get('proxyHost').hasError('required')) { @@ -105,7 +105,7 @@ } - + rule-node-config.proxy-port @if (sendEmailConfigForm.get('proxyPort').hasError('required')) { @@ -120,21 +120,21 @@ }
- + rule-node-config.proxy-user - + rule-node-config.proxy-password
} - + rule-node-config.username - + rule-node-config.password diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.html index ff9a77a3f5..d9136b3cb6 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/send-sms-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.numbers-to-template @if (sendSmsConfigForm.get('numbersToTemplate').hasError('required')) { @@ -26,9 +26,9 @@ } - + rule-node-config.sms-message-template - + @if (sendSmsConfigForm.get('smsMessageTemplate').hasError('required')) { {{ 'rule-node-config.sms-message-template-required' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.html index 9ef122ad9e..31f91ba86a 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/slack-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.message-template @if (slackConfigForm.get('messageTemplate').hasError('required')) { @@ -30,7 +30,7 @@ {{ 'rule-node-config.use-system-slack-settings' | translate }} @if (!slackConfigForm.get('useSystemSettings').value) { - + rule-node-config.slack-api-token @if (slackConfigForm.get('botToken').hasError('required')) { @@ -49,6 +49,7 @@ }
- + rule-node-config.topic-arn-pattern @if (snsConfigForm.get('topicArnPattern').hasError('required')) { @@ -26,7 +26,7 @@ } rule-node-config.general-pattern-hint - + rule-node-config.aws-access-key-id @if (snsConfigForm.get('accessKeyId').hasError('required')) { @@ -35,7 +35,7 @@ } - + rule-node-config.aws-secret-access-key @if (snsConfigForm.get('secretAccessKey').hasError('required')) { @@ -44,7 +44,7 @@ } - + rule-node-config.aws-region @if (snsConfigForm.get('region').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.html index 0e07578f19..98ca8b93d8 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/external/sqs-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.queue-type @for (type of sqsQueueTypes; track type) { @@ -26,7 +26,7 @@ } - + rule-node-config.queue-url-pattern @if (sqsConfigForm.get('queueUrlPattern').hasError('required')) { @@ -37,7 +37,7 @@ rule-node-config.general-pattern-hint @if (sqsConfigForm.get('queueType').value === sqsQueueType.STANDARD) { - + rule-node-config.delay-seconds @if (sqsConfigForm.get('delaySeconds').hasError('min')) { @@ -62,7 +62,7 @@ valText="rule-node-config.value" valRequiredText="rule-node-config.value-required"> - + rule-node-config.aws-access-key-id @if (sqsConfigForm.get('accessKeyId').hasError('required')) { @@ -71,7 +71,7 @@ } - + rule-node-config.aws-secret-access-key @if (sqsConfigForm.get('secretAccessKey').hasError('required')) { @@ -80,7 +80,7 @@ } - + rule-node-config.aws-region @if (sqsConfigForm.get('region').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.html index c4cd8f18ed..ae5a957bf6 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-message-config.component.html @@ -23,6 +23,7 @@
@@ -30,6 +31,7 @@ matTooltip="{{ 'rule-node-config.chip-help' | translate: { inputName: 'rule-node-config.field-name' | translate } }}">help diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.html index 3e2773037d..6aee9bf101 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/check-relation-config.component.html @@ -18,7 +18,7 @@
rule-node-config.relation-search-parameters
- + {{ 'relation.direction' | translate }} @for (direction of entitySearchDirection; track direction) { @@ -29,6 +29,7 @@ @@ -41,6 +42,7 @@ @if (checkRelationConfigForm.get('checkForSingleEntity').value) {
@if (checkRelationConfigForm.get('entityType').value) { rule-node-config.coordinate-field-names
- + {{ 'rule-node-config.latitude-field-name' | translate }} @if (geoFilterConfigForm.get('latitudeKeyName').hasError('required')) { @@ -29,7 +29,7 @@ } - + {{ 'rule-node-config.longitude-field-name' | translate }} @if (geoFilterConfigForm.get('longitudeKeyName').hasError('required')) { @@ -45,7 +45,7 @@
rule-node-config.geofence-configuration
- + {{ 'rule-node-config.perimeter-type' | translate }} @for (type of perimeterTypes; track type) { @@ -70,7 +70,7 @@
@if (geoFilterConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { - + {{ 'rule-node-config.perimeter-key-name' | translate }} @if (geoFilterConfigForm.get('perimeterKeyName').hasError('required')) { @@ -83,10 +83,9 @@ } @if (geoFilterConfigForm.get('perimeterType').value === perimeterType.CIRCLE && !geoFilterConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { -
+
- + {{ 'rule-node-config.circle-center-latitude' | translate }} @if (geoFilterConfigForm.get('centerLatitude').hasError('required')) { @@ -95,7 +94,7 @@ } - + {{ 'rule-node-config.circle-center-longitude' | translate }} @if (geoFilterConfigForm.get('centerLongitude').hasError('required')) { @@ -106,7 +105,7 @@
- + {{ 'rule-node-config.range' | translate }} @if (geoFilterConfigForm.get('range').hasError('required')) { @@ -115,7 +114,7 @@ } - + {{ 'rule-node-config.range-units' | translate }} @for (type of rangeUnits; track type) { @@ -135,8 +134,7 @@ } @if (geoFilterConfigForm.get('perimeterType').value === perimeterType.POLYGON && !geoFilterConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { - + {{ 'rule-node-config.polygon-definition' | translate }} {{ 'rule-node-config.polygon-definition-hint' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.html index a0d90bab4a..b94519d914 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/filter/originator-type-config.component.html @@ -17,6 +17,7 @@ -->
diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.html index 22d58fb9fc..efcea2eb7b 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/change-originator-config.component.html @@ -16,7 +16,7 @@ -->
- + rule-node-config.new-originator @@ -40,8 +40,7 @@ @if (changeOriginatorConfigForm.get('originatorSource').value === originatorSource.ENTITY) { -
+
@@ -49,11 +48,12 @@ - + rule-node-config.entity-name-pattern @if (changeOriginatorConfigForm.get('entityNamePattern').hasError('required') || diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.html index 0fc80e6197..a899a344b3 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/copy-keys-config.component.html @@ -23,6 +23,7 @@
- + {{'rule-node-config.interval' | translate}} @if (deduplicationConfigForm.get('interval').hasError('required')) { @@ -44,26 +44,24 @@ } @if (deduplicationConfigForm.get('strategy').value === 'ALL') { - + } @if (deduplicationConfigForm.get('strategy').value === 'FIRST') { + textAlign="'center'"> } @if (deduplicationConfigForm.get('strategy').value === 'LAST') { + textAlign="'center'"> } @if (deduplicationConfigForm.get('strategy').value === deduplicationStrategie.ALL) {
@@ -75,7 +73,7 @@ rule-node-config.advanced-settings
- + {{'rule-node-config.max-pending-msgs' | translate}} @if (deduplicationConfigForm.get('maxPendingMsgs').hasError('required')) { @@ -97,7 +95,7 @@ color="primary" matTooltip="{{ 'rule-node-config.max-pending-msgs-hint' | translate }}">help - + {{'rule-node-config.max-retries' | translate}} @if (deduplicationConfigForm.get('maxRetries').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.html index c5d94739d6..4da3fdf45c 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/delete-keys-config.component.html @@ -22,6 +22,7 @@
- + {{ 'rule-node-config.json-path-expression' | translate }} {{ 'rule-node-config.json-path-expression-hint' | translate }} diff --git a/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.html index f2d8e253ec..118faf6657 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/transformation/to-email-config.component.html @@ -19,7 +19,7 @@
rule-node-config.email-sender
- + rule-node-config.from-template @@ -52,7 +52,7 @@
- + rule-node-config.to-template - + rule-node-config.bcc-template - @if (entity?.id) {
} + + + tenant.description + +
-
diff --git a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html index 84744f6217..685f0f3336 100644 --- a/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html +++ b/ui-ngx/src/app/modules/home/pages/widget/widget-editor.component.html @@ -236,16 +236,9 @@ [(ngModel)]="widget.image" (ngModelChange)="isDirty = true" > - - widget.description - - {{descriptionInput.value?.length || 0}}/1024 - @@ -261,20 +254,20 @@ {{ 'widget.deprecated' | translate }}
- + widget.settings-form-selector - + widget.data-key-settings-form-selector @if (widget.type === widgetTypes.timeseries) { - + widget.latest-data-key-settings-form-selector @if (widget.hasBasicMode) { - + widget.basic-mode-form-selector } + + widget.description + + {{descriptionInput.value?.length || 0}}/1024 +
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 16ef833ec3..98faaac2f2 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 @@ -34,9 +34,10 @@
-
+
diff --git a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.html b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.html index 6a94f1fc04..fb7eac1806 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.html +++ b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.html @@ -27,6 +27,7 @@ [allowedEntityTypes]="allowedEntityTypes" [filterAllowedEntityTypes]="filterAllowedEntityTypes" [additionEntityTypes]="additionEntityTypes" + [appearance]="appearance" formControlName="entityType"> } @@ -38,6 +39,7 @@ [required]="required" [entityType]="modelValue.entityType" [useEntityDisplayName]="useEntityDisplayName" + [appearance]="appearance" formControlName="entityIds"> } diff --git a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts index 7ceaee14d7..1bf532af2e 100644 --- a/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts +++ b/ui-ngx/src/app/shared/components/entity/entity-list-select.component.ts @@ -21,6 +21,7 @@ import { EntityService } from '@core/http/entity.service'; import { EntityId } from '@shared/models/id/entity-id'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { isDefinedAndNotNull } from '@core/utils'; +import { MatFormFieldAppearance } from '@angular/material/form-field'; interface EntityListSelectModel { entityType: EntityType | AliasEntityType; @@ -72,6 +73,9 @@ export class EntityListSelectComponent implements ControlValueAccessor, OnInit { @Input({transform: booleanAttribute}) useEntityDisplayName = false; + @Input() + appearance: MatFormFieldAppearance = 'fill'; + displayEntityTypeSelect: boolean; private defaultEntityType: EntityType | AliasEntityType = null; diff --git a/ui-ngx/src/app/shared/components/image/image-dialog.component.html b/ui-ngx/src/app/shared/components/image/image-dialog.component.html index ee830aa102..0fe1a4be59 100644 --- a/ui-ngx/src/app/shared/components/image/image-dialog.component.html +++ b/ui-ngx/src/app/shared/components/image/image-dialog.component.html @@ -33,7 +33,7 @@ }
- + image.name @if (imageFormGroup.get('title').hasError('required')) { diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.html b/ui-ngx/src/app/shared/components/material-icon-select.component.html index dc22921c7e..55e76946d6 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.html +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.html @@ -18,7 +18,7 @@ @if (!asBoxInput) {
{{materialIconFormGroup.get('icon').value}} - + {{ label }} @if (iconClearButton) { diff --git a/ui-ngx/src/app/shared/components/material-icon-select.component.ts b/ui-ngx/src/app/shared/components/material-icon-select.component.ts index a0f5387a24..ec040bd488 100644 --- a/ui-ngx/src/app/shared/components/material-icon-select.component.ts +++ b/ui-ngx/src/app/shared/components/material-icon-select.component.ts @@ -36,6 +36,7 @@ import { TbPopoverService } from '@shared/components/popover.service'; import { MaterialIconsComponent } from '@shared/components/material-icons.component'; import { MatButton } from '@angular/material/button'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; +import { MatFormFieldAppearance } from '@angular/material/form-field'; @Component({ selector: 'tb-material-icon-select', @@ -76,6 +77,9 @@ export class MaterialIconSelectComponent extends PageComponent implements OnInit @coerceBoolean() allowedCustomIcon = false; + @Input() + appearance: MatFormFieldAppearance = 'fill'; + private requiredValue: boolean; get required(): boolean { return this.requiredValue; diff --git a/ui-ngx/src/app/shared/components/message-type-autocomplete.component.html b/ui-ngx/src/app/shared/components/message-type-autocomplete.component.html index fb82ed8032..c63cbf82fb 100644 --- a/ui-ngx/src/app/shared/components/message-type-autocomplete.component.html +++ b/ui-ngx/src/app/shared/components/message-type-autocomplete.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + {{ 'rulenode.message-type' | translate }} - settings_ethernet From 1cedb66138e344c2d39aad16cd20803809f50016 Mon Sep 17 00:00:00 2001 From: Maksym Tsymbarov Date: Thu, 2 Apr 2026 13:17:05 +0200 Subject: [PATCH 06/24] Additional inputs reworks --- .../dashboard-state-dialog.component.html | 4 +- ...snmp-device-profile-mapping.component.html | 9 ++-- .../common/kv-map-config-old.component.html | 4 +- ...command-button-basic-config.component.html | 2 +- .../power-button-basic-config.component.html | 2 +- .../toggle-button-basic-config.component.html | 2 +- .../status-widget-basic-config.component.html | 2 +- .../single-switch-basic-config.component.html | 2 +- .../rpc/slider-basic-config.component.html | 2 +- .../value-stepper-basic-config.component.html | 2 +- .../scada-symbol-basic-config.component.html | 2 +- .../config/target-device.component.html | 2 + .../widget/config/target-device.component.ts | 4 ++ .../date-range-navigator-panel.component.html | 2 +- .../date-range-navigator.component.html | 6 +-- .../lib/home-page/doc-link.component.html | 6 +-- .../lib/home-page/quick-link.component.html | 2 +- .../rpc/persistent-add-dialog.component.html | 6 +-- .../persistent-details-dialog.component.html | 13 +++--- .../persistent-filter-panel.component.html | 2 +- ...board-state-widget-settings.component.html | 5 ++- .../cards/label-widget-label.component.html | 9 ++-- .../qrcode-widget-settings.component.html | 2 +- .../common/widget-font.component.html | 12 +++--- .../device-key-autocomplete.component.html | 2 +- .../device-key-autocomplete.component.ts | 4 ++ ...d-indicator-widget-settings.component.html | 12 +++--- ...stent-table-widget-settings.component.html | 4 +- ...ound-switch-widget-settings.component.html | 2 +- .../control/rpc-button-style.component.html | 2 + .../rpc-shell-widget-settings.component.html | 2 +- ...pc-terminal-widget-settings.component.html | 4 +- .../send-rpc-widget-settings.component.html | 10 ++--- ...lide-toggle-widget-settings.component.html | 4 +- ...tch-control-widget-settings.component.html | 2 +- .../switch-rpc-settings.component.html | 11 ++--- ...e-attribute-widget-settings.component.html | 9 ++-- ...e-navigator-widget-settings.component.html | 6 +-- ...eway-config-widget-settings.component.html | 13 +++--- ...eway-events-widget-settings.component.html | 3 +- ...pio-control-widget-settings.component.html | 7 ++-- .../settings/gpio/gpio-item.component.html | 9 ++-- ...quick-links-widget-settings.component.html | 2 +- .../datakey-select-option.component.html | 4 +- ...ce-claiming-widget-settings.component.html | 18 ++++---- ...e-attribute-widget-settings.component.html | 5 ++- ...n-attribute-widget-settings.component.html | 17 ++++---- ...ple-attributes-key-settings.component.html | 41 ++++++++++--------- ...-attributes-widget-settings.component.html | 19 +++++---- ...gation-card-widget-settings.component.html | 5 ++- ...ation-cards-widget-settings.component.html | 2 +- .../widget/widget-config.component.html | 2 +- .../admin/send-test-sms-dialog.component.html | 3 +- .../device-profile-tabs.component.html | 2 +- ...ice-transport-configuration.component.html | 24 +++++------ ...ate-nested-rulechain-dialog.component.html | 4 +- .../rulechain/link-labels.component.html | 2 +- .../email-auth-dialog.component.html | 4 +- .../sms-auth-dialog.component.html | 3 +- .../totp-auth-dialog.component.html | 2 +- .../save-widget-type-as-dialog.component.html | 3 +- .../widget/widgets-bundle.component.html | 14 +++---- ...force-two-factor-auth-login.component.html | 1 + .../components/color-input.component.html | 2 +- .../components/color-input.component.ts | 6 ++- .../image/upload-image-dialog.component.html | 2 +- .../widgets-bundle-select.component.html | 2 +- .../widgets-bundle-select.component.ts | 4 ++ 68 files changed, 222 insertions(+), 181 deletions(-) diff --git a/ui-ngx/src/app/modules/home/components/dashboard-page/states/dashboard-state-dialog.component.html b/ui-ngx/src/app/modules/home/components/dashboard-page/states/dashboard-state-dialog.component.html index ad0aeb097c..58cb21253d 100644 --- a/ui-ngx/src/app/modules/home/components/dashboard-page/states/dashboard-state-dialog.component.html +++ b/ui-ngx/src/app/modules/home/components/dashboard-page/states/dashboard-state-dialog.component.html @@ -31,7 +31,7 @@ }
- + dashboard.state-name @if (stateFormGroup.get('name').hasError('required')) { @@ -40,7 +40,7 @@ } - + dashboard.state-id @if (stateFormGroup.get('id').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html index acd00e3707..2f0ef4d0af 100644 --- a/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html +++ b/ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html @@ -26,10 +26,9 @@
@for (mappingConfig of mappingsConfigFormArray.controls; track mappingConfig; let isLast = $last) { -
+
- + @for (dataType of dataTypes; track dataType) { @@ -43,7 +42,7 @@ } - + @if (mappingConfig.get('key').hasError('required')) { @@ -51,7 +50,7 @@ } - + @if (mappingConfig.get('oid').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html index 3e75dc69ec..831630074a 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html @@ -31,7 +31,7 @@
- + @if (keyValControl.get('key').hasError('required')) { @@ -40,7 +40,7 @@ } - + @if (keyValControl.get('value').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/command-button-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/command-button-basic-config.component.html index 34ce199e65..e39d57b64e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/command-button-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/command-button-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
widgets.command-button.behavior
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html index 71edff2c83..8d05bad66d 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/power-button-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
widgets.power-button.behavior
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.html index d506fe4e8b..905e9e3bac 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/button/toggle-button-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
widgets.toggle-button.behavior
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html index 450f8227fa..c64a4e23d9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/indicator/status-widget-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
widgets.status-widget.behavior
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html index 8b34ffa0c8..1ed4ec86a9 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/single-switch-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
widgets.single-switch.behavior
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html index d9bd4b82f2..ff5397ea16 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/slider-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
widgets.slider.behavior
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html index d2d1e65491..986af5d80e 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/rpc/value-stepper-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
widgets.value-stepper.behavior
diff --git a/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html b/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html index 03cdee6b95..fa9780cd06 100644 --- a/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/config/basic/scada/scada-symbol-basic-config.component.html @@ -16,7 +16,7 @@ --> - +
scada.symbol.symbol
@if (targetDeviceFormGroup.get('type').value === targetDeviceType.device) { @@ -32,6 +33,7 @@ } @if (targetDeviceFormGroup.get('type').value === targetDeviceType.entity) {
@if (settings.showTemplate) { - + } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html index 3b5990a268..5cdc878b57 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator.component.html @@ -19,7 +19,7 @@ [class.short-mode]="ctx.width < 400" [class.labels-hidden]="settings.hideLabels" [class.long-mode]="ctx.width >= 400"> - + @if (!settings.hideLabels) { widgets.date-range-navigator.localizationMap.Date picker } @@ -29,7 +29,7 @@ - + @if (!settings.hideLabels) { widgets.date-range-navigator.localizationMap.Interval } @@ -51,7 +51,7 @@ - + @if (!settings.hideLabels) { widgets.date-range-navigator.localizationMap.Step size } diff --git a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html index 5c153621fc..de4bc3c1a5 100644 --- a/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html +++ b/ui-ngx/src/app/modules/home/components/widget/lib/home-page/doc-link.component.html @@ -18,8 +18,8 @@ @if (addMode || editMode) {