diff --git a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java index 1a65232296..867efa67cc 100644 --- a/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java @@ -119,6 +119,8 @@ public class SwaggerConfiguration { // Keyed by the schema name that swagger-core generates (see resolveSchemaName). private final Map> schemaPropertyOrders = new ConcurrentHashMap<>(); private final Map> schemaOwnProps = new ConcurrentHashMap<>(); + // Tracks schema name → fully-qualified class names to detect collisions. + private final Map> schemaNameToClasses = new ConcurrentHashMap<>(); @Value("${swagger.api_path:/api/**}") private String apiPath; @@ -343,6 +345,10 @@ public class SwaggerConfiguration { try { var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType); String schemaName = resolveSchemaName(javaType); + Set classes = schemaNameToClasses.computeIfAbsent(schemaName, k -> ConcurrentHashMap.newKeySet()); + if (classes.add(cls.getName()) && classes.size() > 1) { + log.error("Duplicate OpenAPI schema name '{}' mapped by: {}. Use @Schema(name = ...) to disambiguate.", schemaName, classes); + } schemaPropertyOrders.put(schemaName, resolvePropertyOrder(cls, beanDesc)); Set ownProps = computeOwnPropNames(cls, beanDesc); if (!ownProps.isEmpty()) { @@ -390,6 +396,19 @@ public class SwaggerConfiguration { var apiKeyRequirement = createSecurityRequirement(API_KEY_SCHEME); return openAPI -> { + // Fail fast on duplicate schema names — two different classes resolving to the same + // OpenAPI schema name causes one to silently overwrite the other. + List duplicates = schemaNameToClasses.entrySet().stream() + .filter(e -> e.getValue().size() > 1) + .map(e -> "'" + e.getKey() + "' mapped by: " + e.getValue()) + .sorted() + .toList(); + if (!duplicates.isEmpty()) { + throw new IllegalStateException( + "Duplicate OpenAPI schema names detected. Use @Schema(name = ...) to disambiguate:\n " + + String.join("\n ", duplicates)); + } + var paths = openAPI.getPaths(); paths.entrySet().stream() .peek(entry -> { @@ -885,7 +904,13 @@ public class SwaggerConfiguration { * This matches the naming convention used by swagger-core's {@code TypeNameResolver}. */ private static String resolveSchemaName(JavaType javaType) { - StringBuilder sb = new StringBuilder(javaType.getRawClass().getSimpleName()); + Class cls = javaType.getRawClass(); + io.swagger.v3.oas.annotations.media.Schema schemaAnnotation = + cls.getAnnotation(io.swagger.v3.oas.annotations.media.Schema.class); + if (schemaAnnotation != null && !schemaAnnotation.name().isEmpty()) { + return schemaAnnotation.name(); + } + StringBuilder sb = new StringBuilder(cls.getSimpleName()); if (javaType.hasGenericTypes()) { for (int i = 0; i < javaType.containedTypeCount(); i++) { JavaType param = javaType.containedType(i); @@ -990,8 +1015,14 @@ public class SwaggerConfiguration { // Map backing field names to their JSON property names (respects @JsonProperty) Map fieldToJsonName = new LinkedHashMap<>(); for (var prop : beanDesc.findProperties()) { - if (prop.getField() != null && prop.couldSerialize()) { - fieldToJsonName.put(prop.getField().getName(), prop.getName()); + if (prop.couldSerialize()) { + if (prop.getField() != null) { + fieldToJsonName.put(prop.getField().getName(), prop.getName()); + } else { + // For transient fields, Jackson may not associate the field with the property. + // Fall back to using the property name as the field name key. + fieldToJsonName.putIfAbsent(prop.getName(), prop.getName()); + } } } @@ -1026,4 +1057,4 @@ public class SwaggerConfiguration { .value(node); } -} +} \ No newline at end of file diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/AlarmRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/AlarmRule.java index b7dc5059a7..3ab3f0a526 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/AlarmRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/AlarmRule.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.alarm.rule.condition.AlarmCondition; import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.id.DashboardId; -@Schema(name = "AlarmRuleDefinition") +@Schema @Data @AllArgsConstructor @NoArgsConstructor diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/AlarmCondition.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/AlarmCondition.java index 88dc7f5859..87bd44ce1f 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/AlarmCondition.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/AlarmCondition.java @@ -32,7 +32,6 @@ import org.thingsboard.server.common.data.alarm.rule.condition.schedule.AlarmSch import org.thingsboard.server.common.data.alarm.rule.condition.schedule.AnyTimeSchedule; @Schema( - name = "AlarmRuleCondition", discriminatorProperty = "type", discriminatorMapping = { @DiscriminatorMapping(value = "SIMPLE", schema = SimpleAlarmCondition.class), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/DurationAlarmCondition.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/DurationAlarmCondition.java index bee112d25d..3599ee5c3c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/DurationAlarmCondition.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/DurationAlarmCondition.java @@ -25,7 +25,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.util.concurrent.TimeUnit; -@Schema(name = "AlarmRuleDurationCondition") +@Schema @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/RepeatingAlarmCondition.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/RepeatingAlarmCondition.java index ca03d257a1..42cba04ec0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/RepeatingAlarmCondition.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/RepeatingAlarmCondition.java @@ -22,7 +22,7 @@ import lombok.Data; import lombok.EqualsAndHashCode; import lombok.ToString; -@Schema(name = "AlarmRuleRepeatingCondition") +@Schema @Data @EqualsAndHashCode(callSuper = true) @ToString(callSuper = true) diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/SimpleAlarmCondition.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/SimpleAlarmCondition.java index 38e0434b1d..f91ff1f3f0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/SimpleAlarmCondition.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/SimpleAlarmCondition.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.alarm.rule.condition; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(name = "AlarmRuleSimpleCondition") +@Schema public class SimpleAlarmCondition extends AlarmCondition { @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/AlarmConditionFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/AlarmConditionFilter.java index 75849967a0..f14e97a704 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/AlarmConditionFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/AlarmConditionFilter.java @@ -31,7 +31,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; import java.util.List; -@Schema(name = "AlarmRuleConditionFilter") +@Schema @Data public class AlarmConditionFilter implements Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/ComplexOperation.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/ComplexOperation.java index 492fc683dd..9ed265f3b8 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/ComplexOperation.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/ComplexOperation.java @@ -15,6 +15,9 @@ */ package org.thingsboard.server.common.data.alarm.rule.condition.expression; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(name = "AlarmRuleComplexOperation") public enum ComplexOperation { AND, OR diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/BooleanFilterPredicate.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/BooleanFilterPredicate.java index 0ff72c4297..64a73d2f6d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/BooleanFilterPredicate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/BooleanFilterPredicate.java @@ -37,6 +37,7 @@ public class BooleanFilterPredicate implements SimpleKeyFilterPredicate return FilterPredicateType.BOOLEAN; } + @Schema(name = "AlarmRuleBooleanOperation") public enum BooleanOperation { EQUAL, NOT_EQUAL diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NoDataFilterPredicate.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NoDataFilterPredicate.java index 6463aa7f81..1f719cb80c 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NoDataFilterPredicate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NoDataFilterPredicate.java @@ -25,7 +25,7 @@ import org.thingsboard.server.common.data.alarm.rule.condition.AlarmConditionVal import java.util.concurrent.TimeUnit; -@Schema(name = "AlarmRuleNoDataFilterPredicate") +@Schema @Data @AllArgsConstructor @NoArgsConstructor diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NumericFilterPredicate.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NumericFilterPredicate.java index 2fb249fc96..5bddbc4387 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NumericFilterPredicate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NumericFilterPredicate.java @@ -41,6 +41,7 @@ public class NumericFilterPredicate implements SimpleKeyFilterPredicate return FilterPredicateType.NUMERIC; } + @Schema(name = "AlarmRuleNumericOperation") public enum NumericOperation { EQUAL, NOT_EQUAL, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/StringFilterPredicate.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/StringFilterPredicate.java index 3af7d9c083..602730b7e0 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/StringFilterPredicate.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/StringFilterPredicate.java @@ -38,6 +38,7 @@ public class StringFilterPredicate implements SimpleKeyFilterPredicate { return FilterPredicateType.STRING; } + @Schema(name= "AlarmRuleStringOperation") public enum StringOperation { EQUAL, NOT_EQUAL, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmSchedule.java index c77eaf4fa5..127487b471 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmSchedule.java @@ -27,7 +27,6 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; @Schema( - name = "AlarmRuleSchedule", discriminatorProperty = "type", discriminatorMapping = { @DiscriminatorMapping(value = "ANY_TIME", schema = AnyTimeSchedule.class), diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmScheduleType.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmScheduleType.java index b2655f58d5..28093fd845 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmScheduleType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmScheduleType.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.alarm.rule.condition.schedule; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(name = "AlarmRuleScheduleType") +@Schema public enum AlarmScheduleType { ANY_TIME, SPECIFIC_TIME, diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AnyTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AnyTimeSchedule.java index 4c1b9fdc75..873e1f882d 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AnyTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AnyTimeSchedule.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.alarm.rule.condition.schedule; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(name = "AlarmRuleAnyTimeSchedule") +@Schema public class AnyTimeSchedule implements AlarmSchedule { @Override diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeSchedule.java index 1530de019b..aea9db5be4 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeSchedule.java @@ -20,7 +20,7 @@ import lombok.Data; import java.util.List; -@Schema(name = "AlarmRuleCustomTimeSchedule") +@Schema @Data public class CustomTimeSchedule implements AlarmSchedule { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeScheduleItem.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeScheduleItem.java index 0029ea9e24..d0756a3195 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeScheduleItem.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeScheduleItem.java @@ -21,7 +21,7 @@ import io.swagger.v3.oas.annotations.media.Schema; import java.io.Serializable; -@Schema(name = "AlarmRuleCustomTimeScheduleItem") +@Schema @Data public class CustomTimeScheduleItem implements Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/SpecificTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/SpecificTimeSchedule.java index 1a888a1e87..a23dbe48a7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/SpecificTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/SpecificTimeSchedule.java @@ -20,7 +20,7 @@ import lombok.Data; import java.util.Set; -@Schema(name = "AlarmRuleSpecificTimeSchedule") +@Schema @Data public class SpecificTimeSchedule implements AlarmSchedule { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java index fc0c4fdd2c..120df6f3b2 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java @@ -24,7 +24,7 @@ import lombok.Data; import java.io.Serializable; import java.util.List; -@Schema +@Schema(hidden = true) @Data @JsonIgnoreProperties(ignoreUnknown = true) @Deprecated diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java index b2a3083f6f..96dfee9703 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.validation.NoXss; import java.io.Serializable; -@Schema +@Schema(hidden = true) @Data @Deprecated public class AlarmConditionFilter implements Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java index 9ceea43677..7a9be0f302 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java @@ -24,7 +24,7 @@ import org.thingsboard.server.common.data.validation.NoXss; import java.io.Serial; import java.io.Serializable; -@Schema +@Schema(hidden = true) @Data @Deprecated public class AlarmRule implements Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java index 5509f7e896..884d9f13c5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java @@ -18,20 +18,13 @@ package org.thingsboard.server.common.data.device.profile; import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonTypeInfo; -import io.swagger.v3.oas.annotations.media.DiscriminatorMapping; import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.query.DynamicValue; import java.io.Serializable; @Schema( - description = "Configuration for alarm schedule", - discriminatorProperty = "type", - discriminatorMapping = { - @DiscriminatorMapping(value = "ANY_TIME", schema = AnyTimeSchedule.class), - @DiscriminatorMapping(value = "SPECIFIC_TIME", schema = SpecificTimeSchedule.class), - @DiscriminatorMapping(value = "CUSTOM", schema = CustomTimeSchedule.class) - } + hidden = true ) @JsonIgnoreProperties(ignoreUnknown = true) @JsonTypeInfo( diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmScheduleType.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmScheduleType.java index 7f78dbc46d..1183f9ae63 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmScheduleType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmScheduleType.java @@ -15,6 +15,9 @@ */ package org.thingsboard.server.common.data.device.profile; +import io.swagger.v3.oas.annotations.media.Schema; + +@Schema(hidden = true) @Deprecated public enum AlarmScheduleType { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java index e7acb04cd3..70463dfbe7 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java @@ -15,8 +15,10 @@ */ package org.thingsboard.server.common.data.device.profile; +import io.swagger.v3.oas.annotations.media.Schema; import org.thingsboard.server.common.data.query.DynamicValue; +@Schema(hidden = true) @Deprecated public class AnyTimeSchedule implements AlarmSchedule { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java index 878a85360b..cea1d76d36 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.common.data.device.profile; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.query.DynamicValue; import java.util.List; +@Schema(hidden = true) @Data @Deprecated public class CustomTimeSchedule implements AlarmSchedule { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeScheduleItem.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeScheduleItem.java index 0d4f5cf018..d91c978ac5 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeScheduleItem.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeScheduleItem.java @@ -15,10 +15,12 @@ */ package org.thingsboard.server.common.data.device.profile; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import java.io.Serializable; +@Schema(hidden = true) @Data @Deprecated public class CustomTimeScheduleItem implements Serializable { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java index dfaf6c763c..8bd56d07be 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java @@ -15,11 +15,13 @@ */ package org.thingsboard.server.common.data.device.profile; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; import org.thingsboard.server.common.data.query.DynamicValue; import java.util.Set; +@Schema(hidden = true) @Data @Deprecated public class SpecificTimeSchedule implements AlarmSchedule { diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java b/common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java index 68f06d94d4..25cd734d77 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java @@ -15,6 +15,7 @@ */ package org.thingsboard.server.common.data.page; +import io.swagger.v3.oas.annotations.media.Schema; import lombok.Data; @Data @@ -36,6 +37,7 @@ public class SortOrder { return new SortOrder(property, direction); } + @Schema(name = "SortOrderDirection") public static enum Direction { ASC, DESC } diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java index 4cd0e51925..008988e6cb 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java +++ b/common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java @@ -17,7 +17,7 @@ package org.thingsboard.server.common.data.transport.resource; import io.swagger.v3.oas.annotations.media.Schema; -@Schema(name = "TransportResourceType") +@Schema(hidden = true) public enum ResourceType { LWM2M_MODEL, JKS, PKCS_12 } 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..fefda74666 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 @@ -268,6 +266,11 @@ public class TbHttpClient { }); } catch (InterruptedException e) { log.warn("Timeout during waiting for reply!", e); + } catch (Exception e) { + if (semaphore != null) { + semaphore.release(); + } + onFailure.accept(msg, e); } } @@ -275,7 +278,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 +343,22 @@ 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); + if (config.isParseToPlainText()) { + return processedTemplate; + } + try { + return JacksonUtil.toJsonNode(processedTemplate); + } catch (Exception e) { + throw new RuntimeException("Request body template produced invalid JSON: " + processedTemplate, e); + } + } + 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 +382,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..1172fd8749 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. " + @@ -56,6 +58,7 @@ public class TbRestApiCallNode extends TbAbstractExternalNode { static final String PARSE_TO_PLAIN_TEXT = "parseToPlainText"; static final String MAX_IN_MEMORY_BUFFER_SIZE_IN_KB = "maxInMemoryBufferSizeInKb"; + static final String REQUEST_BODY_TEMPLATE = "requestBodyTemplate"; static final String TRIM_DOUBLE_QUOTES = "trimDoubleQuotes"; protected TbHttpClient httpClient; @@ -110,12 +113,16 @@ public class TbRestApiCallNode extends TbAbstractExternalNode { ((ObjectNode) oldConfiguration).put(MAX_IN_MEMORY_BUFFER_SIZE_IN_KB, 256); } case 3: + if (!oldConfiguration.has(REQUEST_BODY_TEMPLATE)) { + hasChanges = true; + ((ObjectNode) oldConfiguration).putNull(REQUEST_BODY_TEMPLATE); + } Set knownProperties = Set.of( "restEndpointUrlPattern", "requestMethod", "headers", "readTimeoutMs", "maxParallelRequestsCount", "parseToPlainText", "enableProxy", "useSystemProxyProperties", "proxyHost", "proxyPort", "proxyUser", "proxyPassword", "credentials", "ignoreRequestBody", - "maxInMemoryBufferSizeInKb" + "requestBodyTemplate", "maxInMemoryBufferSizeInKb" ); Iterator fieldNames = oldConfiguration.fieldNames(); while (fieldNames.hasNext()) { 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..e8e63ee2a8 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,11 +48,14 @@ 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; 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; @@ -71,19 +70,17 @@ import static org.mockito.Mockito.verify; @ExtendWith(MockitoExtension.class) public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { - private RuleNode ruleNode; - @Spy private TbRestApiCallNode restNode; @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; @@ -108,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); @@ -217,22 +214,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 +261,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 +310,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 AtomicReference capturedBody = new AtomicReference<>(); + 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.get()); + } + + @Test + public void postRequestWithBodyTemplateAndParseToPlainText() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final String path = "/api/text"; + final AtomicReference capturedBody = new AtomicReference<>(); + 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.get()); + } + + @Test + public void postRequestWithBodyTemplateEscapesJsonSpecialChars() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final String path = "/api/token"; + final AtomicReference capturedBody = new AtomicReference<>(); + 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.get()); + } + + @Test + public void postRequestWithEmptyBodyTemplateUsesMessageData() throws IOException, InterruptedException { + final CountDownLatch latch = new CountDownLatch(1); + final String path = "/api/data"; + final AtomicReference capturedBody = new AtomicReference<>(); + 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.get()); + } + + 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.set(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, @@ -339,7 +463,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"credentials\": {\"type\": \"anonymous\"}," + - "\"maxInMemoryBufferSizeInKb\": 256}"), + "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"), Arguments.of(1, "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + @@ -355,7 +479,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"credentials\": {\"type\": \"anonymous\"}," + - "\"maxInMemoryBufferSizeInKb\": 256}"), + "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"), Arguments.of(2, "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + @@ -370,7 +494,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"credentials\": {\"type\": \"anonymous\"}," + - "\"maxInMemoryBufferSizeInKb\": 256}"), + "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"), Arguments.of(3, """ { "restEndpointUrlPattern": "http://localhost/api", @@ -419,6 +543,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { "credentials": { "type": "anonymous" }, + "requestBodyTemplate": null, "maxInMemoryBufferSizeInKb": 256 }""") ); 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/core/api/widget-api.models.ts b/ui-ngx/src/app/core/api/widget-api.models.ts index f92db80cc4..12f5bc60d8 100644 --- a/ui-ngx/src/app/core/api/widget-api.models.ts +++ b/ui-ngx/src/app/core/api/widget-api.models.ts @@ -67,6 +67,7 @@ import { DashboardUtilsService } from '@core/services/dashboard-utils.service'; import { MatDialogRef } from '@angular/material/dialog'; import { TbUnit } from '@shared/models/unit.models'; import { UnitService } from '@core/services/unit.service'; +import { HttpOptionsResult, HttpUploadOptionsResult, QueryParams, RequestConfig } from '@core/http/http-utils'; export interface TimewindowFunctions { onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; @@ -94,6 +95,14 @@ export interface IWidgetUtils { getEntityDetailsPageURL: (id: string, entityType: EntityType) => string; } +export interface IWidgetHttpUtils { + defaultHttpOptions: (ignoreLoading?: boolean, ignoreErrors?: boolean, resendRequest?: boolean, queryParams?: QueryParams) => HttpOptionsResult; + defaultHttpOptionsFromConfig: (config?: RequestConfig) => HttpOptionsResult; + defaultHttpOptionsFromParams: (queryParams?: QueryParams, config?: RequestConfig) => HttpOptionsResult; + defaultHttpUploadOptions: (ignoreLoading?: boolean, ignoreErrors?: boolean, resendRequest?: boolean, queryParams?: QueryParams) => HttpUploadOptionsResult; + createDefaultHttpOptions: (queryParamsOrConfig?: QueryParams | RequestConfig, config?: RequestConfig) => HttpOptionsResult; +} + export interface PlaceMapItemActionData { action: WidgetAction | WidgetActionDescriptor; additionalParams?: any; diff --git a/ui-ngx/src/app/core/http/http-utils.ts b/ui-ngx/src/app/core/http/http-utils.ts index 8b90821c09..98f65f924a 100644 --- a/ui-ngx/src/app/core/http/http-utils.ts +++ b/ui-ngx/src/app/core/http/http-utils.ts @@ -15,7 +15,7 @@ /// import { InterceptorHttpParams } from '../interceptors/interceptor-http-params'; -import { HttpHeaders } from '@angular/common/http'; +import { HttpHeaders, HttpParams } from '@angular/common/http'; import { InterceptorConfig } from '../interceptors/interceptor-config'; export type QueryParams = { [param:string]: any }; @@ -27,6 +27,15 @@ export interface RequestConfig { queryParams?: QueryParams; } +export interface HttpOptionsResult { + headers: HttpHeaders; + params: HttpParams; +} + +export interface HttpUploadOptionsResult { + params: HttpParams; +} + export function hasRequestConfig(config?: any): boolean { if (!config) { return false; diff --git a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts index 49f8e92458..f2f27c3b8b 100644 --- a/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts +++ b/ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts @@ -332,7 +332,10 @@ export class CfAlarmRuleConditionDialogComponent extends DialogComponent -
+
dashboard.select-existing
-
+
dashboard.create-new - + dashboard.new-dashboard-title diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html index e2aca6e139..251496096b 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html @@ -25,7 +25,7 @@
- + attribute.delete-timeseries.strategy @for (strategy of strategiesTranslationsMap.keys(); track strategy) { @@ -37,13 +37,13 @@ @if (isPeriodStrategy()) {
- + attribute.delete-timeseries.start-time - + attribute.delete-timeseries.ends-on diff --git a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss index c42ab55735..4a5969afe7 100644 --- a/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss +++ b/ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss @@ -26,7 +26,7 @@ display: flex; flex-direction: column; gap: 16px; - padding-top: 0; + padding-top: 4px; } .tb-select-interval { 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/device/device-credentials.component.html b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html index fb9332018a..3afadca7b4 100644 --- a/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html +++ b/ui-ngx/src/app/modules/home/components/device/device-credentials.component.html @@ -62,7 +62,7 @@ @case (deviceCredentialsType.X509_CERTIFICATE) { device.certificate-pem-format - + @if (deviceCredentialsFormGroup.get('credentialsValue').value) { + {{ 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/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/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..7543feb3ad 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) { @@ -34,6 +34,7 @@ maxErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}" [minTime]="1" [maxTime]="maxDeduplicateTime" + [appearance]="appearance" formControlName="deduplicationIntervalSecs"> } 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..6b4ec383a1 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')) { @@ -91,7 +91,7 @@ {{ '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/arguments-map-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html index ee56981b6a..633c169cf1 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html @@ -45,8 +45,8 @@ {{argumentControl.get('name').value}}. } -
- +
+ rule-node-config.argument-source-field-input @@ -70,8 +70,7 @@
@if (argumentControl.get('type').value && argumentControl.get('type').value !== ArgumentType.CONSTANT) { - + rule-node-config.argument-key-field-input } @if (argumentControl.get('type').value === ArgumentType.CONSTANT) { - + rule-node-config.constant-value-field-input } @if (argumentControl.get('type').value && argumentControl.get('type').value !== ArgumentType.CONSTANT) { - + rule-node-config.default-value-field-input }
@if (argumentControl.get('type').value === ArgumentType.ATTRIBUTE) { - + rule-node-config.attribute-scope-field-input @for (scope of attributeScope; track scope) { 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 } - + @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/rule-node/common/kv-map-config.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html index d7dc9b6bb3..d706102304 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html @@ -47,8 +47,7 @@
@for (keyValControl of keyValsFormArray().controls; track keyValControl) { -
+
@@ -56,7 +55,7 @@
-
+ } @empty { + @if (emptyText) { +
{{ emptyText }}
+ } }
diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts index e98f145724..ac8b62487e 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts @@ -80,6 +80,8 @@ export class KvMapConfigComponent implements ControlValueAccessor, OnInit, Valid @Input() hintText: string; + @Input() emptyText: string; + @Input() popupHelpLink: string; @Input() @@ -171,7 +173,7 @@ export class KvMapConfigComponent implements ControlValueAccessor, OnInit, Valid }; writeValue(keyValMap: { [key: string]: string }): void { - const keyValuesData = Object.keys(keyValMap).map(key => ({key, value: keyValMap[key]})); + const keyValuesData = Object.keys(keyValMap ?? {}).map(key => ({key, value: keyValMap[key]})); if (this.keyValsFormArray().length === keyValuesData.length) { this.keyValsFormArray().patchValue(keyValuesData, {emitEvent: false}); } else { diff --git a/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html b/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html index 8dfbbaf1d6..5ae8457030 100644 --- a/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html +++ b/ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + 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..9395f59d6a 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..cc02018710 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) { @@ -88,20 +88,21 @@ - - + [keyText]="'rule-node-config.key' | translate" + [keyRequiredText]="'rule-node-config.key-required' | translate" + [valText]="'rule-node-config.value' | translate" + [valRequiredText]="'rule-node-config.value-required' | translate" + [emptyText]="'rule-node-config.other-properties-empty' | translate"> + {{ 'rule-node-config.add-metadata-key-values-as-kafka-headers' | translate }}
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')) { @@ -46,12 +46,13 @@
- - + [keyText]="'rule-node-config.name' | translate" + [keyRequiredText]="'rule-node-config.name-required' | translate" + [valText]="'rule-node-config.value' | translate" + [valRequiredText]="'rule-node-config.value-required' | translate" + [emptyText]="'rule-node-config.message-attributes-empty' | translate"> +
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..71b53b53f6 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,15 +64,15 @@ }
- + rule-node-config.virtual-host - + rule-node-config.username - + rule-node-config.password @@ -80,7 +80,7 @@ {{ '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')) { @@ -99,12 +99,13 @@ } - - + [keyText]="'rule-node-config.key' | translate" + [keyRequiredText]="'rule-node-config.key-required' | translate" + [valText]="'rule-node-config.value' | translate" + [valRequiredText]="'rule-node-config.value-required' | translate" + [emptyText]="'rule-node-config.client-properties-empty' | translate"> +
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..f5ec1ba156 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) { @@ -61,15 +61,16 @@ {{ 'rule-node-config.headers' | translate }} -
+ - + [keyText]="'rule-node-config.header' | translate" + [keyRequiredText]="'rule-node-config.header-required' | translate" + [valText]="'rule-node-config.value' | translate" + [valRequiredText]="'rule-node-config.value-required' | translate" + [emptyText]="'rule-node-config.headers-empty' | translate"> +
@@ -92,7 +93,7 @@ @if (!restApiCallConfigForm.get('useSystemProxyProperties').value) {
- + rule-node-config.proxy-host @if (restApiCallConfigForm.get('proxyHost').hasError('required')) { @@ -101,7 +102,7 @@ } - + rule-node-config.proxy-port @if (restApiCallConfigForm.get('proxyPort').hasError('required')) { @@ -116,11 +117,11 @@ }
- + rule-node-config.proxy-user - + rule-node-config.proxy-password @@ -137,12 +138,23 @@
-
+
{{ 'rule-node-config.ignore-request-body' | translate }} + @if(!restApiCallConfigForm.get('ignoreRequestBody').value) { +
+ + rule-node-config.request-body-template + + + }
- + rule-node-config.read-timeout rule-node-config.read-timeout-hint @@ -152,7 +164,7 @@ } - + rule-node-config.max-parallel-requests-count rule-node-config.max-parallel-requests-count-hint @@ -162,7 +174,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/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/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/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..e803d37921 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')) { @@ -54,15 +54,16 @@ }
- - - + [keyText]="'rule-node-config.name' | translate" + [keyRequiredText]="'rule-node-config.name-required' | translate" + [valText]="'rule-node-config.value' | translate" + [valRequiredText]="'rule-node-config.value-required' | translate" + [emptyText]="'rule-node-config.message-attributes-empty' | translate"> + + rule-node-config.aws-access-key-id @if (sqsConfigForm.get('accessKeyId').hasError('required')) { @@ -71,7 +72,7 @@ } - + rule-node-config.aws-secret-access-key @if (sqsConfigForm.get('secretAccessKey').hasError('required')) { @@ -80,7 +81,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 +
diff --git a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-edge-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-edge-dialog.component.html index 599b217e61..4aed4a41b8 100644 --- a/ui-ngx/src/app/modules/home/dialogs/add-entities-to-edge-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/add-entities-to-edge-dialog.component.html @@ -37,6 +37,7 @@ diff --git a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html index 1e105ff86a..efa6ef3c1c 100644 --- a/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html +++ b/ui-ngx/src/app/modules/home/dialogs/assign-to-customer-dialog.component.html @@ -36,6 +36,7 @@
{{ assignToCustomerText | translate }}
diff --git a/ui-ngx/src/app/modules/home/models/widget-component.models.ts b/ui-ngx/src/app/modules/home/models/widget-component.models.ts index 4cf6eb68ec..a9d57a0d41 100644 --- a/ui-ngx/src/app/modules/home/models/widget-component.models.ts +++ b/ui-ngx/src/app/modules/home/models/widget-component.models.ts @@ -38,6 +38,7 @@ import { Timewindow, WidgetTimewindow } from '@shared/models/time/time.models'; import { IAliasController, IStateController, + IWidgetHttpUtils, IWidgetSubscription, IWidgetUtils, RpcApi, @@ -58,6 +59,13 @@ import { ViewContainerRef } from '@angular/core'; import { HttpClient, HttpErrorResponse } from '@angular/common/http'; +import { + createDefaultHttpOptions, + defaultHttpOptions, + defaultHttpOptionsFromConfig, + defaultHttpOptionsFromParams, + defaultHttpUploadOptions +} from '@core/http/http-utils'; import { RafService } from '@core/services/raf.service'; import { WidgetTypeId } from '@shared/models/id/widget-type-id'; import { TenantId } from '@shared/models/id/tenant-id'; @@ -68,7 +76,8 @@ import { formatValue, getEntityDetailsPageURL, hasDatasourceLabelsVariables, - isDefined, isDefinedAndNotNull + isDefined, + isDefinedAndNotNull } from '@core/utils'; import { Store } from '@ngrx/store'; import { AppState } from '@core/core.state'; @@ -294,6 +303,14 @@ export class WidgetContext { getEntityDetailsPageURL }; + httpUtils: IWidgetHttpUtils = { + defaultHttpOptions, + defaultHttpOptionsFromConfig, + defaultHttpOptionsFromParams, + defaultHttpUploadOptions, + createDefaultHttpOptions + }; + $widgetElement: JQuery; $container: JQuery; $containerParent: JQuery; diff --git a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html index e6dc6b74b9..0142c83a85 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/general-settings.component.html @@ -33,7 +33,7 @@
- + admin.base-url @if (generalSettings.get('baseUrl').hasError('required')) { @@ -104,11 +104,11 @@ {{ 'admin.device-connectivity.' + protocol | translate }}
- + admin.device-connectivity.host - + admin.device-connectivity.port @if (deviceConnectivitySettingsForm.get(protocol + '.port').hasError('pattern')) { diff --git a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html index a653845ca3..25c9adfea0 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/home-settings.component.html @@ -36,6 +36,7 @@
- + admin.mail-from @if (mailSettings.get('mailFrom').hasError('required')) { @@ -44,7 +44,7 @@ } - + admin.oauth2.smtp-provider @for (provider of templateProvider; track provider) { @@ -62,7 +62,7 @@ - + admin.smtp-protocol @for (protocol of smtpProtocols; track protocol) { @@ -73,7 +73,7 @@
- + admin.smtp-host @if (mailSettings.get('smtpHost').hasError('required')) { @@ -82,7 +82,7 @@ } - + admin.smtp-port {{smtpPortInput.value?.length || 0}}/5 @@ -98,7 +98,7 @@ }
- + admin.timeout-msec {{timeoutInput.value?.length || 0}}/6 @@ -117,7 +117,7 @@ {{ 'admin.enable-tls' | translate }} @if (mailSettings.get('enableTls').value) { - + admin.tls-version @for (tlsVersion of tlsVersions; track tlsVersion) { @@ -134,7 +134,7 @@ @if (mailSettings.get('enableProxy').value) {
- + admin.proxy-host @if (mailSettings.get('proxyHost').hasError('required')) { @@ -143,7 +143,7 @@ } - + admin.proxy-port @if (mailSettings.get('proxyPort').hasError('required')) { @@ -161,11 +161,11 @@
- + admin.proxy-user - + admin.proxy-password @@ -178,7 +178,7 @@
admin.oauth2.authentication - + common.username @@ -202,7 +202,7 @@ } @if (mailSettings.get('changePassword').value || !showChangePassword) { - + common.password @@ -214,7 +214,7 @@ @if (mailSettings.get('enableOauth2').value) {
- + admin.oauth2.client-id @if (mailSettings.get('clientId').hasError('required')) { @@ -228,7 +228,7 @@ } - + admin.oauth2.client-secret @if (mailSettings.get('clientSecret').hasError('required')) { @@ -244,7 +244,7 @@
@if (mailSettings.get('providerId').value === mailServerOauth2Provider.OFFICE_365) { - + admin.oauth2.microsoft-tenant-id @if (mailSettings.get('providerTenantId').hasError('required')) { @@ -262,7 +262,7 @@
- + admin.oauth2.authorization-uri
@@ -313,7 +314,7 @@
- + admin.oauth2.protocol @for (protocol of protocols; track protocol) { @@ -323,7 +324,7 @@ } - + admin.domain-name @if (domainForm.get('name').hasError('pattern')) { @@ -345,7 +346,7 @@ }
- + admin.oauth2.redirect-uri-template
- + admin.oauth2.title @if (entityForm.get('title').hasError('required')) { @@ -34,7 +34,7 @@
- + admin.oauth2.provider @for (provider of templateProvider; track provider) { @@ -45,7 +45,7 @@
- + admin.oauth2.allowed-platforms @for (platform of platformTypes; track platform) { @@ -57,7 +57,7 @@
- + admin.oauth2.client-id @if (entityForm.get('clientId').hasError('required')) { @@ -71,7 +71,7 @@ } - + admin.oauth2.client-secret @if (entityForm.get('clientSecret').hasError('required')) { @@ -102,10 +102,9 @@
@if (generalSettingsMode) { -
+
- + admin.oauth2.access-token-uri @if (entityForm.get('accessTokenUri').hasError('required')) { @@ -119,7 +118,7 @@ } - + admin.oauth2.authorization-uri @if (entityForm.get('authorizationUri').hasError('required')) { @@ -135,7 +134,7 @@
- + admin.oauth2.jwk-set-uri @if (entityForm.get('jwkSetUri').hasError('pattern')) { @@ -144,7 +143,7 @@ } - + admin.oauth2.user-info-uri @if (entityForm.get('userInfoUri').hasError('pattern')) { @@ -155,7 +154,7 @@
- + admin.oauth2.client-authentication-method @for (clientAuthenticationMethod of clientAuthenticationMethods; track clientAuthenticationMethod) { @@ -169,7 +168,7 @@
@if (isCustomProvider()) {
- + admin.oauth2.login-button-label @@ -203,6 +202,7 @@
@@ -212,7 +212,7 @@ @if (!generalSettingsMode) {
- + admin.oauth2.user-name-attribute-name @if (entityForm.get('userNameAttributeName').hasError('required')) { @@ -224,7 +224,7 @@
- + admin.oauth2.type @for (mapperType of mapperTypes; track mapperType) { @@ -237,23 +237,19 @@
@if (entityForm.get('mapperConfig.type').value !== mapperType.CUSTOM && entityForm.get('mapperConfig.basic')) { -
+
@if (entityForm.get('mapperConfig.type').value !== mapperType.GITHUB) { -
- +
+ admin.oauth2.email-attribute-key @if (entityForm.get('mapperConfig.basic.emailAttributeKey').hasError('required')) { - + {{ 'admin.oauth2.email-attribute-key-required' | translate }} } @if (entityForm.get('mapperConfig.basic.emailAttributeKey').hasError('maxlength')) { - + {{ 'admin.oauth2.email-attribute-key-max-length' | translate }} } @@ -261,7 +257,7 @@
}
- + admin.oauth2.first-name-attribute-key @if (entityForm.get('mapperConfig.basic.firstNameAttributeKey').hasError('maxlength')) { @@ -270,7 +266,7 @@ } - + admin.oauth2.last-name-attribute-key @if (entityForm.get('mapperConfig.basic.lastNameAttributeKey').hasError('maxlength')) { @@ -281,7 +277,7 @@
- + admin.oauth2.tenant-name-strategy @for (tenantNameStrategy of tenantNameStrategies; track tenantNameStrategy) { @@ -293,26 +289,25 @@ admin.oauth2.tenant-name-pattern @if (entityForm.get('mapperConfig.basic.tenantNamePattern').hasError('required')) { - + {{ 'admin.oauth2.tenant-name-pattern-required' | translate }} } @if (entityForm.get('mapperConfig.basic.tenantNamePattern').hasError('maxlength')) { - + {{ 'admin.oauth2.tenant-name-pattern-max-length' | translate }} }
- + admin.oauth2.customer-name-pattern @if (entityForm.get('mapperConfig.basic.customerNamePattern').hasError('maxlength')) { @@ -323,7 +318,7 @@
- + admin.oauth2.default-dashboard-name @if (entityForm.get('mapperConfig.basic.defaultDashboardName').hasError('maxlength')) { @@ -340,10 +335,9 @@
} @if (entityForm.get('mapperConfig.type').value === mapperType.CUSTOM && entityForm.get('mapperConfig.custom')) { -
+
- + admin.oauth2.url @if (entityForm.get('mapperConfig.custom.url').hasError('required')) { @@ -364,7 +358,7 @@
- + common.username @if (entityForm.get('mapperConfig.custom.username').hasError('maxlength')) { @@ -373,7 +367,7 @@ } - + common.password @if (entityForm.get('mapperConfig.custom.password').hasError('maxlength')) { diff --git a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html index fb72798997..dc0726e68d 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/oauth2/domains/domain.component.html @@ -17,7 +17,7 @@ -->
- + admin.oauth2.domain-name @if (entityForm.get('name').hasError('required')) { @@ -38,7 +38,7 @@
- + admin.oauth2.redirect-url-template
-
- + admin.jwt.expiration-time } - + admin.jwt.refresh-expiration-time - + admin.sms-message @if (sendTestSmsFormGroup.get('message').hasError('required')) { diff --git a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html index a723b89043..a83292d2ea 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/sms-provider.component.html @@ -67,7 +67,7 @@
} - + admin.slack-api-token diff --git a/ui-ngx/src/app/modules/home/pages/admin/trendz-settings.component.html b/ui-ngx/src/app/modules/home/pages/admin/trendz-settings.component.html index 07789156bb..06031c8a69 100644 --- a/ui-ngx/src/app/modules/home/pages/admin/trendz-settings.component.html +++ b/ui-ngx/src/app/modules/home/pages/admin/trendz-settings.component.html @@ -38,7 +38,7 @@ {{ 'admin.trendz-enable' | translate }} - + admin.trendz-url @if (trendzSettingsForm.get('trendzUrl').hasError('required')) { @@ -52,7 +52,7 @@ } - + admin.trendz-api-key @if (trendzSettingsForm.get('apiKey').hasError('pattern')) { diff --git a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html index db5287fd68..76349148fd 100644 --- a/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html @@ -19,7 +19,7 @@ @if (transportType.isActive) {
- + device-profile.transport-type @for (type of deviceTransportTypes; track type) { diff --git a/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html b/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html index 992a88ab16..ec05b5ae77 100644 --- a/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/data/snmp-device-transport-configuration.component.html @@ -17,7 +17,7 @@ -->
- + device-profile.snmp.host @if (snmpDeviceTransportConfigurationFormGroup.get('host').hasError('required') || @@ -27,7 +27,7 @@ } - + device-profile.snmp.port @if (snmpDeviceTransportConfigurationFormGroup.get('port').hasError('required')) { @@ -43,7 +43,7 @@ }
- + device-profile.snmp.protocol-version @for (snmpDeviceProtocolVersion of snmpDeviceProtocolVersions; track snmpDeviceProtocolVersion) { @@ -60,7 +60,7 @@ @if (!isV3protocolVersion()) {
- + device-profile.snmp.community @if (snmpDeviceTransportConfigurationFormGroup.get('community').hasError('required') || @@ -75,7 +75,7 @@ @if (isV3protocolVersion()) {
- + device-profile.snmp.user-name @if (snmpDeviceTransportConfigurationFormGroup.get('username').hasError('required') || @@ -85,7 +85,7 @@ } - + device-profile.snmp.security-name @if (snmpDeviceTransportConfigurationFormGroup.get('securityName').hasError('required') || @@ -97,7 +97,7 @@
- + device-profile.snmp.authentication-protocol @for (snmpAuthenticationProtocol of snmpAuthenticationProtocols; track snmpAuthenticationProtocol) { @@ -112,7 +112,7 @@ } - + device-profile.snmp.authentication-passphrase @if (snmpDeviceTransportConfigurationFormGroup.get('authenticationPassphrase').hasError('required') || @@ -124,7 +124,7 @@
- + device-profile.snmp.privacy-protocol @for (snmpPrivacyProtocol of snmpPrivacyProtocols; track snmpPrivacyProtocol) { @@ -139,7 +139,7 @@ } - + device-profile.snmp.privacy-passphrase @if (snmpDeviceTransportConfigurationFormGroup.get('privacyPassphrase').hasError('required') || @@ -151,11 +151,11 @@
- + device-profile.snmp.context-name - + device-profile.snmp.engine-id diff --git a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html index 697dcf86af..3f3368e3b2 100644 --- a/ui-ngx/src/app/modules/home/pages/profile/profile.component.html +++ b/ui-ngx/src/app/modules/home/pages/profile/profile.component.html @@ -39,7 +39,7 @@
- + user.email @if (profile.get('email').hasError('required')) { @@ -53,20 +53,21 @@ } - + user.first-name - + user.last-name - + language.language @@ -76,7 +77,7 @@ } - + unit.unit-system @@ -90,6 +91,7 @@
- + rulechain.name @if (createNestedRuleChainFormGroup.get('name').hasError('required')) { @@ -47,7 +47,7 @@ }
- + rulechain.description diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html index d243baaa29..f6e244a81b 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/link-labels.component.html @@ -15,7 +15,7 @@ limitations under the License. --> - + rulenode.link-labels @for (label of labels; track label) { diff --git a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html index a59ad5c547..a83c1f8b17 100644 --- a/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html +++ b/ui-ngx/src/app/modules/home/pages/rulechain/rule-node-details.component.html @@ -44,8 +44,7 @@ [additionalActionConfig]="additionalActionConfig" class="mr-2" formControlName="debugSettings" - [entityType]="entityType.RULE_NODE" - /> + [entityType]="entityType.RULE_NODE"/> @if (isSingleton()) {