Browse Source

Merge branch 'master' into sparkplug-any-name-node-unique-device-names

pull/14987/head
nickAS21 6 months ago
parent
commit
e976989c94
  1. 39
      application/src/main/java/org/thingsboard/server/config/SwaggerConfiguration.java
  2. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/AlarmRule.java
  3. 1
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/AlarmCondition.java
  4. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/DurationAlarmCondition.java
  5. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/RepeatingAlarmCondition.java
  6. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/SimpleAlarmCondition.java
  7. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/AlarmConditionFilter.java
  8. 3
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/ComplexOperation.java
  9. 1
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/BooleanFilterPredicate.java
  10. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NoDataFilterPredicate.java
  11. 1
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/NumericFilterPredicate.java
  12. 1
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/expression/predicate/StringFilterPredicate.java
  13. 1
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmSchedule.java
  14. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AlarmScheduleType.java
  15. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/AnyTimeSchedule.java
  16. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeSchedule.java
  17. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/CustomTimeScheduleItem.java
  18. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/rule/condition/schedule/SpecificTimeSchedule.java
  19. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmCondition.java
  20. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmConditionFilter.java
  21. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmRule.java
  22. 9
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmSchedule.java
  23. 3
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AlarmScheduleType.java
  24. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/AnyTimeSchedule.java
  25. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeSchedule.java
  26. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/CustomTimeScheduleItem.java
  27. 2
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/SpecificTimeSchedule.java
  28. 2
      common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java
  29. 2
      common/data/src/main/java/org/thingsboard/server/common/data/transport/resource/ResourceType.java
  30. 32
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/util/TbNodeUtils.java
  31. 44
      rule-engine/rule-engine-api/src/test/java/org/thingsboard/rule/engine/api/util/TbNodeUtilsTest.java
  32. 33
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbHttpClient.java
  33. 11
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNode.java
  34. 1
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java
  35. 4
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java
  36. 229
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeTest.java
  37. 3
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbSendRestApiCallReplyNodeTest.java
  38. 9
      ui-ngx/src/app/core/api/widget-api.models.ts
  39. 11
      ui-ngx/src/app/core/http/http-utils.ts
  40. 5
      ui-ngx/src/app/modules/home/components/alarm-rules/cf-alarm-rule-condition-dialog.component.ts
  41. 7
      ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html
  42. 6
      ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html
  43. 2
      ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss
  44. 4
      ui-ngx/src/app/modules/home/components/dashboard-page/states/dashboard-state-dialog.component.html
  45. 2
      ui-ngx/src/app/modules/home/components/device/device-credentials.component.html
  46. 8
      ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html
  47. 2
      ui-ngx/src/app/modules/home/components/event/event-table-header.component.html
  48. 9
      ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html
  49. 2
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html
  50. 4
      ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts
  51. 6
      ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html
  52. 96
      ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html
  53. 4
      ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html
  54. 26
      ui-ngx/src/app/modules/home/components/queue/queue-form.component.html
  55. 4
      ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html
  56. 4
      ui-ngx/src/app/modules/home/components/relation/relation-table.component.html
  57. 3
      ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.html
  58. 4
      ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.ts
  59. 5
      ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting.component.html
  60. 2
      ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html
  61. 7
      ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html
  62. 2
      ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.html
  63. 8
      ui-ngx/src/app/modules/home/components/rule-node/action/create-alarm-config.component.html
  64. 6
      ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html
  65. 6
      ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html
  66. 5
      ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html
  67. 2
      ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html
  68. 5
      ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html
  69. 27
      ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.html
  70. 14
      ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html
  71. 4
      ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html
  72. 5
      ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html
  73. 4
      ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html
  74. 4
      ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.html
  75. 6
      ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.html
  76. 2
      ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.html
  77. 4
      ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.html
  78. 4
      ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.html
  79. 4
      ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.html
  80. 2
      ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html
  81. 15
      ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html
  82. 6
      ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html
  83. 6
      ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html
  84. 4
      ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html
  85. 9
      ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html
  86. 4
      ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.ts
  87. 2
      ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html
  88. 4
      ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.ts
  89. 2
      ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.html
  90. 4
      ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html
  91. 5
      ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts
  92. 4
      ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html
  93. 4
      ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.html
  94. 8
      ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html
  95. 1
      ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html
  96. 22
      ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.html
  97. 2
      ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html
  98. 14
      ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html
  99. 31
      ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html
  100. 14
      ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html

39
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). // Keyed by the schema name that swagger-core generates (see resolveSchemaName).
private final Map<String, List<String>> schemaPropertyOrders = new ConcurrentHashMap<>(); private final Map<String, List<String>> schemaPropertyOrders = new ConcurrentHashMap<>();
private final Map<String, Set<String>> schemaOwnProps = new ConcurrentHashMap<>(); private final Map<String, Set<String>> schemaOwnProps = new ConcurrentHashMap<>();
// Tracks schema name → fully-qualified class names to detect collisions.
private final Map<String, Set<String>> schemaNameToClasses = new ConcurrentHashMap<>();
@Value("${swagger.api_path:/api/**}") @Value("${swagger.api_path:/api/**}")
private String apiPath; private String apiPath;
@ -343,6 +345,10 @@ public class SwaggerConfiguration {
try { try {
var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType); var beanDesc = Json.mapper().getSerializationConfig().introspect(javaType);
String schemaName = resolveSchemaName(javaType); String schemaName = resolveSchemaName(javaType);
Set<String> 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)); schemaPropertyOrders.put(schemaName, resolvePropertyOrder(cls, beanDesc));
Set<String> ownProps = computeOwnPropNames(cls, beanDesc); Set<String> ownProps = computeOwnPropNames(cls, beanDesc);
if (!ownProps.isEmpty()) { if (!ownProps.isEmpty()) {
@ -390,6 +396,19 @@ public class SwaggerConfiguration {
var apiKeyRequirement = createSecurityRequirement(API_KEY_SCHEME); var apiKeyRequirement = createSecurityRequirement(API_KEY_SCHEME);
return openAPI -> { 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<String> 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(); var paths = openAPI.getPaths();
paths.entrySet().stream() paths.entrySet().stream()
.peek(entry -> { .peek(entry -> {
@ -885,7 +904,13 @@ public class SwaggerConfiguration {
* This matches the naming convention used by swagger-core's {@code TypeNameResolver}. * This matches the naming convention used by swagger-core's {@code TypeNameResolver}.
*/ */
private static String resolveSchemaName(JavaType javaType) { 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()) { if (javaType.hasGenericTypes()) {
for (int i = 0; i < javaType.containedTypeCount(); i++) { for (int i = 0; i < javaType.containedTypeCount(); i++) {
JavaType param = javaType.containedType(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 backing field names to their JSON property names (respects @JsonProperty)
Map<String, String> fieldToJsonName = new LinkedHashMap<>(); Map<String, String> fieldToJsonName = new LinkedHashMap<>();
for (var prop : beanDesc.findProperties()) { for (var prop : beanDesc.findProperties()) {
if (prop.getField() != null && prop.couldSerialize()) { if (prop.couldSerialize()) {
fieldToJsonName.put(prop.getField().getName(), prop.getName()); 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); .value(node);
} }
} }

2
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 io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.id.DashboardId; import org.thingsboard.server.common.data.id.DashboardId;
@Schema(name = "AlarmRuleDefinition") @Schema
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor

1
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; import org.thingsboard.server.common.data.alarm.rule.condition.schedule.AnyTimeSchedule;
@Schema( @Schema(
name = "AlarmRuleCondition",
discriminatorProperty = "type", discriminatorProperty = "type",
discriminatorMapping = { discriminatorMapping = {
@DiscriminatorMapping(value = "SIMPLE", schema = SimpleAlarmCondition.class), @DiscriminatorMapping(value = "SIMPLE", schema = SimpleAlarmCondition.class),

2
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; import java.util.concurrent.TimeUnit;
@Schema(name = "AlarmRuleDurationCondition") @Schema
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

2
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.EqualsAndHashCode;
import lombok.ToString; import lombok.ToString;
@Schema(name = "AlarmRuleRepeatingCondition") @Schema
@Data @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true) @ToString(callSuper = true)

2
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; import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "AlarmRuleSimpleCondition") @Schema
public class SimpleAlarmCondition extends AlarmCondition { public class SimpleAlarmCondition extends AlarmCondition {
@Override @Override

2
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.io.Serializable;
import java.util.List; import java.util.List;
@Schema(name = "AlarmRuleConditionFilter") @Schema
@Data @Data
public class AlarmConditionFilter implements Serializable { public class AlarmConditionFilter implements Serializable {

3
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; package org.thingsboard.server.common.data.alarm.rule.condition.expression;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "AlarmRuleComplexOperation")
public enum ComplexOperation { public enum ComplexOperation {
AND, AND,
OR OR

1
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<Boolean>
return FilterPredicateType.BOOLEAN; return FilterPredicateType.BOOLEAN;
} }
@Schema(name = "AlarmRuleBooleanOperation")
public enum BooleanOperation { public enum BooleanOperation {
EQUAL, EQUAL,
NOT_EQUAL NOT_EQUAL

2
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; import java.util.concurrent.TimeUnit;
@Schema(name = "AlarmRuleNoDataFilterPredicate") @Schema
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor

1
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<Double>
return FilterPredicateType.NUMERIC; return FilterPredicateType.NUMERIC;
} }
@Schema(name = "AlarmRuleNumericOperation")
public enum NumericOperation { public enum NumericOperation {
EQUAL, EQUAL,
NOT_EQUAL, NOT_EQUAL,

1
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<String> {
return FilterPredicateType.STRING; return FilterPredicateType.STRING;
} }
@Schema(name= "AlarmRuleStringOperation")
public enum StringOperation { public enum StringOperation {
EQUAL, EQUAL,
NOT_EQUAL, NOT_EQUAL,

1
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; import java.io.Serializable;
@Schema( @Schema(
name = "AlarmRuleSchedule",
discriminatorProperty = "type", discriminatorProperty = "type",
discriminatorMapping = { discriminatorMapping = {
@DiscriminatorMapping(value = "ANY_TIME", schema = AnyTimeSchedule.class), @DiscriminatorMapping(value = "ANY_TIME", schema = AnyTimeSchedule.class),

2
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; import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "AlarmRuleScheduleType") @Schema
public enum AlarmScheduleType { public enum AlarmScheduleType {
ANY_TIME, ANY_TIME,
SPECIFIC_TIME, SPECIFIC_TIME,

2
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; import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "AlarmRuleAnyTimeSchedule") @Schema
public class AnyTimeSchedule implements AlarmSchedule { public class AnyTimeSchedule implements AlarmSchedule {
@Override @Override

2
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; import java.util.List;
@Schema(name = "AlarmRuleCustomTimeSchedule") @Schema
@Data @Data
public class CustomTimeSchedule implements AlarmSchedule { public class CustomTimeSchedule implements AlarmSchedule {

2
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; import java.io.Serializable;
@Schema(name = "AlarmRuleCustomTimeScheduleItem") @Schema
@Data @Data
public class CustomTimeScheduleItem implements Serializable { public class CustomTimeScheduleItem implements Serializable {

2
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; import java.util.Set;
@Schema(name = "AlarmRuleSpecificTimeSchedule") @Schema
@Data @Data
public class SpecificTimeSchedule implements AlarmSchedule { public class SpecificTimeSchedule implements AlarmSchedule {

2
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.io.Serializable;
import java.util.List; import java.util.List;
@Schema @Schema(hidden = true)
@Data @Data
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@Deprecated @Deprecated

2
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; import java.io.Serializable;
@Schema @Schema(hidden = true)
@Data @Data
@Deprecated @Deprecated
public class AlarmConditionFilter implements Serializable { public class AlarmConditionFilter implements Serializable {

2
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.Serial;
import java.io.Serializable; import java.io.Serializable;
@Schema @Schema(hidden = true)
@Data @Data
@Deprecated @Deprecated
public class AlarmRule implements Serializable { public class AlarmRule implements Serializable {

9
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.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonSubTypes; import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo; import com.fasterxml.jackson.annotation.JsonTypeInfo;
import io.swagger.v3.oas.annotations.media.DiscriminatorMapping;
import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import java.io.Serializable; import java.io.Serializable;
@Schema( @Schema(
description = "Configuration for alarm schedule", hidden = true
discriminatorProperty = "type",
discriminatorMapping = {
@DiscriminatorMapping(value = "ANY_TIME", schema = AnyTimeSchedule.class),
@DiscriminatorMapping(value = "SPECIFIC_TIME", schema = SpecificTimeSchedule.class),
@DiscriminatorMapping(value = "CUSTOM", schema = CustomTimeSchedule.class)
}
) )
@JsonIgnoreProperties(ignoreUnknown = true) @JsonIgnoreProperties(ignoreUnknown = true)
@JsonTypeInfo( @JsonTypeInfo(

3
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; package org.thingsboard.server.common.data.device.profile;
import io.swagger.v3.oas.annotations.media.Schema;
@Schema(hidden = true)
@Deprecated @Deprecated
public enum AlarmScheduleType { public enum AlarmScheduleType {

2
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; package org.thingsboard.server.common.data.device.profile;
import io.swagger.v3.oas.annotations.media.Schema;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
@Schema(hidden = true)
@Deprecated @Deprecated
public class AnyTimeSchedule implements AlarmSchedule { public class AnyTimeSchedule implements AlarmSchedule {

2
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; package org.thingsboard.server.common.data.device.profile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.List; import java.util.List;
@Schema(hidden = true)
@Data @Data
@Deprecated @Deprecated
public class CustomTimeSchedule implements AlarmSchedule { public class CustomTimeSchedule implements AlarmSchedule {

2
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; package org.thingsboard.server.common.data.device.profile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import java.io.Serializable; import java.io.Serializable;
@Schema(hidden = true)
@Data @Data
@Deprecated @Deprecated
public class CustomTimeScheduleItem implements Serializable { public class CustomTimeScheduleItem implements Serializable {

2
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; package org.thingsboard.server.common.data.device.profile;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import org.thingsboard.server.common.data.query.DynamicValue; import org.thingsboard.server.common.data.query.DynamicValue;
import java.util.Set; import java.util.Set;
@Schema(hidden = true)
@Data @Data
@Deprecated @Deprecated
public class SpecificTimeSchedule implements AlarmSchedule { public class SpecificTimeSchedule implements AlarmSchedule {

2
common/data/src/main/java/org/thingsboard/server/common/data/page/SortOrder.java

@ -15,6 +15,7 @@
*/ */
package org.thingsboard.server.common.data.page; package org.thingsboard.server.common.data.page;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
@Data @Data
@ -36,6 +37,7 @@ public class SortOrder {
return new SortOrder(property, direction); return new SortOrder(property, direction);
} }
@Schema(name = "SortOrderDirection")
public static enum Direction { public static enum Direction {
ASC, DESC ASC, DESC
} }

2
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; import io.swagger.v3.oas.annotations.media.Schema;
@Schema(name = "TransportResourceType") @Schema(hidden = true)
public enum ResourceType { public enum ResourceType {
LWM2M_MODEL, JKS, PKCS_12 LWM2M_MODEL, JKS, PKCS_12
} }

32
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; package org.thingsboard.rule.engine.api.util;
import com.fasterxml.jackson.core.io.JsonStringEncoder;
import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.JsonNode;
import org.thingsboard.common.util.JacksonUtil; import org.thingsboard.common.util.JacksonUtil;
import org.thingsboard.rule.engine.api.TbNodeConfiguration; import org.thingsboard.rule.engine.api.TbNodeConfiguration;
@ -57,8 +58,12 @@ public final class TbNodeUtils {
} }
public static String processPattern(String pattern, TbMsg tbMsg) { public static String processPattern(String pattern, TbMsg tbMsg) {
return processPattern(pattern, tbMsg, false);
}
public static String processPattern(String pattern, TbMsg tbMsg, boolean escapeJsonValues) {
try { try {
String result = processPattern(pattern, tbMsg.getMetaData()); String result = processPattern(pattern, tbMsg.getMetaData(), escapeJsonValues);
JsonNode json = JacksonUtil.toJsonNode(tbMsg.getData()); JsonNode json = JacksonUtil.toJsonNode(tbMsg.getData());
result = result.replace(ALL_DATA_TEMPLATE, JacksonUtil.toString(json)); result = result.replace(ALL_DATA_TEMPLATE, JacksonUtil.toString(json));
@ -79,7 +84,11 @@ public final class TbNodeUtils {
} }
if (jsonNode != null && jsonNode.isValueNode()) { 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()); String replacement = metaData.isEmpty() ? "{}" : JacksonUtil.toString(metaData.getData());
pattern = pattern.replace(ALL_METADATA_TEMPLATE, replacement); 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<String, String> data) { public static String processTemplate(String template, Map<String, String> data) {
return processTemplate(template, data, false);
}
private static String processTemplate(String template, Map<String, String> data, boolean escapeJsonValues) {
String result = template; String result = template;
for (Map.Entry<String, String> kv : data.entrySet()) { for (Map.Entry<String, String> 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; return result;
} }
private static String processVar(String pattern, String key, String val) { private static String escapeJsonValue(String value) {
return pattern.replace(formatMetadataVarTemplate(key), val); return new String(JsonStringEncoder.getInstance().quoteAsString(value));
} }
static String formatDataVarTemplate(String key) { static String formatDataVarTemplate(String key) {

44
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)); 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 @Test
public void testMixedAllDataMetadataAndNormalTemplates() { public void testMixedAllDataMetadataAndNormalTemplates() {
// GIVEN // GIVEN

33
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.Base64;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects;
import java.util.Properties; import java.util.Properties;
import java.util.concurrent.Semaphore; import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -182,10 +183,7 @@ public class TbHttpClient {
} }
EventLoopGroup getSharedOrCreateEventLoopGroup(EventLoopGroup eventLoopGroupShared) { EventLoopGroup getSharedOrCreateEventLoopGroup(EventLoopGroup eventLoopGroupShared) {
if (eventLoopGroupShared != null) { return Objects.requireNonNullElseGet(eventLoopGroupShared, () -> this.eventLoopGroup = new NioEventLoopGroup());
return eventLoopGroupShared;
}
return this.eventLoopGroup = new NioEventLoopGroup();
} }
private void checkSystemProxyProperties() throws TbNodeException { private void checkSystemProxyProperties() throws TbNodeException {
@ -243,7 +241,7 @@ public class TbHttpClient {
if ((HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) || if ((HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) ||
HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method)) && HttpMethod.PATCH.equals(method) || HttpMethod.DELETE.equals(method)) &&
!config.isIgnoreRequestBody()) { !config.isIgnoreRequestBody()) {
request.body(BodyInserters.fromValue(getData(msg, config.isParseToPlainText()))); request.body(BodyInserters.fromValue(getRequestBody(msg)));
} }
request request
@ -268,6 +266,11 @@ public class TbHttpClient {
}); });
} catch (InterruptedException e) { } catch (InterruptedException e) {
log.warn("Timeout during waiting for reply!", 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 if (origin instanceof WebClientResponseException restClientResponseException
&& restClientResponseException.getStatusCode().is2xxSuccessful()) { && restClientResponseException.getStatusCode().is2xxSuccessful()) {
// return cause instead of original exception in case 2xx status code // 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 new RuntimeException(restClientResponseException.getCause());
} }
return origin; return origin;
@ -340,6 +343,22 @@ public class TbHttpClient {
return UriComponentsBuilder.fromUriString(endpointUrl).build().encode().toUri(); 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) { private Object getData(TbMsg tbMsg, boolean parseToPlainText) {
String data = tbMsg.getData(); String data = tbMsg.getData();
return parseToPlainText ? JacksonUtil.toPlainText(data) : JacksonUtil.toJsonNode(data); return parseToPlainText ? JacksonUtil.toPlainText(data) : JacksonUtil.toJsonNode(data);
@ -363,7 +382,7 @@ public class TbHttpClient {
headers.forEach((key, values) -> { headers.forEach((key, values) -> {
if (values != null && !values.isEmpty()) { if (values != null && !values.isEmpty()) {
if (values.size() == 1) { if (values.size() == 1) {
consumer.accept(key, values.get(0)); consumer.accept(key, values.getFirst());
} else { } else {
consumer.accept(key, JacksonUtil.toString(values)); consumer.accept(key, JacksonUtil.toString(values));
} }

11
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, version = 4,
nodeDescription = "Invoke REST API calls to external REST server", nodeDescription = "Invoke REST API calls to external REST server",
nodeDetails = "Will invoke REST API call <code>GET | POST | PUT | DELETE</code> to external REST server. " + nodeDetails = "Will invoke REST API call <code>GET | POST | PUT | DELETE</code> 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 <code>${metadataKey}</code> and <code>$[messageKey]</code> placeholders. " +
"Configured attributes can be added into Headers from Message Metadata." +
" Outbound message will contain response fields " + " Outbound message will contain response fields " +
"(<code>status</code>, <code>statusCode</code>, <code>statusReason</code> and response <code>headers</code>) in the Message Metadata." + "(<code>status</code>, <code>statusCode</code>, <code>statusReason</code> and response <code>headers</code>) in the Message Metadata." +
" Response body saved in outbound Message payload. " + " 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 PARSE_TO_PLAIN_TEXT = "parseToPlainText";
static final String MAX_IN_MEMORY_BUFFER_SIZE_IN_KB = "maxInMemoryBufferSizeInKb"; 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"; static final String TRIM_DOUBLE_QUOTES = "trimDoubleQuotes";
protected TbHttpClient httpClient; protected TbHttpClient httpClient;
@ -110,12 +113,16 @@ public class TbRestApiCallNode extends TbAbstractExternalNode {
((ObjectNode) oldConfiguration).put(MAX_IN_MEMORY_BUFFER_SIZE_IN_KB, 256); ((ObjectNode) oldConfiguration).put(MAX_IN_MEMORY_BUFFER_SIZE_IN_KB, 256);
} }
case 3: case 3:
if (!oldConfiguration.has(REQUEST_BODY_TEMPLATE)) {
hasChanges = true;
((ObjectNode) oldConfiguration).putNull(REQUEST_BODY_TEMPLATE);
}
Set<String> knownProperties = Set.of( Set<String> knownProperties = Set.of(
"restEndpointUrlPattern", "requestMethod", "headers", "restEndpointUrlPattern", "requestMethod", "headers",
"readTimeoutMs", "maxParallelRequestsCount", "parseToPlainText", "readTimeoutMs", "maxParallelRequestsCount", "parseToPlainText",
"enableProxy", "useSystemProxyProperties", "proxyHost", "proxyPort", "enableProxy", "useSystemProxyProperties", "proxyHost", "proxyPort",
"proxyUser", "proxyPassword", "credentials", "ignoreRequestBody", "proxyUser", "proxyPassword", "credentials", "ignoreRequestBody",
"maxInMemoryBufferSizeInKb" "requestBodyTemplate", "maxInMemoryBufferSizeInKb"
); );
Iterator<String> fieldNames = oldConfiguration.fieldNames(); Iterator<String> fieldNames = oldConfiguration.fieldNames();
while (fieldNames.hasNext()) { while (fieldNames.hasNext()) {

1
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/rest/TbRestApiCallNodeConfiguration.java

@ -51,6 +51,7 @@ public class TbRestApiCallNodeConfiguration implements NodeConfiguration<TbRestA
private String proxyPassword; private String proxyPassword;
private ClientCredentials credentials; private ClientCredentials credentials;
private boolean ignoreRequestBody; private boolean ignoreRequestBody;
private String requestBodyTemplate;
private int maxInMemoryBufferSizeInKb; private int maxInMemoryBufferSizeInKb;
@JsonIgnore @JsonIgnore

4
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/rest/TbHttpClientTest.java

@ -569,8 +569,8 @@ public class TbHttpClientTest {
Map<String, String> data = metaData.getData(); Map<String, String> data = metaData.getData();
Assertions.assertEquals(2, data.size()); Assertions.assertEquals(2, data.size());
Assertions.assertEquals(data.get("Content-Type"), "binary"); Assertions.assertEquals("binary", data.get("Content-Type"));
Assertions.assertEquals(data.get("Set-Cookie"), "[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]"); Assertions.assertEquals("[\"sap-context=sap-client=075; path=/\",\"sap-token=sap-client=075; path=/\"]", data.get("Set-Cookie"));
} }
} }

229
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; package org.thingsboard.rule.engine.rest;
import com.datastax.oss.driver.api.core.uuid.Uuids; 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.config.SocketConfig;
import org.apache.http.impl.bootstrap.HttpServer; import org.apache.http.impl.bootstrap.HttpServer;
import org.apache.http.impl.bootstrap.ServerBootstrap; import org.apache.http.impl.bootstrap.ServerBootstrap;
import org.apache.http.protocol.HttpContext;
import org.apache.http.protocol.HttpRequestHandler; import org.apache.http.protocol.HttpRequestHandler;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.BeforeEach;
@ -52,11 +48,14 @@ import org.thingsboard.server.common.msg.TbMsgMetaData;
import org.thingsboard.server.exception.DataValidationException; import org.thingsboard.server.exception.DataValidationException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.concurrent.CountDownLatch; import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream; import java.util.stream.Stream;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
@ -71,19 +70,17 @@ import static org.mockito.Mockito.verify;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest { public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
private RuleNode ruleNode;
@Spy @Spy
private TbRestApiCallNode restNode; private TbRestApiCallNode restNode;
@Mock @Mock
private TbContext ctx; private TbContext ctx;
private EntityId originator = new DeviceId(Uuids.timeBased()); private final EntityId originator = new DeviceId(Uuids.timeBased());
private TbMsgMetaData metaData = new TbMsgMetaData(); private final TbMsgMetaData metaData = new TbMsgMetaData();
private RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased()); private final RuleChainId ruleChainId = new RuleChainId(Uuids.timeBased());
private RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased()); private final RuleNodeId ruleNodeId = new RuleNodeId(Uuids.timeBased());
private HttpServer server; private HttpServer server;
@ -108,7 +105,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
@BeforeEach @BeforeEach
public void setup() { public void setup() {
ruleNode = new RuleNode(); RuleNode ruleNode = new RuleNode();
ruleNode.setId(ruleNodeId); ruleNode.setId(ruleNodeId);
ruleNode.setName("Test REST API call node"); ruleNode.setName("Test REST API call node");
lenient().when(ctx.getSelf()).thenReturn(ruleNode); lenient().when(ctx.getSelf()).thenReturn(ruleNode);
@ -217,22 +214,17 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
public void deleteRequestWithoutBody() throws IOException, InterruptedException { public void deleteRequestWithoutBody() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
final String path = "/path/to/delete"; final String path = "/path/to/delete";
setupServer("*", new HttpRequestHandler() { setupServer("*", (request, response, _) -> {
try {
@Override assertEquals(path, request.getRequestLine().getUri(), "Request path matches");
public void handle(HttpRequest request, HttpResponse response, HttpContext context) assertTrue(request.containsHeader("Foo"), "Custom header included");
throws HttpException, IOException { assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value");
try { response.setStatusCode(200);
assertEquals(request.getRequestLine().getUri(), path, "Request path matches"); latch.countDown();
assertTrue(request.containsHeader("Foo"), "Custom header included"); } catch (Exception e) {
assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); System.out.println("Exception handling request: " + e);
response.setStatusCode(200); e.printStackTrace();
latch.countDown(); latch.countDown();
} catch (Exception e) {
System.out.println("Exception handling request: " + e.toString());
e.printStackTrace();
latch.countDown();
}
} }
}); });
@ -269,28 +261,23 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
public void deleteRequestWithBody() throws IOException, InterruptedException { public void deleteRequestWithBody() throws IOException, InterruptedException {
final CountDownLatch latch = new CountDownLatch(1); final CountDownLatch latch = new CountDownLatch(1);
final String path = "/path/to/delete"; final String path = "/path/to/delete";
setupServer("*", new HttpRequestHandler() { setupServer("*", (request, response, _) -> {
try {
@Override assertEquals(path, request.getRequestLine().getUri(), "Request path matches");
public void handle(HttpRequest request, HttpResponse response, HttpContext context) assertTrue(request.containsHeader("Content-Type"), "Content-Type included");
throws HttpException, IOException { assertEquals("application/json",
try { request.getFirstHeader("Content-Type").getValue(), "Content-Type value");
assertEquals(path, request.getRequestLine().getUri(), "Request path matches"); assertTrue(request.containsHeader("Content-Length"), "Content-Length included");
assertTrue(request.containsHeader("Content-Type"), "Content-Type included"); assertEquals("2",
assertEquals("application/json", request.getFirstHeader("Content-Length").getValue(), "Content-Length value");
request.getFirstHeader("Content-Type").getValue(), "Content-Type value"); assertTrue(request.containsHeader("Foo"), "Custom header included");
assertTrue(request.containsHeader("Content-Length"), "Content-Length included"); assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value");
assertEquals("2", response.setStatusCode(200);
request.getFirstHeader("Content-Length").getValue(), "Content-Length value"); latch.countDown();
assertTrue(request.containsHeader("Foo"), "Custom header included"); } catch (Exception e) {
assertEquals("Bar", request.getFirstHeader("Foo").getValue(), "Custom header value"); System.out.println("Exception handling request: " + e);
response.setStatusCode(200); e.printStackTrace();
latch.countDown(); latch.countDown();
} catch (Exception e) {
System.out.println("Exception handling request: " + e.toString());
e.printStackTrace();
latch.countDown();
}
} }
}); });
@ -323,6 +310,143 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
assertEquals(TbMsg.EMPTY_JSON_OBJECT, dataCaptor.getValue()); 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<String> 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<String> 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<String> 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<String> 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<TbMsg> msgCaptor = ArgumentCaptor.forClass(TbMsg.class);
ArgumentCaptor<TbMsgMetaData> metadataCaptor = ArgumentCaptor.forClass(TbMsgMetaData.class);
ArgumentCaptor<String> 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<String> 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<Arguments> givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() { private static Stream<Arguments> givenFromVersionAndConfig_whenUpgrade_thenVerifyHasChangesAndConfig() {
return Stream.of( return Stream.of(
Arguments.of(0, Arguments.of(0,
@ -339,7 +463,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
"\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," +
"\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," +
"\"credentials\": {\"type\": \"anonymous\"}," + "\"credentials\": {\"type\": \"anonymous\"}," +
"\"maxInMemoryBufferSizeInKb\": 256}"), "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"),
Arguments.of(1, Arguments.of(1,
"{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," +
"\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," +
@ -355,7 +479,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
"\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," +
"\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," +
"\"credentials\": {\"type\": \"anonymous\"}," + "\"credentials\": {\"type\": \"anonymous\"}," +
"\"maxInMemoryBufferSizeInKb\": 256}"), "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"),
Arguments.of(2, Arguments.of(2,
"{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," + "{\"restEndpointUrlPattern\":\"http://localhost/api\",\"requestMethod\": \"POST\"," +
"\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," + "\"useSimpleClientHttpFactory\": false,\"parseToPlainText\": false,\"ignoreRequestBody\": false," +
@ -370,7 +494,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
"\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," + "\"proxyPort\": 0,\"proxyUser\": null,\"proxyPassword\": null,\"readTimeoutMs\": 0," +
"\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," + "\"maxParallelRequestsCount\": 0,\"headers\": {\"Content-Type\": \"application/json\"}," +
"\"credentials\": {\"type\": \"anonymous\"}," + "\"credentials\": {\"type\": \"anonymous\"}," +
"\"maxInMemoryBufferSizeInKb\": 256}"), "\"maxInMemoryBufferSizeInKb\": 256,\"requestBodyTemplate\": null}"),
Arguments.of(3, """ Arguments.of(3, """
{ {
"restEndpointUrlPattern": "http://localhost/api", "restEndpointUrlPattern": "http://localhost/api",
@ -419,6 +543,7 @@ public class TbRestApiCallNodeTest extends AbstractRuleNodeUpgradeTest {
"credentials": { "credentials": {
"type": "anonymous" "type": "anonymous"
}, },
"requestBodyTemplate": null,
"maxInMemoryBufferSizeInKb": 256 "maxInMemoryBufferSizeInKb": 256
}""") }""")
); );

3
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 TbSendRestApiCallReplyNode node;
private TbSendRestApiCallReplyNodeConfiguration config; private TbSendRestApiCallReplyNodeConfiguration config;
@Mock @Mock
private TbContext ctxMock; private TbContext ctxMock;
@Mock @Mock
@ -140,4 +140,5 @@ public class TbSendRestApiCallReplyNodeTest {
TbMsg.EMPTY_STRING, "Request body is empty!") TbMsg.EMPTY_STRING, "Request body is empty!")
); );
} }
} }

9
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 { MatDialogRef } from '@angular/material/dialog';
import { TbUnit } from '@shared/models/unit.models'; import { TbUnit } from '@shared/models/unit.models';
import { UnitService } from '@core/services/unit.service'; import { UnitService } from '@core/services/unit.service';
import { HttpOptionsResult, HttpUploadOptionsResult, QueryParams, RequestConfig } from '@core/http/http-utils';
export interface TimewindowFunctions { export interface TimewindowFunctions {
onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void; onUpdateTimewindow: (startTimeMs: number, endTimeMs: number, interval?: number) => void;
@ -94,6 +95,14 @@ export interface IWidgetUtils {
getEntityDetailsPageURL: (id: string, entityType: EntityType) => string; 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 { export interface PlaceMapItemActionData {
action: WidgetAction | WidgetActionDescriptor; action: WidgetAction | WidgetActionDescriptor;
additionalParams?: any; additionalParams?: any;

11
ui-ngx/src/app/core/http/http-utils.ts

@ -15,7 +15,7 @@
/// ///
import { InterceptorHttpParams } from '../interceptors/interceptor-http-params'; 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'; import { InterceptorConfig } from '../interceptors/interceptor-config';
export type QueryParams = { [param:string]: any }; export type QueryParams = { [param:string]: any };
@ -27,6 +27,15 @@ export interface RequestConfig {
queryParams?: QueryParams; queryParams?: QueryParams;
} }
export interface HttpOptionsResult {
headers: HttpHeaders;
params: HttpParams;
}
export interface HttpUploadOptionsResult {
params: HttpParams;
}
export function hasRequestConfig(config?: any): boolean { export function hasRequestConfig(config?: any): boolean {
if (!config) { if (!config) {
return false; return false;

5
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<CfAlarm
} }
save(): void { save(): void {
this.dialogRef.close(this.conditionFormGroup.value as AlarmRuleCondition); this.dialogRef.close({
...this.conditionFormGroup.value,
type: this.conditionFormGroup.get('type').value,
} as AlarmRuleCondition);
} }
onTestScript($event: Event) { onTestScript($event: Event) {

7
ui-ngx/src/app/modules/home/components/attribute/add-widget-to-dashboard-dialog.component.html

@ -33,18 +33,19 @@
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
<mat-radio-group formControlName="addToDashboardType"> <mat-radio-group formControlName="addToDashboardType">
<mat-radio-button [value]="0" color="primary"> <mat-radio-button [value]="0" color="primary">
<section class="flex flex-col" style="width: 300px;"> <section class="flex flex-col gap-1" style="width: 300px;">
<span translate>dashboard.select-existing</span> <span translate>dashboard.select-existing</span>
<tb-dashboard-autocomplete formControlName="dashboardId" <tb-dashboard-autocomplete formControlName="dashboardId"
appearance="outline"
[required]="addWidgetFormGroup.get('addToDashboardType').value === 0" [required]="addWidgetFormGroup.get('addToDashboardType').value === 0"
[selectFirstDashboard]="false"> [selectFirstDashboard]="false">
</tb-dashboard-autocomplete> </tb-dashboard-autocomplete>
</section> </section>
</mat-radio-button> </mat-radio-button>
<mat-radio-button [value]="1" color="primary"> <mat-radio-button [value]="1" color="primary">
<section class="flex flex-col" style="width: 300px;"> <section class="flex flex-col gap-1" style="width: 300px;">
<span translate>dashboard.create-new</span> <span translate>dashboard.create-new</span>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>dashboard.new-dashboard-title</mat-label> <mat-label translate>dashboard.new-dashboard-title</mat-label>
<input matInput formControlName="newDashboardTitle" <input matInput formControlName="newDashboardTitle"
[required]="addWidgetFormGroup.get('addToDashboardType').value === 1"> [required]="addWidgetFormGroup.get('addToDashboardType').value === 1">

6
ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.html

@ -25,7 +25,7 @@
</button> </button>
</mat-toolbar> </mat-toolbar>
<form [formGroup]="deleteTimeseriesFormGroup" class="mat-content mat-padding tb-form-settings"> <form [formGroup]="deleteTimeseriesFormGroup" class="mat-content mat-padding tb-form-settings">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>attribute.delete-timeseries.strategy</mat-label> <mat-label translate>attribute.delete-timeseries.strategy</mat-label>
<mat-select formControlName="strategy"> <mat-select formControlName="strategy">
@for (strategy of strategiesTranslationsMap.keys(); track strategy) { @for (strategy of strategiesTranslationsMap.keys(); track strategy) {
@ -37,13 +37,13 @@
</mat-form-field> </mat-form-field>
@if (isPeriodStrategy()) { @if (isPeriodStrategy()) {
<div class="tb-select-interval"> <div class="tb-select-interval">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>attribute.delete-timeseries.start-time</mat-label> <mat-label translate>attribute.delete-timeseries.start-time</mat-label>
<mat-datetimepicker-toggle [for]="startDateTimePicker" matPrefix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="startDateTimePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #startDateTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker> <mat-datetimepicker #startDateTimePicker type="datetime" openOnFocus="true"></mat-datetimepicker>
<input required matInput formControlName="startDateTime" [matDatetimepicker]="startDateTimePicker"> <input required matInput formControlName="startDateTime" [matDatetimepicker]="startDateTimePicker">
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>attribute.delete-timeseries.ends-on</mat-label> <mat-label translate>attribute.delete-timeseries.ends-on</mat-label>
<mat-datetimepicker-toggle [for]="endDatePicker" matPrefix></mat-datetimepicker-toggle> <mat-datetimepicker-toggle [for]="endDatePicker" matPrefix></mat-datetimepicker-toggle>
<mat-datetimepicker #endDatePicker type="datetime" openOnFocus="true"></mat-datetimepicker> <mat-datetimepicker #endDatePicker type="datetime" openOnFocus="true"></mat-datetimepicker>

2
ui-ngx/src/app/modules/home/components/attribute/delete-timeseries-panel.component.scss

@ -26,7 +26,7 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 16px;
padding-top: 0; padding-top: 4px;
} }
.tb-select-interval { .tb-select-interval {

4
ui-ngx/src/app/modules/home/components/dashboard-page/states/dashboard-state-dialog.component.html

@ -31,7 +31,7 @@
} }
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async" class="flex flex-col"> <fieldset [disabled]="isLoading$ | async" class="flex flex-col">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>dashboard.state-name</mat-label> <mat-label translate>dashboard.state-name</mat-label>
<input required matInput formControlName="name"> <input required matInput formControlName="name">
@if (stateFormGroup.get('name').hasError('required')) { @if (stateFormGroup.get('name').hasError('required')) {
@ -40,7 +40,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>dashboard.state-id</mat-label> <mat-label translate>dashboard.state-id</mat-label>
<input required matInput formControlName="id"> <input required matInput formControlName="id">
@if (stateFormGroup.get('id').hasError('required')) { @if (stateFormGroup.get('id').hasError('required')) {

2
ui-ngx/src/app/modules/home/components/device/device-credentials.component.html

@ -62,7 +62,7 @@
@case (deviceCredentialsType.X509_CERTIFICATE) { @case (deviceCredentialsType.X509_CERTIFICATE) {
<mat-form-field class="mat-block" appearance="outline"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>device.certificate-pem-format</mat-label> <mat-label translate>device.certificate-pem-format</mat-label>
<textarea matInput formControlName="credentialsValue" cols="15" rows="5" required></textarea> <textarea matInput formControlName="credentialsValue" cdkTextareaAutosize rows="15" required></textarea>
@if (deviceCredentialsFormGroup.get('credentialsValue').value) { @if (deviceCredentialsFormGroup.get('credentialsValue').value) {
<tb-copy-button <tb-copy-button
matSuffix matSuffix

8
ui-ngx/src/app/modules/home/components/event/event-filter-panel.component.html

@ -20,7 +20,7 @@
@for (column of showColumns; track column) { @for (column of showColumns; track column) {
@switch (column.key) { @switch (column.key) {
@case (isSelector(column.key)) { @case (isSelector(column.key)) {
<mat-form-field> <mat-form-field appearance="outline">
<mat-label>{{ column.title | translate}}</mat-label> <mat-label>{{ column.title | translate}}</mat-label>
<mat-select [formControlName]="column.key"> <mat-select [formControlName]="column.key">
<mat-option [value]="">{{ 'event.all-events' | translate}}</mat-option> <mat-option [value]="">{{ 'event.all-events' | translate}}</mat-option>
@ -33,7 +33,7 @@
</mat-form-field> </mat-form-field>
} }
@case (isNumberFields(column.key)) { @case (isNumberFields(column.key)) {
<mat-form-field> <mat-form-field appearance="outline">
<mat-label>{{ column.title | translate}}</mat-label> <mat-label>{{ column.title | translate}}</mat-label>
<input matInput type="number" min="0" [name]="column.key" [formControlName]="column.key"> <input matInput type="number" min="0" [name]="column.key" [formControlName]="column.key">
@if (eventFilterFormGroup.get(column.key).hasError('min')) { @if (eventFilterFormGroup.get(column.key).hasError('min')) {
@ -50,13 +50,13 @@
</tb-checkbox> </tb-checkbox>
} }
@case ('errorStr') { @case ('errorStr') {
<mat-form-field [class.!hidden]="!showErrorMsgFields()"> <mat-form-field [class.!hidden]="!showErrorMsgFields()" appearance="outline">
<mat-label>{{ column.title | translate}}</mat-label> <mat-label>{{ column.title | translate}}</mat-label>
<input matInput type="text" name="errorSearchText" formControlName="errorStr"> <input matInput type="text" name="errorSearchText" formControlName="errorStr">
</mat-form-field> </mat-form-field>
} }
@default { @default {
<mat-form-field> <mat-form-field appearance="outline">
<mat-label>{{ column.title | translate}}</mat-label> <mat-label>{{ column.title | translate}}</mat-label>
<input matInput type="text" [name]="column.key" [formControlName]="column.key"> <input matInput type="text" [name]="column.key" [formControlName]="column.key">
</mat-form-field> </mat-form-field>

2
ui-ngx/src/app/modules/home/components/event/event-table-header.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>event.event-type</mat-label> <mat-label translate>event.event-type</mat-label>
<mat-select [ngModel]="eventTableConfig.eventType" <mat-select [ngModel]="eventTableConfig.eventType"
(ngModelChange)="eventTypeChanged($event)"> (ngModelChange)="eventTypeChanged($event)">

9
ui-ngx/src/app/modules/home/components/profile/device/snmp/snmp-device-profile-mapping.component.html

@ -26,10 +26,9 @@
</div> </div>
<mat-divider></mat-divider> <mat-divider></mat-divider>
@for (mappingConfig of mappingsConfigFormArray.controls; track mappingConfig; let isLast = $last) { @for (mappingConfig of mappingsConfigFormArray.controls; track mappingConfig; let isLast = $last) {
<div <div [formGroup]="mappingConfig" class="mapping-list flex flex-row items-center justify-start gap-2">
[formGroup]="mappingConfig" class="mapping-list flex flex-row items-center justify-start gap-2">
<div class="tb-layout-fill flex flex-1 flex-row items-stretch justify-start gap-2"> <div class="tb-layout-fill flex flex-1 flex-row items-stretch justify-start gap-2">
<mat-form-field class="max-w-26% flex-full"> <mat-form-field class="max-w-26% flex-full" appearance="outline">
<mat-select formControlName="dataType" required> <mat-select formControlName="dataType" required>
@for (dataType of dataTypes; track dataType) { @for (dataType of dataTypes; track dataType) {
<mat-option [value]="dataType"> <mat-option [value]="dataType">
@ -43,7 +42,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="max-w-37% flex-full"> <mat-form-field class="max-w-37% flex-full" appearance="outline">
<input matInput formControlName="key" required/> <input matInput formControlName="key" required/>
@if (mappingConfig.get('key').hasError('required')) { @if (mappingConfig.get('key').hasError('required')) {
<mat-error> <mat-error>
@ -51,7 +50,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="max-w-37% flex-full"> <mat-form-field class="max-w-37% flex-full" appearance="outline">
<input matInput formControlName="oid" required/> <input matInput formControlName="oid" required/>
@if (mappingConfig.get('oid').hasError('required')) { @if (mappingConfig.get('oid').hasError('required')) {
<mat-error> <mat-error>

2
ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field [formGroup]="selectTenantProfileFormGroup" class="mat-block"> <mat-form-field [formGroup]="selectTenantProfileFormGroup" class="mat-block" [appearance]="appearance">
<mat-label translate>tenant-profile.tenant-profile</mat-label> <mat-label translate>tenant-profile.tenant-profile</mat-label>
<input matInput type="text" <input matInput type="text"
#tenantProfileInput #tenantProfileInput

4
ui-ngx/src/app/modules/home/components/profile/tenant-profile-autocomplete.component.ts

@ -35,6 +35,7 @@ import { MatDialog } from '@angular/material/dialog';
import { TenantProfileDialogComponent, TenantProfileDialogData } from './tenant-profile-dialog.component'; import { TenantProfileDialogComponent, TenantProfileDialogData } from './tenant-profile-dialog.component';
import { emptyPageData } from '@shared/models/page/page-data'; import { emptyPageData } from '@shared/models/page/page-data';
import { getEntityDetailsPageURL } from '@core/utils'; import { getEntityDetailsPageURL } from '@core/utils';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({ @Component({
selector: 'tb-tenant-profile-autocomplete', selector: 'tb-tenant-profile-autocomplete',
@ -71,6 +72,9 @@ export class TenantProfileAutocompleteComponent implements ControlValueAccessor,
@Input() @Input()
showDetailsPageLink = false; showDetailsPageLink = false;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Output() @Output()
tenantProfileUpdated = new EventEmitter<TenantProfileId>(); tenantProfileUpdated = new EventEmitter<TenantProfileId>();

6
ui-ngx/src/app/modules/home/components/profile/tenant-profile.component.html

@ -50,7 +50,7 @@
<div class="mat-padding flex flex-col"> <div class="mat-padding flex flex-col">
<form [formGroup]="entityForm"> <form [formGroup]="entityForm">
<fieldset [disabled]="(isLoading$ | async) || !isEdit"> <fieldset [disabled]="(isLoading$ | async) || !isEdit">
<mat-form-field class="mat-block" appearance="fill"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>tenant-profile.name</mat-label> <mat-label translate>tenant-profile.name</mat-label>
<input matInput formControlName="name" required/> <input matInput formControlName="name" required/>
@if (entityForm.get('name').hasError('required')) { @if (entityForm.get('name').hasError('required')) {
@ -103,9 +103,9 @@
</mat-expansion-panel> </mat-expansion-panel>
</mat-accordion> </mat-accordion>
</div> </div>
<mat-form-field class="mat-block" appearance="fill"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>tenant-profile.description</mat-label> <mat-label translate>tenant-profile.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea> <textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
</mat-form-field> </mat-form-field>
</fieldset> </fieldset>
</form> </form>

96
ui-ngx/src/app/modules/home/components/profile/tenant/default-tenant-profile-configuration.component.html

@ -21,7 +21,7 @@
{{ 'tenant-profile.entities' | translate }} <span translate>tenant-profile.unlimited</span> {{ 'tenant-profile.entities' | translate }} <span translate>tenant-profile.unlimited</span>
</legend> </legend>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-devices</mat-label> <mat-label translate>tenant-profile.maximum-devices</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxDevices" formControlName="maxDevices"
@ -38,7 +38,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-dashboards</mat-label> <mat-label translate>tenant-profile.maximum-dashboards</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxDashboards" formControlName="maxDashboards"
@ -57,7 +57,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-assets</mat-label> <mat-label translate>tenant-profile.maximum-assets</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxAssets" formControlName="maxAssets"
@ -74,7 +74,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-users</mat-label> <mat-label translate>tenant-profile.maximum-users</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxUsers" formControlName="maxUsers"
@ -100,7 +100,7 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-customers</mat-label> <mat-label translate>tenant-profile.maximum-customers</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxCustomers" formControlName="maxCustomers"
@ -117,7 +117,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-rule-chains</mat-label> <mat-label translate>tenant-profile.maximum-rule-chains</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxRuleChains" formControlName="maxRuleChains"
@ -136,7 +136,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.maximum-edges</mat-label> <mat-label translate>tenant-profile.maximum-edges</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxEdges" formControlName="maxEdges"
@ -164,7 +164,7 @@
{{ 'tenant-profile.rule-engine' | translate }} <span translate>tenant-profile.unlimited</span> {{ 'tenant-profile.rule-engine' | translate }} <span translate>tenant-profile.unlimited</span>
</legend> </legend>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-r-e-executions</mat-label> <mat-label translate>tenant-profile.max-r-e-executions</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxREExecutions" formControlName="maxREExecutions"
@ -181,7 +181,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-transport-messages</mat-label> <mat-label translate>tenant-profile.max-transport-messages</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxTransportMessages" formControlName="maxTransportMessages"
@ -207,7 +207,7 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-j-s-executions</mat-label> <mat-label translate>tenant-profile.max-j-s-executions</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxJSExecutions" formControlName="maxJSExecutions"
@ -224,7 +224,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-tbel-executions</mat-label> <mat-label translate>tenant-profile.max-tbel-executions</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxTbelExecutions" formControlName="maxTbelExecutions"
@ -243,7 +243,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-rule-node-executions-per-message</mat-label> <mat-label translate>tenant-profile.max-rule-node-executions-per-message</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxRuleNodeExecutionsPerMessage" formControlName="maxRuleNodeExecutionsPerMessage"
@ -260,7 +260,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-transport-data-points</mat-label> <mat-label translate>tenant-profile.max-transport-data-points</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxTransportDataPoints" formControlName="maxTransportDataPoints"
@ -286,7 +286,7 @@
{{ 'tenant-profile.calculated-fields' | translate }} <span translate>tenant-profile.unlimited</span> {{ 'tenant-profile.calculated-fields' | translate }} <span translate>tenant-profile.unlimited</span>
</legend> </legend>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-calculated-fields</mat-label> <mat-label translate>tenant-profile.max-calculated-fields</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxCalculatedFieldsPerEntity" formControlName="maxCalculatedFieldsPerEntity"
@ -303,7 +303,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-data-points-per-rolling-arg</mat-label> <mat-label translate>tenant-profile.max-data-points-per-rolling-arg</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxDataPointsPerRollingArg" formControlName="maxDataPointsPerRollingArg"
@ -322,7 +322,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-arguments-per-cf</mat-label> <mat-label translate>tenant-profile.max-arguments-per-cf</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxArgumentsPerCF" formControlName="maxArgumentsPerCF"
@ -349,7 +349,7 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-state-size</mat-label> <mat-label translate>tenant-profile.max-state-size</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxStateSizeInKBytes" formControlName="maxStateSizeInKBytes"
@ -366,7 +366,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-value-argument-size</mat-label> <mat-label translate>tenant-profile.max-value-argument-size</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxSingleValueArgumentSizeInKBytes" formControlName="maxSingleValueArgumentSizeInKBytes"
@ -385,7 +385,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-related-level-per-argument</mat-label> <mat-label translate>tenant-profile.max-related-level-per-argument</mat-label>
<input matInput required min="1" step="1" <input matInput required min="1" step="1"
formControlName="maxRelationLevelPerCfArgument" formControlName="maxRelationLevelPerCfArgument"
@ -402,7 +402,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.min-allowed-scheduled-update-interval</mat-label> <mat-label translate>tenant-profile.min-allowed-scheduled-update-interval</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="minAllowedScheduledUpdateIntervalInSecForCF" formControlName="minAllowedScheduledUpdateIntervalInSecForCF"
@ -421,7 +421,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.min-allowed-aggregation-interval</mat-label> <mat-label translate>tenant-profile.min-allowed-aggregation-interval</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="minAllowedAggregationIntervalInSecForCF" formControlName="minAllowedAggregationIntervalInSecForCF"
@ -438,7 +438,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.min-allowed-deduplication-interval</mat-label> <mat-label translate>tenant-profile.min-allowed-deduplication-interval</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="minAllowedDeduplicationIntervalInSecForCF" formControlName="minAllowedDeduplicationIntervalInSecForCF"
@ -457,7 +457,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.intermediate-aggregation-interval</mat-label> <mat-label translate>tenant-profile.intermediate-aggregation-interval</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="intermediateAggregationIntervalInSecForCF" formControlName="intermediateAggregationIntervalInSecForCF"
@ -474,7 +474,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.reevaluation-check-interval</mat-label> <mat-label translate>tenant-profile.reevaluation-check-interval</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="cfReevaluationCheckInterval" formControlName="cfReevaluationCheckInterval"
@ -493,7 +493,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.relation-search-entity-limit</mat-label> <mat-label translate>tenant-profile.relation-search-entity-limit</mat-label>
<input matInput required min="1" step="1" <input matInput required min="1" step="1"
formControlName="maxRelatedEntitiesToReturnPerCfArgument" formControlName="maxRelatedEntitiesToReturnPerCfArgument"
@ -521,7 +521,7 @@
{{ 'tenant-profile.time-to-live' | translate }} <span translate>tenant-profile.unlimited</span> {{ 'tenant-profile.time-to-live' | translate }} <span translate>tenant-profile.unlimited</span>
</legend> </legend>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-d-p-storage-days</mat-label> <mat-label translate>tenant-profile.max-d-p-storage-days</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxDPStorageDays" formControlName="maxDPStorageDays"
@ -538,7 +538,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.alarms-ttl-days</mat-label> <mat-label translate>tenant-profile.alarms-ttl-days</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="alarmsTtlDays" formControlName="alarmsTtlDays"
@ -557,7 +557,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.default-storage-ttl-days</mat-label> <mat-label translate>tenant-profile.default-storage-ttl-days</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="defaultStorageTtlDays" formControlName="defaultStorageTtlDays"
@ -574,7 +574,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.rpc-ttl-days</mat-label> <mat-label translate>tenant-profile.rpc-ttl-days</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="rpcTtlDays" formControlName="rpcTtlDays"
@ -593,7 +593,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.queue-stats-ttl-days</mat-label> <mat-label translate>tenant-profile.queue-stats-ttl-days</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="queueStatsTtlDays" formControlName="queueStatsTtlDays"
@ -610,7 +610,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.rule-engine-exceptions-ttl-days</mat-label> <mat-label translate>tenant-profile.rule-engine-exceptions-ttl-days</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="ruleEngineExceptionsTtlDays" formControlName="ruleEngineExceptionsTtlDays"
@ -639,7 +639,7 @@
</mat-slide-toggle> </mat-slide-toggle>
@if (tenantProfileConfigurationForm.get('smsEnabled').value) { @if (tenantProfileConfigurationForm.get('smsEnabled').value) {
<mat-form-field class="mat-block flex-1" <mat-form-field class="mat-block flex-1"
appearance="fill" subscriptSizing="dynamic"> appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-sms</mat-label> <mat-label translate>tenant-profile.max-sms</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxSms" formControlName="maxSms"
@ -657,7 +657,7 @@
</mat-form-field> </mat-form-field>
} }
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-emails</mat-label> <mat-label translate>tenant-profile.max-emails</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxEmails" formControlName="maxEmails"
@ -674,7 +674,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.max-created-alarms</mat-label> <mat-label translate>tenant-profile.max-created-alarms</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxCreatedAlarms" formControlName="maxCreatedAlarms"
@ -693,7 +693,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.alarms-reevaluation-interval</mat-label> <mat-label translate>tenant-profile.alarms-reevaluation-interval</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="alarmsReevaluationInterval" formControlName="alarmsReevaluationInterval"
@ -719,7 +719,7 @@
{{ 'tenant-profile.debug' | translate }} {{ 'tenant-profile.debug' | translate }}
</legend> </legend>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-debug-duration-min</mat-label> <mat-label translate>tenant-profile.maximum-debug-duration-min</mat-label>
<input matInput min="0" step="1" <input matInput min="0" step="1"
formControlName="maxDebugModeDurationMinutes" formControlName="maxDebugModeDurationMinutes"
@ -740,7 +740,7 @@
{{ 'tenant-profile.ota-files-in-bytes' | translate }} <span translate>tenant-profile.unlimited</span> {{ 'tenant-profile.ota-files-in-bytes' | translate }} <span translate>tenant-profile.unlimited</span>
</legend> </legend>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-resources-sum-data-size</mat-label> <mat-label translate>tenant-profile.maximum-resources-sum-data-size</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxResourcesInBytes" formControlName="maxResourcesInBytes"
@ -757,7 +757,7 @@
} }
<mat-hint></mat-hint> <mat-hint></mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill" subscriptSizing="dynamic"> <mat-form-field class="mat-block flex-1" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.maximum-ota-packages-sum-data-size</mat-label> <mat-label translate>tenant-profile.maximum-ota-packages-sum-data-size</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxOtaPackagesInBytes" formControlName="maxOtaPackagesInBytes"
@ -776,7 +776,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.maximum-resource-size</mat-label> <mat-label translate>tenant-profile.maximum-resource-size</mat-label>
<input matInput required min="0" step="1" <input matInput required min="0" step="1"
formControlName="maxResourceSize" formControlName="maxResourceSize"
@ -802,7 +802,7 @@
{{ 'tenant-profile.ws-title' | translate }} <span translate>tenant-profile.unlimited</span> {{ 'tenant-profile.ws-title' | translate }} <span translate>tenant-profile.unlimited</span>
</legend> </legend>
<div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="fields-element flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-tenant</mat-label> <mat-label translate>tenant-profile.ws-limit-max-sessions-per-tenant</mat-label>
<input matInput type="number" formControlName="maxWsSessionsPerTenant"> <input matInput type="number" formControlName="maxWsSessionsPerTenant">
@if (tenantProfileConfigurationForm.get('maxWsSessionsPerTenant').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSessionsPerTenant').hasError('min')) {
@ -811,7 +811,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-tenant</mat-label> <mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-tenant</mat-label>
<input matInput type="number" formControlName="maxWsSubscriptionsPerTenant"> <input matInput type="number" formControlName="maxWsSubscriptionsPerTenant">
@if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerTenant').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerTenant').hasError('min')) {
@ -822,7 +822,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-customer</mat-label> <mat-label translate>tenant-profile.ws-limit-max-sessions-per-customer</mat-label>
<input matInput type="number" formControlName="maxWsSessionsPerCustomer"> <input matInput type="number" formControlName="maxWsSessionsPerCustomer">
@if (tenantProfileConfigurationForm.get('maxWsSessionsPerCustomer').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSessionsPerCustomer').hasError('min')) {
@ -831,7 +831,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-customer</mat-label> <mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-customer</mat-label>
<input matInput type="number" formControlName="maxWsSubscriptionsPerCustomer"> <input matInput type="number" formControlName="maxWsSubscriptionsPerCustomer">
@if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerCustomer').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerCustomer').hasError('min')) {
@ -849,7 +849,7 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-public-user</mat-label> <mat-label translate>tenant-profile.ws-limit-max-sessions-per-public-user</mat-label>
<input matInput type="number" formControlName="maxWsSessionsPerPublicUser"> <input matInput type="number" formControlName="maxWsSessionsPerPublicUser">
@if (tenantProfileConfigurationForm.get('maxWsSessionsPerPublicUser').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSessionsPerPublicUser').hasError('min')) {
@ -858,7 +858,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-public-user</mat-label> <mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-public-user</mat-label>
<input matInput type="number" formControlName="maxWsSubscriptionsPerPublicUser"> <input matInput type="number" formControlName="maxWsSubscriptionsPerPublicUser">
@if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerPublicUser').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerPublicUser').hasError('min')) {
@ -869,7 +869,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-sessions-per-regular-user</mat-label> <mat-label translate>tenant-profile.ws-limit-max-sessions-per-regular-user</mat-label>
<input matInput type="number" formControlName="maxWsSessionsPerRegularUser"> <input matInput type="number" formControlName="maxWsSessionsPerRegularUser">
@if (tenantProfileConfigurationForm.get('maxWsSessionsPerRegularUser').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSessionsPerRegularUser').hasError('min')) {
@ -878,7 +878,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-regular-user</mat-label> <mat-label translate>tenant-profile.ws-limit-max-subscriptions-per-regular-user</mat-label>
<input matInput type="number" formControlName="maxWsSubscriptionsPerRegularUser"> <input matInput type="number" formControlName="maxWsSubscriptionsPerRegularUser">
@if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerRegularUser').hasError('min')) { @if (tenantProfileConfigurationForm.get('maxWsSubscriptionsPerRegularUser').hasError('min')) {
@ -889,7 +889,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
<mat-form-field class="mat-block flex-1" appearance="fill"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>tenant-profile.ws-limit-queue-per-session</mat-label> <mat-label translate>tenant-profile.ws-limit-queue-per-session</mat-label>
<input matInput type="number" formControlName="wsMsgQueueLimitPerSession"> <input matInput type="number" formControlName="wsMsgQueueLimitPerSession">
@if (tenantProfileConfigurationForm.get('wsMsgQueueLimitPerSession').hasError('min')) { @if (tenantProfileConfigurationForm.get('wsMsgQueueLimitPerSession').hasError('min')) {

4
ui-ngx/src/app/modules/home/components/profile/tenant/rate-limits/rate-limits-list.component.html

@ -22,7 +22,7 @@
<div class="tb-rate-limits-operation" translate>tenant-profile.rate-limits.and-also-less-than</div> <div class="tb-rate-limits-operation" translate>tenant-profile.rate-limits.and-also-less-than</div>
} }
<div class="flex flex-1 flex-row gap-2"> <div class="flex flex-1 flex-row gap-2">
<mat-form-field hideRequiredMarker appearance="fill" class="mat-block flex-1" subscriptSizing="dynamic"> <mat-form-field hideRequiredMarker appearance="outline" class="mat-block flex-1" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.rate-limits.number-of-messages</mat-label> <mat-label translate>tenant-profile.rate-limits.number-of-messages</mat-label>
<input matInput placeholder="{{ 'tenant-profile.rate-limits.number-of-messages' | translate }}" <input matInput placeholder="{{ 'tenant-profile.rate-limits.number-of-messages' | translate }}"
type="number" min="1" step="1" formControlName="value" required> type="number" min="1" step="1" formControlName="value" required>
@ -33,7 +33,7 @@
<mat-error translate>tenant-profile.rate-limits.number-of-messages-min</mat-error> <mat-error translate>tenant-profile.rate-limits.number-of-messages-min</mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field hideRequiredMarker appearance="fill" class="mat-block flex-1" subscriptSizing="dynamic"> <mat-form-field hideRequiredMarker appearance="outline" class="mat-block flex-1" subscriptSizing="dynamic">
<mat-label translate>tenant-profile.rate-limits.per-seconds</mat-label> <mat-label translate>tenant-profile.rate-limits.per-seconds</mat-label>
<input matInput placeholder="{{ 'tenant-profile.rate-limits.per-seconds' | translate }}" <input matInput placeholder="{{ 'tenant-profile.rate-limits.per-seconds' | translate }}"
type="number" min="1" step="1" formControlName="time" required> type="number" min="1" step="1" formControlName="time" required>

26
ui-ngx/src/app/modules/home/components/queue/queue-form.component.html

@ -16,7 +16,7 @@
--> -->
<form [formGroup]="queueFormGroup" class="flex flex-col"> <form [formGroup]="queueFormGroup" class="flex flex-col">
<mat-form-field class="mat-block" appearance="fill"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>admin.queue-name</mat-label> <mat-label translate>admin.queue-name</mat-label>
<input matInput formControlName="name" required> <input matInput formControlName="name" required>
@if (queueFormGroup.get('name').hasError('required')) { @if (queueFormGroup.get('name').hasError('required')) {
@ -62,7 +62,7 @@
@if (hideBatchSize) { @if (hideBatchSize) {
<div class="flex-1"> <div class="flex-1">
<label class="group-label" translate>queue.grouping-parameter</label> <label class="group-label" translate>queue.grouping-parameter</label>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>queue.batch-size</mat-label> <mat-label translate>queue.batch-size</mat-label>
<input type="number" matInput formControlName="batchSize" required> <input type="number" matInput formControlName="batchSize" required>
@if (queueFormGroup.get('submitStrategy.batchSize').hasError('required')) { @if (queueFormGroup.get('submitStrategy.batchSize').hasError('required')) {
@ -107,7 +107,7 @@
</div> </div>
<div class="flex-1"> <div class="flex-1">
<label class="group-label" translate>queue.retries-settings</label> <label class="group-label" translate>queue.retries-settings</label>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>queue.retries</mat-label> <mat-label translate>queue.retries</mat-label>
<input type="number" matInput formControlName="retries" required> <input type="number" matInput formControlName="retries" required>
<mat-hint></mat-hint> <mat-hint></mat-hint>
@ -123,7 +123,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>queue.failure-percentage</mat-label> <mat-label translate>queue.failure-percentage</mat-label>
<input type="number" matInput formControlName="failurePercentage" required> <input type="number" matInput formControlName="failurePercentage" required>
<mat-hint></mat-hint> <mat-hint></mat-hint>
@ -147,7 +147,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>queue.pause-between-retries</mat-label> <mat-label translate>queue.pause-between-retries</mat-label>
<input type="number" matInput formControlName="pauseBetweenRetries" required> <input type="number" matInput formControlName="pauseBetweenRetries" required>
<mat-hint></mat-hint> <mat-hint></mat-hint>
@ -163,7 +163,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>queue.max-pause-between-retries</mat-label> <mat-label translate>queue.max-pause-between-retries</mat-label>
<input type="number" matInput formControlName="maxPauseBetweenRetries" required> <input type="number" matInput formControlName="maxPauseBetweenRetries" required>
<mat-hint></mat-hint> <mat-hint></mat-hint>
@ -194,7 +194,7 @@
<div class="flex-1"> <div class="flex-1">
<label class="group-label" translate>queue.batch-processing</label> <label class="group-label" translate>queue.batch-processing</label>
<div class="flex flex-1 flex-row gap-2.5 xs:flex-col"> <div class="flex flex-1 flex-row gap-2.5 xs:flex-col">
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>queue.poll-interval</mat-label> <mat-label translate>queue.poll-interval</mat-label>
<input type="number" matInput formControlName="pollInterval" required> <input type="number" matInput formControlName="pollInterval" required>
@if (queueFormGroup.get('pollInterval').hasError('required')) { @if (queueFormGroup.get('pollInterval').hasError('required')) {
@ -209,7 +209,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>queue.partitions</mat-label> <mat-label translate>queue.partitions</mat-label>
<input type="number" matInput formControlName="partitions" required> <input type="number" matInput formControlName="partitions" required>
@if (queueFormGroup.get('partitions').hasError('required')) { @if (queueFormGroup.get('partitions').hasError('required')) {
@ -232,7 +232,7 @@
<mat-checkbox class="flex-1 gt-xs:flex gt-xs:items-stretch gt-xs:justify-center" formControlName="consumerPerPartition"> <mat-checkbox class="flex-1 gt-xs:flex gt-xs:items-stretch gt-xs:justify-center" formControlName="consumerPerPartition">
{{ 'queue.consumer-per-partition' | translate }} {{ 'queue.consumer-per-partition' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>queue.processing-timeout</mat-label> <mat-label translate>queue.processing-timeout</mat-label>
<input type="number" matInput formControlName="packProcessingTimeout" required> <input type="number" matInput formControlName="packProcessingTimeout" required>
@if (queueFormGroup.get('packProcessingTimeout').hasError('required')) { @if (queueFormGroup.get('packProcessingTimeout').hasError('required')) {
@ -257,14 +257,14 @@
<mat-checkbox class="flex-1" formControlName="duplicateMsgToAllPartitions" style="padding-bottom: 16px;"> <mat-checkbox class="flex-1" formControlName="duplicateMsgToAllPartitions" style="padding-bottom: 16px;">
{{ 'queue.duplicate-msg-to-all-partitions' | translate }} {{ 'queue.duplicate-msg-to-all-partitions' | translate }}
</mat-checkbox> </mat-checkbox>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>queue.custom-properties</mat-label> <mat-label translate>queue.custom-properties</mat-label>
<textarea matInput formControlName="customProperties" cdkTextareaAutosize cdkAutosizeMinRows="1"></textarea> <textarea matInput formControlName="customProperties" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
<mat-hint translate>queue.custom-properties-hint</mat-hint> <mat-hint translate>queue.custom-properties-hint</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block" appearance="fill"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>queue.description</mat-label> <mat-label translate>queue.description</mat-label>
<textarea matInput formControlName="description" rows="2"></textarea> <textarea matInput formControlName="description" cdkTextareaAutosize cdkAutosizeMinRows="1" rows="1"></textarea>
<mat-hint translate>queue.description-hint</mat-hint> <mat-hint translate>queue.description-hint</mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>

4
ui-ngx/src/app/modules/home/components/relation/relation-dialog.component.html

@ -34,14 +34,16 @@
<div mat-dialog-content> <div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async"> <fieldset [disabled]="isLoading$ | async">
<tb-relation-type-autocomplete <tb-relation-type-autocomplete
appearance="outline"
formControlName="type" formControlName="type"
required="true"> [required]="true">
</tb-relation-type-autocomplete> </tb-relation-type-autocomplete>
<small>{{(direction === entitySearchDirection.FROM ? <small>{{(direction === entitySearchDirection.FROM ?
'relation.to-entity' : 'relation.from-entity') | translate}}</small> 'relation.to-entity' : 'relation.from-entity') | translate}}</small>
<tb-entity-list-select <tb-entity-list-select
formControlName="targetEntityIds" formControlName="targetEntityIds"
[additionEntityTypes]="additionEntityTypes" [additionEntityTypes]="additionEntityTypes"
appearance="outline"
required="true"> required="true">
</tb-entity-list-select> </tb-entity-list-select>
<tb-json-object-edit <tb-json-object-edit

4
ui-ngx/src/app/modules/home/components/relation/relation-table.component.html

@ -19,11 +19,11 @@
<div class="tb-entity-table-content tb-outlined-border flex flex-1 flex-col"> <div class="tb-entity-table-content tb-outlined-border flex flex-1 flex-col">
<mat-toolbar class="mat-mdc-table-toolbar" [class.!hidden]="textSearchMode || !dataSource.selection.isEmpty()"> <mat-toolbar class="mat-mdc-table-toolbar" [class.!hidden]="textSearchMode || !dataSource.selection.isEmpty()">
<div class="mat-toolbar-tools"> <div class="mat-toolbar-tools">
<div class="title-container flex flex-row items-center justify-start xs:flex-col xs:items-start xs:justify-center"> <div class="title-container flex flex-row items-center justify-start xs:flex-col xs:items-start xs:justify-center pt-1">
<span class="tb-entity-table-title"> <span class="tb-entity-table-title">
{{(direction == directions.FROM ? 'relation.from-relations' : 'relation.to-relations') | translate}} {{(direction == directions.FROM ? 'relation.from-relations' : 'relation.to-relations') | translate}}
</span> </span>
<mat-form-field class="mat-block tb-relation-direction"> <mat-form-field class="mat-block tb-relation-direction" appearance="outline" subscriptSizing="dynamic">
<mat-label translate>relation.direction</mat-label> <mat-label translate>relation.direction</mat-label>
<mat-select [ngModel]="direction" <mat-select [ngModel]="direction"
(ngModelChange)="directionChanged($event)"> (ngModelChange)="directionChanged($event)">

3
ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting-row.component.html

@ -17,7 +17,7 @@
--> -->
<section [formGroup]="processingSettingRowForm" class="tb-form-panel stroked no-gap no-padding-bottom"> <section [formGroup]="processingSettingRowForm" class="tb-form-panel stroked no-gap no-padding-bottom">
<div class="tb-form-panel-title mb-4">{{ title }}</div> <div class="tb-form-panel-title mb-4">{{ title }}</div>
<mat-form-field> <mat-form-field [appearance]="appearance">
<mat-label translate>rule-node-config.save-time-series.strategy</mat-label> <mat-label translate>rule-node-config.save-time-series.strategy</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="type">
@for (strategy of processingStrategies; track strategy) { @for (strategy of processingStrategies; track strategy) {
@ -34,6 +34,7 @@
maxErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}" maxErrorText="{{ 'rule-node-config.save-time-series.deduplication-interval-min-max-range' | translate }}"
[minTime]="1" [minTime]="1"
[maxTime]="maxDeduplicateTime" [maxTime]="maxDeduplicateTime"
[appearance]="appearance"
formControlName="deduplicationIntervalSecs"> formControlName="deduplicationIntervalSecs">
</tb-time-unit-input> </tb-time-unit-input>
} }

4
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'; } from '@home/components/rule-node/action/timeseries-config.models';
import { isDefinedAndNotNull } from '@core/utils'; import { isDefinedAndNotNull } from '@core/utils';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({ @Component({
selector: 'tb-advanced-processing-setting-row', selector: 'tb-advanced-processing-setting-row',
@ -52,6 +53,9 @@ export class AdvancedProcessingSettingRowComponent implements ControlValueAccess
@Input() @Input()
title: string; title: string;
@Input()
appearance: MatFormFieldAppearance = 'fill';
processingSettingRowForm = this.fb.group({ processingSettingRowForm = this.fb.group({
type: [defaultAdvancedProcessingConfig.type], type: [defaultAdvancedProcessingConfig.type],
deduplicationIntervalSecs: [{value: 60, disabled: true}] deduplicationIntervalSecs: [{value: 60, disabled: true}]

5
ui-ngx/src/app/modules/home/components/rule-node/action/advanced-processing-setting.component.html

@ -24,30 +24,35 @@
@if (timeseries) { @if (timeseries) {
<tb-advanced-processing-setting-row <tb-advanced-processing-setting-row
formControlName="timeseries" formControlName="timeseries"
appearance="outline"
title="{{ 'rule-node-config.save-time-series.time-series' | translate }}" title="{{ 'rule-node-config.save-time-series.time-series' | translate }}"
></tb-advanced-processing-setting-row> ></tb-advanced-processing-setting-row>
} }
@if (attributes) { @if (attributes) {
<tb-advanced-processing-setting-row <tb-advanced-processing-setting-row
formControlName="attributes" formControlName="attributes"
appearance="outline"
title="{{ 'rule-node-config.save-attribute.attributes' | translate }}" title="{{ 'rule-node-config.save-attribute.attributes' | translate }}"
></tb-advanced-processing-setting-row> ></tb-advanced-processing-setting-row>
} }
@if (latest) { @if (latest) {
<tb-advanced-processing-setting-row <tb-advanced-processing-setting-row
formControlName="latest" formControlName="latest"
appearance="outline"
title="{{ 'rule-node-config.save-time-series.latest' | translate }}" title="{{ 'rule-node-config.save-time-series.latest' | translate }}"
></tb-advanced-processing-setting-row> ></tb-advanced-processing-setting-row>
} }
@if (webSockets) { @if (webSockets) {
<tb-advanced-processing-setting-row <tb-advanced-processing-setting-row
formControlName="webSockets" formControlName="webSockets"
appearance="outline"
title="{{ 'rule-node-config.save-time-series.web-sockets' | translate }}" title="{{ 'rule-node-config.save-time-series.web-sockets' | translate }}"
></tb-advanced-processing-setting-row> ></tb-advanced-processing-setting-row>
} }
@if (calculatedFields) { @if (calculatedFields) {
<tb-advanced-processing-setting-row <tb-advanced-processing-setting-row
formControlName="calculatedFields" formControlName="calculatedFields"
appearance="outline"
title="{{ 'rule-node-config.save-time-series.calculated-fields-and-alarm-rules' | translate }}" title="{{ 'rule-node-config.save-time-series.calculated-fields-and-alarm-rules' | translate }}"
></tb-advanced-processing-setting-row> ></tb-advanced-processing-setting-row>
} }

2
ui-ngx/src/app/modules/home/components/rule-node/action/assign-customer-config.component.html

@ -17,7 +17,7 @@
--> -->
<section [formGroup]="assignCustomerConfigForm" class="flex flex-col"> <section [formGroup]="assignCustomerConfigForm" class="flex flex-col">
<div class="tb-form-panel no-padding no-border"> <div class="tb-form-panel no-padding no-border">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.customer-name-pattern</mat-label> <mat-label translate>rule-node-config.customer-name-pattern</mat-label>
<input required matInput formControlName="customerNamePattern"> <input required matInput formControlName="customerNamePattern">
@if (assignCustomerConfigForm.get('customerNamePattern').hasError('required') || @if (assignCustomerConfigForm.get('customerNamePattern').hasError('required') ||

7
ui-ngx/src/app/modules/home/components/rule-node/action/attributes-config.component.html

@ -28,7 +28,7 @@
</tb-toggle-select> </tb-toggle-select>
</div> </div>
@if(!attributesConfigForm.get('processingSettings.isAdvanced').value) { @if(!attributesConfigForm.get('processingSettings.isAdvanced').value) {
<mat-form-field> <mat-form-field appearance="outline">
<mat-label translate>rule-node-config.save-attribute.strategy</mat-label> <mat-label translate>rule-node-config.save-attribute.strategy</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="type">
@for (strategy of processingStrategies; track strategy) { @for (strategy of processingStrategies; track strategy) {
@ -39,6 +39,7 @@
@if(attributesConfigForm.get('processingSettings.type').value === ProcessingType.DEDUPLICATE) { @if(attributesConfigForm.get('processingSettings.type').value === ProcessingType.DEDUPLICATE) {
<tb-time-unit-input <tb-time-unit-input
appearance="outline"
required required
labelText="{{ 'rule-node-config.save-attribute.deduplication-interval' | translate }}" labelText="{{ 'rule-node-config.save-attribute.deduplication-interval' | translate }}"
requiredText="{{ 'rule-node-config.save-attribute.deduplication-interval-required' | translate }}" requiredText="{{ 'rule-node-config.save-attribute.deduplication-interval-required' | translate }}"
@ -63,7 +64,7 @@
rule-node-config.save-attribute.scope rule-node-config.save-attribute.scope
</div> </div>
<div class="tb-form-row no-border no-padding tb-standard-fields"> <div class="tb-form-row no-border no-padding tb-standard-fields">
<mat-form-field class="flex" subscriptSizing="dynamic"> <mat-form-field class="flex" subscriptSizing="dynamic" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label>
<mat-select required <mat-select required
class="tb-entity-type-select" matInput formControlName="scope"> class="tb-entity-type-select" matInput formControlName="scope">
@ -74,7 +75,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="flex" subscriptSizing="dynamic"> <mat-form-field class="flex" subscriptSizing="dynamic" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label>
<input type="text" matInput readonly disabled [ngModel]="attributesConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}"> <input type="text" matInput readonly disabled [ngModel]="attributesConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}">
<button type="button" <button type="button"

2
ui-ngx/src/app/modules/home/components/rule-node/action/clear-alarm-config.component.html

@ -62,7 +62,7 @@
{{ testScriptLabel | translate }} {{ testScriptLabel | translate }}
</button> </button>
</div> </div>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.alarm-type</mat-label> <mat-label translate>rule-node-config.alarm-type</mat-label>
<input required matInput formControlName="alarmType"> <input required matInput formControlName="alarmType">
@if (clearAlarmConfigForm.get('alarmType').hasError('required')) { @if (clearAlarmConfigForm.get('alarmType').hasError('required')) {

8
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) { @if (createAlarmConfigForm.get('useMessageAlarmData').value === false) {
<section class="flex flex-col"> <section class="flex flex-col">
<mat-form-field class="flex-1" subscriptSizing="dynamic"> <mat-form-field class="flex-1" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.alarm-type</mat-label> <mat-label translate>rule-node-config.alarm-type</mat-label>
<input required matInput formControlName="alarmType"> <input required matInput formControlName="alarmType">
@if (createAlarmConfigForm.get('alarmType').hasError('required')) { @if (createAlarmConfigForm.get('alarmType').hasError('required')) {
@ -91,7 +91,7 @@
{{ 'rule-node-config.use-alarm-severity-pattern' | translate }} {{ 'rule-node-config.use-alarm-severity-pattern' | translate }}
</mat-checkbox> </mat-checkbox>
@if (!createAlarmConfigForm.get('dynamicSeverity').value) { @if (!createAlarmConfigForm.get('dynamicSeverity').value) {
<mat-form-field class="flex-1"> <mat-form-field class="flex-1" appearance="outline">
<mat-label translate>rule-node-config.alarm-severity</mat-label> <mat-label translate>rule-node-config.alarm-severity</mat-label>
<mat-select formControlName="severity" required> <mat-select formControlName="severity" required>
@for (severity of alarmSeverities; track severity) { @for (severity of alarmSeverities; track severity) {
@ -108,7 +108,7 @@
</mat-form-field> </mat-form-field>
} }
@if (createAlarmConfigForm.get('dynamicSeverity').value) { @if (createAlarmConfigForm.get('dynamicSeverity').value) {
<mat-form-field class="flex-1" subscriptSizing="dynamic"> <mat-form-field class="flex-1" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.alarm-severity-pattern</mat-label> <mat-label translate>rule-node-config.alarm-severity-pattern</mat-label>
<input matInput formControlName="severity" required> <input matInput formControlName="severity" required>
@if (createAlarmConfigForm.get('severity').hasError('required')) { @if (createAlarmConfigForm.get('severity').hasError('required')) {
@ -124,7 +124,7 @@
</mat-checkbox> </mat-checkbox>
@if (createAlarmConfigForm.get('propagate').value === true) { @if (createAlarmConfigForm.get('propagate').value === true) {
<section> <section>
<mat-form-field floatLabel="always" class="mat-block" subscriptSizing="dynamic"> <mat-form-field floatLabel="always" class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.relation-types-list</mat-label> <mat-label translate>rule-node-config.relation-types-list</mat-label>
<mat-chip-grid #relationTypesChipList> <mat-chip-grid #relationTypesChipList>
@for (key of createAlarmConfigForm.get('relationTypes').value; track key) { @for (key of createAlarmConfigForm.get('relationTypes').value; track key) {

6
ui-ngx/src/app/modules/home/components/rule-node/action/create-relation-config.component.html

@ -19,7 +19,7 @@
<div class="tb-form-panel stroked no-padding-bottom"> <div class="tb-form-panel stroked no-padding-bottom">
<div class="tb-form-panel-title" translate>rule-node-config.relation-parameters</div> <div class="tb-form-panel-title" translate>rule-node-config.relation-parameters</div>
<div class="flex flex-col"> <div class="flex flex-col">
<mat-form-field class="mat-block" hideRequiredMarker> <mat-form-field class="mat-block" hideRequiredMarker appearance="outline">
<mat-label translate>relation.direction</mat-label> <mat-label translate>relation.direction</mat-label>
<mat-select required matInput formControlName="direction"> <mat-select required matInput formControlName="direction">
@for (type of directionTypes; track type) { @for (type of directionTypes; track type) {
@ -30,6 +30,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<tb-relation-type-autocomplete <tb-relation-type-autocomplete
appearance="outline"
required required
formControlName="relationType"> formControlName="relationType">
</tb-relation-type-autocomplete> </tb-relation-type-autocomplete>
@ -40,6 +41,7 @@
<div class="tb-form-panel-title" translate>rule-node-config.target-entity</div> <div class="tb-form-panel-title" translate>rule-node-config.target-entity</div>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<tb-entity-type-select <tb-entity-type-select
appearance="outline"
class="flex-1" class="flex-1"
showLabel showLabel
required required
@ -50,6 +52,7 @@
@if (createRelationConfigForm.get('entityType').value && @if (createRelationConfigForm.get('entityType').value &&
createRelationConfigForm.get('entityType').value !== entityType.TENANT) { createRelationConfigForm.get('entityType').value !== entityType.TENANT) {
<mat-form-field <mat-form-field
appearance="outline"
class="mat-block flex-1"> class="mat-block flex-1">
<mat-label>{{ entityTypeNamePatternTranslation.get(createRelationConfigForm.get('entityType').value) | translate }}</mat-label> <mat-label>{{ entityTypeNamePatternTranslation.get(createRelationConfigForm.get('entityType').value) | translate }}</mat-label>
<input required matInput formControlName="entityNamePattern"> <input required matInput formControlName="entityNamePattern">
@ -59,6 +62,7 @@
@if (createRelationConfigForm.get('entityType').value === entityType.DEVICE || @if (createRelationConfigForm.get('entityType').value === entityType.DEVICE ||
createRelationConfigForm.get('entityType').value === entityType.ASSET) { createRelationConfigForm.get('entityType').value === entityType.ASSET) {
<mat-form-field <mat-form-field
appearance="outline"
class="mat-block flex-1"> class="mat-block flex-1">
<mat-label translate>rule-node-config.profile-name</mat-label> <mat-label translate>rule-node-config.profile-name</mat-label>
<input matInput formControlName="entityTypePattern"> <input matInput formControlName="entityTypePattern">

6
ui-ngx/src/app/modules/home/components/rule-node/action/delete-attributes-config.component.html

@ -20,7 +20,7 @@
<tb-example-hint [hintText]="'rule-node-config.attributes-scope-hint'"> <tb-example-hint [hintText]="'rule-node-config.attributes-scope-hint'">
</tb-example-hint> </tb-example-hint>
<div class="tb-form-row no-border no-padding tb-standard-fields"> <div class="tb-form-row no-border no-padding tb-standard-fields">
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label>
<mat-select required <mat-select required
class="tb-entity-type-select" matInput formControlName="scope"> class="tb-entity-type-select" matInput formControlName="scope">
@ -31,7 +31,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label>
<input type="text" matInput readonly disabled [ngModel]="deleteAttributesConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}"> <input type="text" matInput readonly disabled [ngModel]="deleteAttributesConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}">
<button type="button" <button type="button"
@ -49,7 +49,7 @@
</div> </div>
</div> </div>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-keys' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-keys' | translate }}</mat-label>
<mat-chip-grid formControlName="keys" #attributeChipList> <mat-chip-grid formControlName="keys" #attributeChipList>
@for (key of deleteAttributesConfigForm.get('keys').value; track key) { @for (key of deleteAttributesConfigForm.get('keys').value; track key) {

5
ui-ngx/src/app/modules/home/components/rule-node/action/delete-relation-config.component.html

@ -19,7 +19,7 @@
<div class="tb-form-panel stroked no-padding-bottom"> <div class="tb-form-panel stroked no-padding-bottom">
<div class="tb-form-panel-title" translate>rule-node-config.relation-parameters</div> <div class="tb-form-panel-title" translate>rule-node-config.relation-parameters</div>
<div class="flex flex-col"> <div class="flex flex-col">
<mat-form-field class="mat-block" hideRequiredMarker> <mat-form-field class="mat-block" hideRequiredMarker appearance="outline">
<mat-label translate>relation.direction</mat-label> <mat-label translate>relation.direction</mat-label>
<mat-select required matInput formControlName="direction"> <mat-select required matInput formControlName="direction">
@for (type of directionTypes; track type) { @for (type of directionTypes; track type) {
@ -30,6 +30,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<tb-relation-type-autocomplete <tb-relation-type-autocomplete
appearance="outline"
required required
formControlName="relationType"> formControlName="relationType">
</tb-relation-type-autocomplete> </tb-relation-type-autocomplete>
@ -49,11 +50,13 @@
class="flex-1" class="flex-1"
showLabel showLabel
required required
appearance="outline"
[allowedEntityTypes]="allowedEntityTypes" [allowedEntityTypes]="allowedEntityTypes"
formControlName="entityType"> formControlName="entityType">
</tb-entity-type-select> </tb-entity-type-select>
@if (deleteRelationConfigForm.get('entityType').value && deleteRelationConfigForm.get('entityType').value !== entityType.TENANT) { @if (deleteRelationConfigForm.get('entityType').value && deleteRelationConfigForm.get('entityType').value !== entityType.TENANT) {
<mat-form-field <mat-form-field
appearance="outline"
class="mat-block flex-1"> class="mat-block flex-1">
<mat-label>{{ entityTypeNamePatternTranslation.get(deleteRelationConfigForm.get('entityType').value) | translate }}</mat-label> <mat-label>{{ entityTypeNamePatternTranslation.get(deleteRelationConfigForm.get('entityType').value) | translate }}</mat-label>
<input required matInput formControlName="entityNamePattern"> <input required matInput formControlName="entityNamePattern">

2
ui-ngx/src/app/modules/home/components/rule-node/action/device-state-config.component.html

@ -16,7 +16,7 @@
--> -->
<section [formGroup]="deviceState"> <section [formGroup]="deviceState">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label>{{ 'rule-node-config.select-device-connectivity-event' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.select-device-connectivity-event' | translate }}</mat-label>
<mat-select formControlName="event"> <mat-select formControlName="event">
@for (eventOption of eventOptions; track eventOption) { @for (eventOption of eventOptions; track eventOption) {

5
ui-ngx/src/app/modules/home/components/rule-node/action/generator-config.component.html

@ -19,7 +19,7 @@
<div class="tb-form-panel no-padding-bottom stroked"> <div class="tb-form-panel no-padding-bottom stroked">
<div class="tb-form-panel-title" translate>rule-node-config.generation-parameters</div> <div class="tb-form-panel-title" translate>rule-node-config.generation-parameters</div>
<div class="tb-form-row no-border no-padding tb-standard-fields column-xs"> <div class="tb-form-row no-border no-padding tb-standard-fields column-xs">
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.message-count</mat-label> <mat-label translate>rule-node-config.message-count</mat-label>
<input required type="number" min="0" step="1" matInput formControlName="msgCount"> <input required type="number" min="0" step="1" matInput formControlName="msgCount">
@if (generatorConfigForm.get('msgCount').hasError('required')) { @if (generatorConfigForm.get('msgCount').hasError('required')) {
@ -33,7 +33,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.generation-frequency-seconds</mat-label> <mat-label translate>rule-node-config.generation-frequency-seconds</mat-label>
<input required type="number" min="1" step="1" matInput formControlName="periodInSeconds"> <input required type="number" min="1" step="1" matInput formControlName="periodInSeconds">
@if (generatorConfigForm.get('periodInSeconds').hasError('required')) { @if (generatorConfigForm.get('periodInSeconds').hasError('required')) {
@ -53,6 +53,7 @@
<div class="tb-form-panel no-padding-bottom stroked"> <div class="tb-form-panel no-padding-bottom stroked">
<div class="tb-form-panel-title" translate>rule-node-config.originator</div> <div class="tb-form-panel-title" translate>rule-node-config.originator</div>
<tb-entity-select class="flex-1" <tb-entity-select class="flex-1"
appearance="outline"
required="true" required="true"
useAliasEntityTypes="true" useAliasEntityTypes="true"
[allowedEntityTypes]="allowedEntityTypes" [allowedEntityTypes]="allowedEntityTypes"

27
ui-ngx/src/app/modules/home/components/rule-node/action/gps-geo-action-config.component.html

@ -20,7 +20,7 @@
<div class="tb-form-panel-title" translate>rule-node-config.coordinate-field-names</div> <div class="tb-form-panel-title" translate>rule-node-config.coordinate-field-names</div>
<section> <section>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<mat-form-field class="mat-block max-w-50% flex-full"> <mat-form-field class="mat-block max-w-50% flex-full" appearance="outline">
<mat-label>{{ 'rule-node-config.latitude-field-name' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.latitude-field-name' | translate }}</mat-label>
<input matInput formControlName="latitudeKeyName" required> <input matInput formControlName="latitudeKeyName" required>
@if (geoActionConfigForm.get('latitudeKeyName').hasError('required')) { @if (geoActionConfigForm.get('latitudeKeyName').hasError('required')) {
@ -29,7 +29,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block max-w-50% flex-full"> <mat-form-field class="mat-block max-w-50% flex-full" appearance="outline">
<mat-label>{{ 'rule-node-config.longitude-field-name' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.longitude-field-name' | translate }}</mat-label>
<input matInput formControlName="longitudeKeyName" required> <input matInput formControlName="longitudeKeyName" required>
@if (geoActionConfigForm.get('longitudeKeyName').hasError('required')) { @if (geoActionConfigForm.get('longitudeKeyName').hasError('required')) {
@ -45,7 +45,7 @@
<section class="tb-form-panel stroked" [class.no-padding-bottom]="!defaultPaddingEnable"> <section class="tb-form-panel stroked" [class.no-padding-bottom]="!defaultPaddingEnable">
<div class="tb-form-panel-title" translate>rule-node-config.geofence-configuration</div> <div class="tb-form-panel-title" translate>rule-node-config.geofence-configuration</div>
<section class="flex flex-col"> <section class="flex flex-col">
<mat-form-field class="mat-block flex-1" hideRequiredMarker> <mat-form-field class="mat-block flex-1" hideRequiredMarker appearance="outline">
<mat-label>{{ 'rule-node-config.perimeter-type' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.perimeter-type' | translate }}</mat-label>
<mat-select formControlName="perimeterType"> <mat-select formControlName="perimeterType">
@for (type of perimeterTypes; track type) { @for (type of perimeterTypes; track type) {
@ -70,7 +70,7 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
@if (geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { @if (geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) {
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'rule-node-config.perimeter-key-name' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.perimeter-key-name' | translate }}</mat-label>
<input matInput formControlName="perimeterKeyName" required> <input matInput formControlName="perimeterKeyName" required>
@if (geoActionConfigForm.get('perimeterKeyName').hasError('required')) { @if (geoActionConfigForm.get('perimeterKeyName').hasError('required')) {
@ -83,8 +83,7 @@
} }
@if (geoActionConfigForm.get('perimeterType').value === perimeterType.CIRCLE && @if (geoActionConfigForm.get('perimeterType').value === perimeterType.CIRCLE &&
!geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { !geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) {
<div class="flex flex-col" <div class="flex flex-col">
>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<mat-form-field class="flex-1"> <mat-form-field class="flex-1">
<mat-label>{{ 'rule-node-config.circle-center-latitude' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.circle-center-latitude' | translate }}</mat-label>
@ -95,7 +94,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="flex-1"> <mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.circle-center-longitude' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.circle-center-longitude' | translate }}</mat-label>
<input type="number" min="-180" max="180" step="0.1" matInput formControlName="centerLongitude" required> <input type="number" min="-180" max="180" step="0.1" matInput formControlName="centerLongitude" required>
@if (geoActionConfigForm.get('centerLongitude').hasError('required')) { @if (geoActionConfigForm.get('centerLongitude').hasError('required')) {
@ -106,7 +105,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<mat-form-field class="flex-1"> <mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.range' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.range' | translate }}</mat-label>
<input type="number" min="0" step="0.1" matInput formControlName="range" required> <input type="number" min="0" step="0.1" matInput formControlName="range" required>
@if (geoActionConfigForm.get('range').hasError('required')) { @if (geoActionConfigForm.get('range').hasError('required')) {
@ -115,7 +114,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="flex-1"> <mat-form-field class="flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.range-units' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.range-units' | translate }}</mat-label>
<mat-select formControlName="rangeUnit" required> <mat-select formControlName="rangeUnit" required>
@for (type of rangeUnits; track type) { @for (type of rangeUnits; track type) {
@ -136,7 +135,7 @@
@if (geoActionConfigForm.get('perimeterType').value === perimeterType.POLYGON && @if (geoActionConfigForm.get('perimeterType').value === perimeterType.POLYGON &&
!geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) { !geoActionConfigForm.get('fetchPerimeterInfoFromMessageMetadata').value) {
<div class="flex flex-col"> <div class="flex flex-col">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.polygon-definition</mat-label> <mat-label translate>rule-node-config.polygon-definition</mat-label>
<input matInput formControlName="polygonsDefinition" required> <input matInput formControlName="polygonsDefinition" required>
<mat-icon matSuffix class="help-icon m-2 cursor-pointer" aria-hidden="false" aria-label="help-icon" <mat-icon matSuffix class="help-icon m-2 cursor-pointer" aria-hidden="false" aria-label="help-icon"
@ -174,7 +173,7 @@
@if (geoActionConfigForm.get('reportPresenceStatusOnEachMessage').value === false) { @if (geoActionConfigForm.get('reportPresenceStatusOnEachMessage').value === false) {
<div> <div>
<div class="flex flex-col gt-sm:flex-row gt-sm:gap-2"> <div class="flex flex-col gt-sm:flex-row gt-sm:gap-2">
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.min-inside-duration</mat-label> <mat-label translate>rule-node-config.min-inside-duration</mat-label>
<input type="number" step="1" min="1" max="2147483647" matInput formControlName="minInsideDuration" required> <input type="number" step="1" min="1" max="2147483647" matInput formControlName="minInsideDuration" required>
@if (geoActionConfigForm.get('minInsideDuration').hasError('required')) { @if (geoActionConfigForm.get('minInsideDuration').hasError('required')) {
@ -193,7 +192,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.min-inside-duration-time-unit</mat-label> <mat-label translate>rule-node-config.min-inside-duration-time-unit</mat-label>
<mat-select formControlName="minInsideDurationTimeUnit" required> <mat-select formControlName="minInsideDurationTimeUnit" required>
@for (timeUnit of timeUnits; track timeUnit) { @for (timeUnit of timeUnits; track timeUnit) {
@ -205,7 +204,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-col gt-sm:flex-row gt-sm:gap-2"> <div class="flex flex-col gt-sm:flex-row gt-sm:gap-2">
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.min-outside-duration</mat-label> <mat-label translate>rule-node-config.min-outside-duration</mat-label>
<input type="number" step="1" min="1" max="2147483647" matInput formControlName="minOutsideDuration" required> <input type="number" step="1" min="1" max="2147483647" matInput formControlName="minOutsideDuration" required>
@if (geoActionConfigForm.get('minOutsideDuration').hasError('required')) { @if (geoActionConfigForm.get('minOutsideDuration').hasError('required')) {
@ -224,7 +223,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.min-outside-duration-time-unit</mat-label> <mat-label translate>rule-node-config.min-outside-duration-time-unit</mat-label>
<mat-select formControlName="minOutsideDurationTimeUnit" required> <mat-select formControlName="minOutsideDurationTimeUnit" required>
@for (timeUnit of timeUnits; track timeUnit) { @for (timeUnit of timeUnits; track timeUnit) {

14
ui-ngx/src/app/modules/home/components/rule-node/action/math-function-config.component.html

@ -19,6 +19,7 @@
<tb-math-function-autocomplete <tb-math-function-autocomplete
class="flex-full max-h-30% xs:max-h-full md:max-h-full" class="flex-full max-h-30% xs:max-h-full md:max-h-full"
required required
appearance="outline"
formControlName="operation"> formControlName="operation">
</tb-math-function-autocomplete> </tb-math-function-autocomplete>
<fieldset class="fields-group flex flex-col gap-2"> <fieldset class="fields-group flex flex-col gap-2">
@ -31,7 +32,7 @@
<fieldset <fieldset
class="fields-group flex flex-col gap-2"> class="fields-group flex flex-col gap-2">
<legend class="group-title">{{'rule-node-config.custom-expression-field-input' | translate }} *</legend> <legend class="group-title">{{'rule-node-config.custom-expression-field-input' | translate }} *</legend>
<mat-form-field class="mat-block no-margin-top flex-1" subscriptSizing="dynamic"> <mat-form-field class="mat-block no-margin-top flex-1" subscriptSizing="dynamic" appearance="outline">
<input matInput formControlName="customFunction" required/> <input matInput formControlName="customFunction" required/>
<div matSuffix <div matSuffix
class="pr-2" class="pr-2"
@ -51,7 +52,7 @@
<fieldset class="fields-group flex flex-col gap-2"> <fieldset class="fields-group flex flex-col gap-2">
<legend class="group-title" translate>rule-node-config.result-title</legend> <legend class="group-title" translate>rule-node-config.result-title</legend>
<div formGroupName="result"> <div formGroupName="result">
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.type-field-input</mat-label> <mat-label translate>rule-node-config.type-field-input</mat-label>
<mat-select formControlName="type" required> <mat-select formControlName="type" required>
<mat-select-trigger> <mat-select-trigger>
@ -75,7 +76,7 @@
</mat-form-field> </mat-form-field>
<div class="xs:flex-col gt-xs:gap-4 flex flex-1 flex-row"> <div class="xs:flex-col gt-xs:gap-4 flex flex-1 flex-row">
@if (mathFunctionConfigForm.get('result').get('type').value === ArgumentTypeResult.ATTRIBUTE) { @if (mathFunctionConfigForm.get('result').get('type').value === ArgumentTypeResult.ATTRIBUTE) {
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.attribute-scope-field-input</mat-label> <mat-label translate>rule-node-config.attribute-scope-field-input</mat-label>
<mat-select required formControlName="attributeScope"> <mat-select required formControlName="attributeScope">
@for (scope of attributeScopeResult; track scope) { @for (scope of attributeScopeResult; track scope) {
@ -86,7 +87,7 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
} }
<mat-form-field floatLabel="always" class="mat-block flex-1"> <mat-form-field floatLabel="always" class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.key-field-input</mat-label> <mat-label translate>rule-node-config.key-field-input</mat-label>
<input matInput formControlName="key" required/> <input matInput formControlName="key" required/>
<mat-icon class="help-icon m-2 cursor-pointer" aria-hidden="false" aria-label="help-icon" matSuffix <mat-icon class="help-icon m-2 cursor-pointer" aria-hidden="false" aria-label="help-icon" matSuffix
@ -100,7 +101,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="xs:flex-col gt-xs:gap-4 flex flex-1 flex-row"> <div class="xs:flex-col gt-xs:gap-4 flex flex-1 flex-row">
<mat-form-field floatLabel="always" class="mat-block flex-1" subscriptSizing="dynamic"> <mat-form-field floatLabel="always" class="mat-block flex-1" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.number-floating-point-field-input</mat-label> <mat-label translate>rule-node-config.number-floating-point-field-input</mat-label>
<input formControlName="resultValuePrecision" <input formControlName="resultValuePrecision"
matInput matInput
@ -110,8 +111,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
@if ([ArgumentTypeResult.ATTRIBUTE, ArgumentTypeResult.TIME_SERIES].includes(mathFunctionConfigForm.get('result').get('type').value)) { @if ([ArgumentTypeResult.ATTRIBUTE, ArgumentTypeResult.TIME_SERIES].includes(mathFunctionConfigForm.get('result').get('type').value)) {
<div <div class="xs:flex-col gt-xs:gap-4 flex flex-1 flex-row items-stretch justify-start" style="padding-top: 16px;">
class="xs:flex-col gt-xs:gap-4 flex flex-1 flex-row items-stretch justify-start" style="padding-top: 16px;">
<mat-checkbox formControlName="addToBody"> <mat-checkbox formControlName="addToBody">
{{'rule-node-config.add-to-message-field-input' | translate }} {{'rule-node-config.add-to-message-field-input' | translate }}
</mat-checkbox> </mat-checkbox>

4
ui-ngx/src/app/modules/home/components/rule-node/action/msg-count-config.component.html

@ -16,7 +16,7 @@
--> -->
<section [formGroup]="msgCountConfigForm" class="flex flex-col"> <section [formGroup]="msgCountConfigForm" class="flex flex-col">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.interval-seconds</mat-label> <mat-label translate>rule-node-config.interval-seconds</mat-label>
<input required type="number" min="1" step="1" matInput formControlName="interval"> <input required type="number" min="1" step="1" matInput formControlName="interval">
@if (msgCountConfigForm.get('interval').hasError('required')) { @if (msgCountConfigForm.get('interval').hasError('required')) {
@ -30,7 +30,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.output-timeseries-key-prefix</mat-label> <mat-label translate>rule-node-config.output-timeseries-key-prefix</mat-label>
<input required matInput formControlName="telemetryPrefix"> <input required matInput formControlName="telemetryPrefix">
@if (msgCountConfigForm.get('telemetryPrefix').hasError('required')) { @if (msgCountConfigForm.get('telemetryPrefix').hasError('required')) {

5
ui-ngx/src/app/modules/home/components/rule-node/action/msg-delay-config.component.html

@ -22,6 +22,7 @@
<div class="tb-hint" translate>rule-node-config.use-metadata-period-in-seconds-patterns-hint</div> <div class="tb-hint" translate>rule-node-config.use-metadata-period-in-seconds-patterns-hint</div>
@if (msgDelayConfigForm.get('useMetadataPeriodInSecondsPatterns').value !== true) { @if (msgDelayConfigForm.get('useMetadataPeriodInSecondsPatterns').value !== true) {
<mat-form-field <mat-form-field
appearance="outline"
class="mat-block"> class="mat-block">
<mat-label translate>rule-node-config.period-seconds</mat-label> <mat-label translate>rule-node-config.period-seconds</mat-label>
<input required type="number" min="0" step="1" matInput formControlName="periodInSeconds"> <input required type="number" min="0" step="1" matInput formControlName="periodInSeconds">
@ -37,7 +38,7 @@
} }
</mat-form-field> </mat-form-field>
} @else { } @else {
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.period-in-seconds-pattern</mat-label> <mat-label translate>rule-node-config.period-in-seconds-pattern</mat-label>
<input required matInput formControlName="periodInSecondsPattern"> <input required matInput formControlName="periodInSecondsPattern">
@if (msgDelayConfigForm.get('periodInSecondsPattern').hasError('required')) { @if (msgDelayConfigForm.get('periodInSecondsPattern').hasError('required')) {
@ -48,7 +49,7 @@
<mat-hint translate>rule-node-config.general-pattern-hint</mat-hint> <mat-hint translate>rule-node-config.general-pattern-hint</mat-hint>
</mat-form-field> </mat-form-field>
} }
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.max-pending-messages</mat-label> <mat-label translate>rule-node-config.max-pending-messages</mat-label>
<input required type="number" min="1" max="100000" step="1" matInput formControlName="maxPendingMsgs"> <input required type="number" min="1" max="100000" step="1" matInput formControlName="maxPendingMsgs">
@if (msgDelayConfigForm.get('maxPendingMsgs').hasError('required')) { @if (msgDelayConfigForm.get('maxPendingMsgs').hasError('required')) {

4
ui-ngx/src/app/modules/home/components/rule-node/action/push-to-cloud-config.component.html

@ -20,7 +20,7 @@
<tb-example-hint [hintText]="'rule-node-config.attributes-scope-hint'"> <tb-example-hint [hintText]="'rule-node-config.attributes-scope-hint'">
</tb-example-hint> </tb-example-hint>
<div class="tb-form-row no-border no-padding tb-standard-fields"> <div class="tb-form-row no-border no-padding tb-standard-fields">
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label>
<mat-select required <mat-select required
class="tb-entity-type-select" matInput formControlName="scope"> class="tb-entity-type-select" matInput formControlName="scope">
@ -31,7 +31,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label>
<input type="text" matInput readonly disabled [ngModel]="pushToCloudConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}"> <input type="text" matInput readonly disabled [ngModel]="pushToCloudConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}">
<button type="button" <button type="button"

4
ui-ngx/src/app/modules/home/components/rule-node/action/push-to-edge-config.component.html

@ -20,7 +20,7 @@
<tb-example-hint [hintText]="'rule-node-config.attributes-scope-hint'"> <tb-example-hint [hintText]="'rule-node-config.attributes-scope-hint'">
</tb-example-hint> </tb-example-hint>
<div class="tb-form-row no-border no-padding tb-standard-fields"> <div class="tb-form-row no-border no-padding tb-standard-fields">
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope' | translate }}</mat-label>
<mat-select required <mat-select required
class="tb-entity-type-select" matInput formControlName="scope"> class="tb-entity-type-select" matInput formControlName="scope">
@ -31,7 +31,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.attributes-scope-value' | translate }}</mat-label>
<input type="text" matInput readonly disabled [ngModel]="pushToEdgeConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}"> <input type="text" matInput readonly disabled [ngModel]="pushToEdgeConfigForm.get('scope').value" [ngModelOptions]="{standalone: true}">
<button type="button" <button type="button"

6
ui-ngx/src/app/modules/home/components/rule-node/action/rpc-reply-config.component.html

@ -20,15 +20,15 @@
<tb-example-hint [hintText]="'rule-node-config.rpc-reply-routing-configuration-hint'"> <tb-example-hint [hintText]="'rule-node-config.rpc-reply-routing-configuration-hint'">
</tb-example-hint> </tb-example-hint>
<div class="tb-form-row no-border no-padding tb-standard-fields column-xs"> <div class="tb-form-row no-border no-padding tb-standard-fields column-xs">
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.service-id-metadata-attribute</mat-label> <mat-label translate>rule-node-config.service-id-metadata-attribute</mat-label>
<input matInput formControlName="serviceIdMetaDataAttribute"> <input matInput formControlName="serviceIdMetaDataAttribute">
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.session-id-metadata-attribute</mat-label> <mat-label translate>rule-node-config.session-id-metadata-attribute</mat-label>
<input matInput formControlName="sessionIdMetaDataAttribute"> <input matInput formControlName="sessionIdMetaDataAttribute">
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.request-id-metadata-attribute</mat-label> <mat-label translate>rule-node-config.request-id-metadata-attribute</mat-label>
<input matInput formControlName="requestIdMetaDataAttribute"> <input matInput formControlName="requestIdMetaDataAttribute">
</mat-form-field> </mat-form-field>

2
ui-ngx/src/app/modules/home/components/rule-node/action/rpc-request-config.component.html

@ -16,7 +16,7 @@
--> -->
<section [formGroup]="rpcRequestConfigForm" class="flex flex-col"> <section [formGroup]="rpcRequestConfigForm" class="flex flex-col">
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.timeout-sec</mat-label> <mat-label translate>rule-node-config.timeout-sec</mat-label>
<input type="number" min="0" step="1" matInput formControlName="timeoutInSeconds" required> <input type="number" min="0" step="1" matInput formControlName="timeoutInSeconds" required>
@if (rpcRequestConfigForm.get('timeoutInSeconds').hasError('required')) { @if (rpcRequestConfigForm.get('timeoutInSeconds').hasError('required')) {

4
ui-ngx/src/app/modules/home/components/rule-node/action/save-to-custom-table-config.component.html

@ -16,7 +16,7 @@
--> -->
<section [formGroup]="saveToCustomTableConfigForm" class="tb-form-panel no-border no-padding"> <section [formGroup]="saveToCustomTableConfigForm" class="tb-form-panel no-border no-padding">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.custom-table-name</mat-label> <mat-label translate>rule-node-config.custom-table-name</mat-label>
<input required matInput formControlName="tableName"> <input required matInput formControlName="tableName">
<mat-icon class="help-icon m-2 cursor-pointer" <mat-icon class="help-icon m-2 cursor-pointer"
@ -45,7 +45,7 @@
[valRequiredText]="'rule-node-config.table-col-required' | translate" [valRequiredText]="'rule-node-config.table-col-required' | translate"
[hintText]="'rule-node-config.fields-mapping-hint'"> [hintText]="'rule-node-config.fields-mapping-hint'">
</tb-kv-map-config> </tb-kv-map-config>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.default-ttl</mat-label> <mat-label translate>rule-node-config.default-ttl</mat-label>
<input type="number" min="0" step="1" matInput formControlName="defaultTtl" required> <input type="number" min="0" step="1" matInput formControlName="defaultTtl" required>
<mat-hint translate>rule-node-config.default-ttl-zero-hint</mat-hint> <mat-hint translate>rule-node-config.default-ttl-zero-hint</mat-hint>

4
ui-ngx/src/app/modules/home/components/rule-node/action/send-rest-api-call-reply-config.component.html

@ -20,11 +20,11 @@
<tb-example-hint [hintText]="'rule-node-config.reply-routing-configuration-hint'"> <tb-example-hint [hintText]="'rule-node-config.reply-routing-configuration-hint'">
</tb-example-hint> </tb-example-hint>
<div class="tb-form-row no-border no-padding tb-standard-fields column-xs"> <div class="tb-form-row no-border no-padding tb-standard-fields column-xs">
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.service-id-metadata-attribute</mat-label> <mat-label translate>rule-node-config.service-id-metadata-attribute</mat-label>
<input matInput formControlName="serviceIdMetaDataAttribute"> <input matInput formControlName="serviceIdMetaDataAttribute">
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.request-id-metadata-attribute</mat-label> <mat-label translate>rule-node-config.request-id-metadata-attribute</mat-label>
<input matInput formControlName="requestIdMetaDataAttribute"> <input matInput formControlName="requestIdMetaDataAttribute">
</mat-form-field> </mat-form-field>

4
ui-ngx/src/app/modules/home/components/rule-node/action/timeseries-config.component.html

@ -28,7 +28,7 @@
</tb-toggle-select> </tb-toggle-select>
</div> </div>
@if(!timeseriesConfigForm.get('processingSettings.isAdvanced').value) { @if(!timeseriesConfigForm.get('processingSettings.isAdvanced').value) {
<mat-form-field> <mat-form-field appearance="outline">
<mat-label translate>rule-node-config.save-time-series.strategy</mat-label> <mat-label translate>rule-node-config.save-time-series.strategy</mat-label>
<mat-select formControlName="type"> <mat-select formControlName="type">
@for (strategy of processingStrategies; track strategy) { @for (strategy of processingStrategies; track strategy) {
@ -39,6 +39,7 @@
@if(timeseriesConfigForm.get('processingSettings.type').value === ProcessingType.DEDUPLICATE) { @if(timeseriesConfigForm.get('processingSettings.type').value === ProcessingType.DEDUPLICATE) {
<tb-time-unit-input <tb-time-unit-input
appearance="outline"
required required
labelText="{{ 'rule-node-config.save-time-series.deduplication-interval' | translate }}" labelText="{{ 'rule-node-config.save-time-series.deduplication-interval' | translate }}"
requiredText="{{ 'rule-node-config.save-time-series.deduplication-interval-required' | translate }}" requiredText="{{ 'rule-node-config.save-time-series.deduplication-interval-required' | translate }}"
@ -70,6 +71,7 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
<tb-time-unit-input <tb-time-unit-input
appearance="outline"
required required
subscriptSizing="dynamic" subscriptSizing="dynamic"
labelText="{{ 'rule-node-config.default-ttl' | translate }}" labelText="{{ 'rule-node-config.default-ttl' | translate }}"

2
ui-ngx/src/app/modules/home/components/rule-node/action/unassign-customer-config.component.html

@ -26,7 +26,7 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
@if (unassignCustomerConfigForm.get('unassignFromCustomer').value) { @if (unassignCustomerConfigForm.get('unassignFromCustomer').value) {
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.customer-name-pattern</mat-label> <mat-label translate>rule-node-config.customer-name-pattern</mat-label>
<input required matInput formControlName="customerNamePattern"> <input required matInput formControlName="customerNamePattern">
@if (unassignCustomerConfigForm.get('customerNamePattern').hasError('required') || @if (unassignCustomerConfigForm.get('customerNamePattern').hasError('required') ||

15
ui-ngx/src/app/modules/home/components/rule-node/common/arguments-map-config.component.html

@ -45,8 +45,8 @@
<span <span
style="padding: 0 10px; min-width: 20px;">{{argumentControl.get('name').value}}.</span> style="padding: 0 10px; min-width: 20px;">{{argumentControl.get('name').value}}.</span>
} }
<div class="flex flex-1 flex-col"> <div class="flex flex-1 flex-col mt-2">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.argument-source-field-input</mat-label> <mat-label translate>rule-node-config.argument-source-field-input</mat-label>
<mat-select [formControl]="argumentControl.get('type')" required> <mat-select [formControl]="argumentControl.get('type')" required>
<mat-select-trigger> <mat-select-trigger>
@ -70,8 +70,7 @@
</mat-form-field> </mat-form-field>
<div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4"> <div class="flex flex-1 flex-row xs:flex-col gt-xs:gap-4">
@if (argumentControl.get('type').value && argumentControl.get('type').value !== ArgumentType.CONSTANT) { @if (argumentControl.get('type').value && argumentControl.get('type').value !== ArgumentType.CONSTANT) {
<mat-form-field floatLabel="always" class="mat-block gt-xs:max-w-50% gt-xs:flex-full" <mat-form-field floatLabel="always" class="mat-block gt-xs:max-w-50% gt-xs:flex-full" appearance="outline">
>
<mat-label translate>rule-node-config.argument-key-field-input</mat-label> <mat-label translate>rule-node-config.argument-key-field-input</mat-label>
<input [formControl]="argumentControl.get('key')" matInput required/> <input [formControl]="argumentControl.get('key')" matInput required/>
<mat-icon class="help-icon m-2 cursor-pointer" aria-hidden="false" aria-label="help-icon" matSuffix <mat-icon class="help-icon m-2 cursor-pointer" aria-hidden="false" aria-label="help-icon" matSuffix
@ -87,8 +86,7 @@
</mat-form-field> </mat-form-field>
} }
@if (argumentControl.get('type').value === ArgumentType.CONSTANT) { @if (argumentControl.get('type').value === ArgumentType.CONSTANT) {
<mat-form-field floatLabel="always" class="mat-block flex-1" <mat-form-field floatLabel="always" class="mat-block flex-1" appearance="outline">
>
<mat-label translate>rule-node-config.constant-value-field-input</mat-label> <mat-label translate>rule-node-config.constant-value-field-input</mat-label>
<input [formControl]="argumentControl.get('key')" <input [formControl]="argumentControl.get('key')"
matInput matInput
@ -103,15 +101,14 @@
</mat-form-field> </mat-form-field>
} }
@if (argumentControl.get('type').value && argumentControl.get('type').value !== ArgumentType.CONSTANT) { @if (argumentControl.get('type').value && argumentControl.get('type').value !== ArgumentType.CONSTANT) {
<mat-form-field <mat-form-field floatLabel="always" class="mat-block gt-xs:max-w-50% gt-xs:flex-full" appearance="outline">
floatLabel="always" class="mat-block gt-xs:max-w-50% gt-xs:flex-full">
<mat-label translate>rule-node-config.default-value-field-input</mat-label> <mat-label translate>rule-node-config.default-value-field-input</mat-label>
<input [formControl]="argumentControl.get('defaultValue')" matInput step="1" type="number"/> <input [formControl]="argumentControl.get('defaultValue')" matInput step="1" type="number"/>
</mat-form-field> </mat-form-field>
} }
</div> </div>
@if (argumentControl.get('type').value === ArgumentType.ATTRIBUTE) { @if (argumentControl.get('type').value === ArgumentType.ATTRIBUTE) {
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.attribute-scope-field-input</mat-label> <mat-label translate>rule-node-config.attribute-scope-field-input</mat-label>
<mat-select required [formControl]="argumentControl.get('attributeScope')"> <mat-select required [formControl]="argumentControl.get('attributeScope')">
@for (scope of attributeScope; track scope) { @for (scope of attributeScope; track scope) {

6
ui-ngx/src/app/modules/home/components/rule-node/common/credentials-config.component.html

@ -24,7 +24,7 @@
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<ng-template matExpansionPanelContent> <ng-template matExpansionPanelContent>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.credentials-type</mat-label> <mat-label translate>rule-node-config.credentials-type</mat-label>
<mat-select formControlName="type" required> <mat-select formControlName="type" required>
@for (credentialsType of allCredentialsTypes; track credentialsType) { @for (credentialsType of allCredentialsTypes; track credentialsType) {
@ -44,7 +44,7 @@
<section class="flex flex-col"> <section class="flex flex-col">
@switch (credentialsConfigFormGroup.get('type').value) { @switch (credentialsConfigFormGroup.get('type').value) {
@case("basic") { @case("basic") {
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.username</mat-label> <mat-label translate>rule-node-config.username</mat-label>
<input required matInput formControlName="username"> <input required matInput formControlName="username">
@if (credentialsConfigFormGroup.get('username').hasError('required')) { @if (credentialsConfigFormGroup.get('username').hasError('required')) {
@ -53,7 +53,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.password</mat-label> <mat-label translate>rule-node-config.password</mat-label>
<input type="password" [required]="passwordFieldRequired" matInput formControlName="password" autocomplete="new-password"> <input type="password" [required]="passwordFieldRequired" matInput formControlName="password" autocomplete="new-password">
<tb-toggle-password matSuffix></tb-toggle-password> <tb-toggle-password matSuffix></tb-toggle-password>

6
ui-ngx/src/app/modules/home/components/rule-node/common/device-relations-query-config.component.html

@ -17,7 +17,7 @@
--> -->
<section class="flex flex-col" [formGroup]="deviceRelationsQueryFormGroup"> <section class="flex flex-col" [formGroup]="deviceRelationsQueryFormGroup">
<div class="flex flex-row gap-5"> <div class="flex flex-row gap-5">
<mat-form-field class="mat-block max-w-50% flex-full" subscriptSizing="dynamic" style="min-width: 100px;" hideRequiredMarker> <mat-form-field class="mat-block max-w-50% flex-full" subscriptSizing="dynamic" style="min-width: 100px;" hideRequiredMarker appearance="outline">
<mat-label translate>relation.direction</mat-label> <mat-label translate>relation.direction</mat-label>
<mat-select required formControlName="direction"> <mat-select required formControlName="direction">
@for (type of directionTypes; track type) { @for (type of directionTypes; track type) {
@ -27,7 +27,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field floatLabel="always" class="mat-block max-w-50% flex-full"> <mat-form-field floatLabel="always" class="mat-block max-w-50% flex-full" appearance="outline">
<mat-label translate>rule-node-config.max-relation-level</mat-label> <mat-label translate>rule-node-config.max-relation-level</mat-label>
<input matInput <input matInput
type="text" type="text"
@ -57,10 +57,12 @@
</div> </div>
} }
<tb-relation-type-autocomplete <tb-relation-type-autocomplete
appearance="outline"
class="flex-1" class="flex-1"
formControlName="relationType"> formControlName="relationType">
</tb-relation-type-autocomplete> </tb-relation-type-autocomplete>
<tb-entity-subtype-list <tb-entity-subtype-list
appearance="outline"
required required
[label]="'rule-node-config.device-profiles' | translate" [label]="'rule-node-config.device-profiles' | translate"
[entityType]="entityType.DEVICE" [entityType]="entityType.DEVICE"

4
ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config-old.component.html

@ -31,7 +31,7 @@
<div class="row flex flex-row items-center justify-start gap-2" <div class="row flex flex-row items-center justify-start gap-2"
formArrayName="keyVals" formArrayName="keyVals"
> >
<mat-form-field class="cell mat-block flex-1"> <mat-form-field class="cell mat-block flex-1" appearance="outline">
<input [formControl]="keyValControl.get('key')" matInput required <input [formControl]="keyValControl.get('key')" matInput required
placeholder="{{ keyText | translate }}"/> placeholder="{{ keyText | translate }}"/>
@if (keyValControl.get('key').hasError('required')) { @if (keyValControl.get('key').hasError('required')) {
@ -40,7 +40,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="cell mat-block flex-1"> <mat-form-field class="cell mat-block flex-1" appearance="outline">
<input [formControl]="keyValControl.get('value')" matInput required <input [formControl]="keyValControl.get('value')" matInput required
placeholder="{{ valText | translate }}"/> placeholder="{{ valText | translate }}"/>
@if (keyValControl.get('value').hasError('required')) { @if (keyValControl.get('value').hasError('required')) {

9
ui-ngx/src/app/modules/home/components/rule-node/common/kv-map-config.component.html

@ -47,8 +47,7 @@
</div> </div>
<div class="tb-form-table-body"> <div class="tb-form-table-body">
@for (keyValControl of keyValsFormArray().controls; track keyValControl) { @for (keyValControl of keyValsFormArray().controls; track keyValControl) {
<div class="tb-form-table-row" <div class="tb-form-table-row">
>
<mat-form-field class="tb-inline-field field-space" appearance="outline" subscriptSizing="dynamic"> <mat-form-field class="tb-inline-field field-space" appearance="outline" subscriptSizing="dynamic">
<input [placeholder]="keyText+'*'" [formControl]="keyValControl.get('key')" matInput/> <input [placeholder]="keyText+'*'" [formControl]="keyValControl.get('key')" matInput/>
</mat-form-field> </mat-form-field>
@ -56,7 +55,7 @@
<input [placeholder]="valText+'*'" [formControl]="keyValControl.get('value')" matInput/> <input [placeholder]="valText+'*'" [formControl]="keyValControl.get('value')" matInput/>
</mat-form-field> </mat-form-field>
<div class="tb-form-table-row-cell-buttons"> <div class="tb-form-table-row-cell-buttons">
<button [class.tb-hidden]="keyValsFormArray().controls.length === 1" <button [class.tb-hidden]="required && keyValsFormArray().controls.length === 1"
type="button" type="button"
mat-icon-button mat-icon-button
(click)="removeKeyVal($index)" (click)="removeKeyVal($index)"
@ -67,6 +66,10 @@
</button> </button>
</div> </div>
</div> </div>
} @empty {
@if (emptyText) {
<div class="tb-prompt flex items-center justify-center">{{ emptyText }}</div>
}
} }
</div> </div>
</div> </div>

4
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() hintText: string;
@Input() emptyText: string;
@Input() popupHelpLink: string; @Input() popupHelpLink: string;
@Input() @Input()
@ -171,7 +173,7 @@ export class KvMapConfigComponent implements ControlValueAccessor, OnInit, Valid
}; };
writeValue(keyValMap: { [key: string]: string }): void { 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) { if (this.keyValsFormArray().length === keyValuesData.length) {
this.keyValsFormArray().patchValue(keyValuesData, {emitEvent: false}); this.keyValsFormArray().patchValue(keyValuesData, {emitEvent: false});
} else { } else {

2
ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field [formGroup]="mathFunctionForm" class="mat-block"> <mat-form-field [formGroup]="mathFunctionForm" class="mat-block" [appearance]="appearance">
<mat-label translate>rule-node-config.functions-field-input</mat-label> <mat-label translate>rule-node-config.functions-field-input</mat-label>
<input [required]="required" <input [required]="required"
(focusin)="onFocus()" (focusin)="onFocus()"

4
ui-ngx/src/app/modules/home/components/rule-node/common/math-function-autocomplete.component.ts

@ -21,6 +21,7 @@ import { TranslateService } from '@ngx-translate/core';
import { FunctionData, MathFunction, MathFunctionMap } from '../rule-node-config.models'; import { FunctionData, MathFunction, MathFunctionMap } from '../rule-node-config.models';
import { map, tap } from 'rxjs/operators'; import { map, tap } from 'rxjs/operators';
import { coerceBooleanProperty } from '@angular/cdk/coercion'; import { coerceBooleanProperty } from '@angular/cdk/coercion';
import { MatFormFieldAppearance } from '@angular/material/form-field';
@Component({ @Component({
selector: 'tb-math-function-autocomplete', selector: 'tb-math-function-autocomplete',
@ -50,6 +51,9 @@ export class MathFunctionAutocompleteComponent implements ControlValueAccessor,
@Input() disabled: boolean; @Input() disabled: boolean;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@ViewChild('operationInput', {static: true}) operationInput: ElementRef; @ViewChild('operationInput', {static: true}) operationInput: ElementRef;
mathFunctionForm: UntypedFormGroup; mathFunctionForm: UntypedFormGroup;

2
ui-ngx/src/app/modules/home/components/rule-node/common/message-types-config.component.html

@ -15,7 +15,7 @@
limitations under the License. limitations under the License.
--> -->
<mat-form-field [formGroup]="messageTypeConfigForm" style="width: 100%;"> <mat-form-field [formGroup]="messageTypeConfigForm" style="width: 100%;" appearance="outline">
@if (label) { @if (label) {
<mat-label>{{ label }}</mat-label> <mat-label>{{ label }}</mat-label>
} }

4
ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.html

@ -16,7 +16,7 @@
--> -->
<section class="tb-form-row no-border no-padding tb-standard-fields column-xs" [formGroup]="messageTypeFormGroup"> <section class="tb-form-row no-border no-padding tb-standard-fields column-xs" [formGroup]="messageTypeFormGroup">
<mat-form-field class="flex" subscriptSizing="{{ subscriptSizing }}" hideRequiredMarker> <mat-form-field class="flex" subscriptSizing="{{ subscriptSizing }}" hideRequiredMarker [appearance]="appearance">
<mat-label>{{'rule-node-config.output-message-type' | translate}}</mat-label> <mat-label>{{'rule-node-config.output-message-type' | translate}}</mat-label>
<mat-select formControlName="messageTypeAlias"> <mat-select formControlName="messageTypeAlias">
@for (msgType of messageTypes; track msgType) { @for (msgType of messageTypes; track msgType) {
@ -26,7 +26,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="flex" subscriptSizing="{{ subscriptSizing }}" [hideRequiredMarker]="messageTypeFormGroup.get('messageType').disabled"> <mat-form-field class="flex" subscriptSizing="{{ subscriptSizing }}" [hideRequiredMarker]="messageTypeFormGroup.get('messageType').disabled" [appearance]="appearance">
<mat-label>{{'rule-node-config.message-type-value' | translate}}</mat-label> <mat-label>{{'rule-node-config.message-type-value' | translate}}</mat-label>
<input matInput type="text" formControlName="messageType"> <input matInput type="text" formControlName="messageType">
@if (messageTypeFormGroup.get('messageType').value) { @if (messageTypeFormGroup.get('messageType').value) {

5
ui-ngx/src/app/modules/home/components/rule-node/common/output-message-type-autocomplete.component.ts

@ -24,7 +24,7 @@ import {
Validator, Validator,
Validators Validators
} from '@angular/forms'; } 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 { coerceBoolean } from '@shared/public-api';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop'; import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
@ -61,6 +61,9 @@ export class OutputMessageTypeAutocompleteComponent implements ControlValueAcces
@coerceBoolean() @coerceBoolean()
disabled: boolean; disabled: boolean;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input() @Input()
@coerceBoolean() @coerceBoolean()
set required(value) { set required(value) {

4
ui-ngx/src/app/modules/home/components/rule-node/common/relations-query-config.component.html

@ -19,7 +19,7 @@
<div class="tb-form-panel-title tb-required" translate>rule-node-config.relations-query</div> <div class="tb-form-panel-title tb-required" translate>rule-node-config.relations-query</div>
<section> <section>
<div class="flex flex-row gap-4"> <div class="flex flex-row gap-4">
<mat-form-field class="mat-block max-w-50% flex-full" style="min-width: 100px;" hideRequiredMarker> <mat-form-field class="mat-block max-w-50% flex-full" style="min-width: 100px;" hideRequiredMarker appearance="outline">
<mat-label translate>relation.direction</mat-label> <mat-label translate>relation.direction</mat-label>
<mat-select required formControlName="direction"> <mat-select required formControlName="direction">
@for (type of directionTypes; track type) { @for (type of directionTypes; track type) {
@ -29,7 +29,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field floatLabel="always" class="mat-block max-w-50% flex-full"> <mat-form-field floatLabel="always" class="mat-block max-w-50% flex-full" appearance="outline">
<mat-label translate>rule-node-config.max-relation-level</mat-label> <mat-label translate>rule-node-config.max-relation-level</mat-label>
<input matInput <input matInput
type="text" type="text"

4
ui-ngx/src/app/modules/home/components/rule-node/common/select-attributes.component.html

@ -20,24 +20,28 @@
[popupHelpLink]="popupHelpLink"></tb-example-hint> [popupHelpLink]="popupHelpLink"></tb-example-hint>
<tb-string-items-list subscriptSizing="dynamic" class="mat-block" editable <tb-string-items-list subscriptSizing="dynamic" class="mat-block" editable
(focusout)="onTouched()" (focusout)="onTouched()"
appearance="outline"
[placeholder]="'rule-node-config.add-attribute-key' | translate" [placeholder]="'rule-node-config.add-attribute-key' | translate"
[label]="'rule-node-config.client-attributes' | translate" formControlName="clientAttributeNames"> [label]="'rule-node-config.client-attributes' | translate" formControlName="clientAttributeNames">
<ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container> <ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container>
</tb-string-items-list> </tb-string-items-list>
<tb-string-items-list subscriptSizing="dynamic" class="mat-block" editable <tb-string-items-list subscriptSizing="dynamic" class="mat-block" editable
(focusout)="onTouched()" (focusout)="onTouched()"
appearance="outline"
[placeholder]="'rule-node-config.add-attribute-key' | translate" [placeholder]="'rule-node-config.add-attribute-key' | translate"
[label]="'rule-node-config.shared-attributes' | translate" formControlName="sharedAttributeNames"> [label]="'rule-node-config.shared-attributes' | translate" formControlName="sharedAttributeNames">
<ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container> <ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container>
</tb-string-items-list> </tb-string-items-list>
<tb-string-items-list subscriptSizing="dynamic" class="mat-block" editable <tb-string-items-list subscriptSizing="dynamic" class="mat-block" editable
(focusout)="onTouched()" (focusout)="onTouched()"
appearance="outline"
[placeholder]="'rule-node-config.add-attribute-key' | translate" [placeholder]="'rule-node-config.add-attribute-key' | translate"
[label]="'rule-node-config.server-attributes' | translate" formControlName="serverAttributeNames"> [label]="'rule-node-config.server-attributes' | translate" formControlName="serverAttributeNames">
<ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container> <ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container>
</tb-string-items-list> </tb-string-items-list>
<tb-string-items-list subscriptSizing="dynamic" editable class="mat-block" <tb-string-items-list subscriptSizing="dynamic" editable class="mat-block"
(focusout)="onTouched()" (focusout)="onTouched()"
appearance="outline"
[placeholder]="'rule-node-config.add-telemetry-key' | translate" [placeholder]="'rule-node-config.add-telemetry-key' | translate"
[label]="'rule-node-config.latest-telemetry' | translate" formControlName="latestTsKeyNames"> [label]="'rule-node-config.latest-telemetry' | translate" formControlName="latestTsKeyNames">
<ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container> <ng-container matSuffix *ngTemplateOutlet="helpIcon"></ng-container>

8
ui-ngx/src/app/modules/home/components/rule-node/enrichment/calculate-delta-config.component.html

@ -17,7 +17,7 @@
--> -->
<section [formGroup]="calculateDeltaConfigForm"> <section [formGroup]="calculateDeltaConfigForm">
<div class="gt-sm:flex gt-sm:flex-row gt-sm:gap-5.5"> <div class="gt-sm:flex gt-sm:flex-row gt-sm:gap-5.5">
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.input-value-key' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.input-value-key' | translate }}</mat-label>
<input matInput formControlName="inputValueKey"> <input matInput formControlName="inputValueKey">
@if (calculateDeltaConfigForm.get('inputValueKey').hasError('required') || @if (calculateDeltaConfigForm.get('inputValueKey').hasError('required') ||
@ -27,7 +27,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.output-value-key' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.output-value-key' | translate }}</mat-label>
<input matInput formControlName="outputValueKey"> <input matInput formControlName="outputValueKey">
@if (calculateDeltaConfigForm.get('outputValueKey').hasError('required') || @if (calculateDeltaConfigForm.get('outputValueKey').hasError('required') ||
@ -38,7 +38,7 @@
} }
</mat-form-field> </mat-form-field>
</div> </div>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.number-of-digits-after-floating-point' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.number-of-digits-after-floating-point' | translate }}</mat-label>
<input type="number" min="0" max="15" step="1" matInput formControlName="round"> <input type="number" min="0" max="15" step="1" matInput formControlName="round">
@if (calculateDeltaConfigForm.get('round').hasError('min')) { @if (calculateDeltaConfigForm.get('round').hasError('min')) {
@ -80,7 +80,7 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
@if (calculateDeltaConfigForm.get('addPeriodBetweenMsgs').value) { @if (calculateDeltaConfigForm.get('addPeriodBetweenMsgs').value) {
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ 'rule-node-config.period-value-key' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.period-value-key' | translate }}</mat-label>
<input required matInput formControlName="periodValueKey"> <input required matInput formControlName="periodValueKey">
@if (calculateDeltaConfigForm.get('periodValueKey').hasError('required')) { @if (calculateDeltaConfigForm.get('periodValueKey').hasError('required')) {

1
ui-ngx/src/app/modules/home/components/rule-node/enrichment/entity-details-config.component.html

@ -18,6 +18,7 @@
<section [formGroup]="entityDetailsConfigForm"> <section [formGroup]="entityDetailsConfigForm">
<tb-string-items-list [predefinedValues]="predefinedValues" <tb-string-items-list [predefinedValues]="predefinedValues"
class="mat-block" required class="mat-block" required
appearance="outline"
[label]="'rule-node-config.select-details' | translate" [label]="'rule-node-config.select-details' | translate"
[placeholder]="'rule-node-config.add-detail' | translate" [placeholder]="'rule-node-config.add-detail' | translate"
[requiredText]="'rule-node-config.entity-details-list-empty' | translate" [requiredText]="'rule-node-config.entity-details-list-empty' | translate"

22
ui-ngx/src/app/modules/home/components/rule-node/enrichment/get-telemetry-from-database-config.component.html

@ -18,6 +18,7 @@
<section [formGroup]="getTelemetryFromDatabaseConfigForm" class="tb-form-panel no-padding no-border"> <section [formGroup]="getTelemetryFromDatabaseConfigForm" class="tb-form-panel no-padding no-border">
<tb-string-items-list class="mat-block" editable [placeholder]="'rule-node-config.add-timeseries-key' | translate" <tb-string-items-list class="mat-block" editable [placeholder]="'rule-node-config.add-timeseries-key' | translate"
subscriptSizing="dynamic" required subscriptSizing="dynamic" required
appearance="outline"
[requiredText]="'rule-node-config.timeseries-keys-required' | translate" [requiredText]="'rule-node-config.timeseries-keys-required' | translate"
[label]="'rule-node-config.timeseries-keys' | translate" formControlName="latestTsKeyNames" [label]="'rule-node-config.timeseries-keys' | translate" formControlName="latestTsKeyNames"
[hint]="'rule-node-config.general-pattern-hint' | translate"> [hint]="'rule-node-config.general-pattern-hint' | translate">
@ -40,10 +41,9 @@
</mat-slide-toggle> </mat-slide-toggle>
</div> </div>
@if (getTelemetryFromDatabaseConfigForm.get('useMetadataIntervalPatterns').value === false) { @if (getTelemetryFromDatabaseConfigForm.get('useMetadataIntervalPatterns').value === false) {
<div formGroupName="interval" class="flex flex-col" <div formGroupName="interval" class="flex flex-col">
>
<div class="flex flex-col gap-0 gt-sm:flex-row gt-sm:gap-4"> <div class="flex flex-col gap-0 gt-sm:flex-row gt-sm:gap-4">
<mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full"> <mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full" appearance="outline">
<mat-label>{{ 'rule-node-config.interval-start' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.interval-start' | translate }}</mat-label>
<input type="number" step="1" min="1" max="2147483647" matInput formControlName="startInterval" required> <input type="number" step="1" min="1" max="2147483647" matInput formControlName="startInterval" required>
@if (getTelemetryFromDatabaseConfigForm.get('interval.startInterval').hasError('required')) { @if (getTelemetryFromDatabaseConfigForm.get('interval.startInterval').hasError('required')) {
@ -62,7 +62,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full" hideRequiredMarker> <mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full" hideRequiredMarker appearance="outline">
<mat-label>{{ 'rule-node-config.time-unit' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.time-unit' | translate }}</mat-label>
<mat-select formControlName="startIntervalTimeUnit" required> <mat-select formControlName="startIntervalTimeUnit" required>
@for (timeUnit of timeUnits; track timeUnit) { @for (timeUnit of timeUnits; track timeUnit) {
@ -74,7 +74,7 @@
</mat-form-field> </mat-form-field>
</div> </div>
<div class="flex flex-col gap-0 gt-sm:flex-row gt-sm:gap-4"> <div class="flex flex-col gap-0 gt-sm:flex-row gt-sm:gap-4">
<mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full"> <mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full" appearance="outline">
<mat-label>{{ 'rule-node-config.interval-end' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.interval-end' | translate }}</mat-label>
<input type="number" step="1" min="1" max="2147483647" matInput formControlName="endInterval" required> <input type="number" step="1" min="1" max="2147483647" matInput formControlName="endInterval" required>
@if (getTelemetryFromDatabaseConfigForm.get('interval.endInterval').hasError('required')) { @if (getTelemetryFromDatabaseConfigForm.get('interval.endInterval').hasError('required')) {
@ -93,7 +93,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full" hideRequiredMarker> <mat-form-field class="mat-block gt-sm:max-w-50% gt-sm:flex-full" hideRequiredMarker appearance="outline">
<mat-label>{{ 'rule-node-config.time-unit' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.time-unit' | translate }}</mat-label>
<mat-select formControlName="endIntervalTimeUnit" required> <mat-select formControlName="endIntervalTimeUnit" required>
@for (timeUnit of timeUnits; track timeUnit) { @for (timeUnit of timeUnits; track timeUnit) {
@ -123,7 +123,7 @@
</div> </div>
} @else { } @else {
<div class="input-block flex flex-col"> <div class="input-block flex flex-col">
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.start-interval' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.start-interval' | translate }}</mat-label>
<input matInput formControlName="startIntervalPattern" required> <input matInput formControlName="startIntervalPattern" required>
@if (getTelemetryFromDatabaseConfigForm.get('startIntervalPattern').hasError('required') || @if (getTelemetryFromDatabaseConfigForm.get('startIntervalPattern').hasError('required') ||
@ -133,7 +133,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label>{{ 'rule-node-config.end-interval' | translate }}</mat-label> <mat-label>{{ 'rule-node-config.end-interval' | translate }}</mat-label>
<input matInput formControlName="endIntervalPattern" required> <input matInput formControlName="endIntervalPattern" required>
@if (getTelemetryFromDatabaseConfigForm.get('endIntervalPattern').hasError('required') || @if (getTelemetryFromDatabaseConfigForm.get('endIntervalPattern').hasError('required') ||
@ -167,7 +167,7 @@
</div> </div>
@if (getTelemetryFromDatabaseConfigForm.get('fetchMode').value === fetchMode.ALL) { @if (getTelemetryFromDatabaseConfigForm.get('fetchMode').value === fetchMode.ALL) {
<div> <div>
<mat-form-field class="mat-block" hideRequiredMarker <mat-form-field class="mat-block" hideRequiredMarker appearance="outline"
[subscriptSizing]="defaultPaddingEnable() ? 'fixed' : 'dynamic'"> [subscriptSizing]="defaultPaddingEnable() ? 'fixed' : 'dynamic'">
<mat-label>{{ 'aggregation.function' | translate }}</mat-label> <mat-label>{{ 'aggregation.function' | translate }}</mat-label>
<mat-select formControlName="aggregation" required> <mat-select formControlName="aggregation" required>
@ -180,7 +180,7 @@
</mat-form-field> </mat-form-field>
@if (getTelemetryFromDatabaseConfigForm.get('aggregation').value === aggregationTypes.NONE) { @if (getTelemetryFromDatabaseConfigForm.get('aggregation').value === aggregationTypes.NONE) {
<div> <div>
<mat-form-field class="mat-block" hideRequiredMarker> <mat-form-field class="mat-block" hideRequiredMarker appearance="outline">
<mat-label> {{ "rule-node-config.order-by-timestamp" | translate }} </mat-label> <mat-label> {{ "rule-node-config.order-by-timestamp" | translate }} </mat-label>
<mat-select formControlName="orderBy" required> <mat-select formControlName="orderBy" required>
@for (order of samplingOrders; track order) { @for (order of samplingOrders; track order) {
@ -190,7 +190,7 @@
} }
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label>{{ "rule-node-config.limit" | translate }}</mat-label> <mat-label>{{ "rule-node-config.limit" | translate }}</mat-label>
<input type="number" min="2" max="1000" step="1" matInput formControlName="limit" required> <input type="number" min="2" max="1000" step="1" matInput formControlName="limit" required>
<mat-hint>{{ "rule-node-config.limit-hint" | translate }}</mat-hint> <mat-hint>{{ "rule-node-config.limit-hint" | translate }}</mat-hint>

2
ui-ngx/src/app/modules/home/components/rule-node/external/ai-config.component.html

@ -82,6 +82,7 @@
</mat-form-field> </mat-form-field>
<tb-entity-list <tb-entity-list
class="flex-1" class="flex-1"
appearance="outline"
syncIdsWithDB syncIdsWithDB
allowCreateNew allowCreateNew
placeholderText="{{ 'rule-node-config.ai.ai-resources' | translate }}" placeholderText="{{ 'rule-node-config.ai.ai-resources' | translate }}"
@ -133,6 +134,7 @@
<tb-time-unit-input <tb-time-unit-input
required required
inlineField inlineField
appearance="outline"
requiredText="{{ 'rule-node-config.ai.timeout-required' | translate }}" requiredText="{{ 'rule-node-config.ai.timeout-required' | translate }}"
minErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}" minErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}"
maxErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}" maxErrorText="{{ 'rule-node-config.ai.timeout-validation' | translate }}"

14
ui-ngx/src/app/modules/home/components/rule-node/external/azure-iot-hub-config.component.html

@ -16,7 +16,7 @@
--> -->
<section [formGroup]="azureIotHubConfigForm" class="flex flex-col"> <section [formGroup]="azureIotHubConfigForm" class="flex flex-col">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.topic</mat-label> <mat-label translate>rule-node-config.topic</mat-label>
<input required matInput formControlName="topicPattern"> <input required matInput formControlName="topicPattern">
@if (azureIotHubConfigForm.get('topicPattern').hasError('required')) { @if (azureIotHubConfigForm.get('topicPattern').hasError('required')) {
@ -26,7 +26,7 @@
} }
<mat-hint translate>rule-node-config.general-pattern-hint</mat-hint> <mat-hint translate>rule-node-config.general-pattern-hint</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.hostname</mat-label> <mat-label translate>rule-node-config.hostname</mat-label>
<input required matInput formControlName="host"> <input required matInput formControlName="host">
@if (azureIotHubConfigForm.get('host').hasError('required')) { @if (azureIotHubConfigForm.get('host').hasError('required')) {
@ -35,7 +35,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.device-id</mat-label> <mat-label translate>rule-node-config.device-id</mat-label>
<input required matInput formControlName="clientId" autocomplete="new-clientId"> <input required matInput formControlName="clientId" autocomplete="new-clientId">
@if (azureIotHubConfigForm.get('clientId').hasError('required')) { @if (azureIotHubConfigForm.get('clientId').hasError('required')) {
@ -44,7 +44,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<tb-mqtt-version-select formControlName="protocolVersion" subscriptSizing="fixed" <tb-mqtt-version-select formControlName="protocolVersion" subscriptSizing="fixed" appearance="outline"
[excludeVersions]="[MqttVersion.MQTT_3_1, MqttVersion.MQTT_5]"> [excludeVersions]="[MqttVersion.MQTT_3_1, MqttVersion.MQTT_5]">
</tb-mqtt-version-select> </tb-mqtt-version-select>
<mat-accordion> <mat-accordion>
@ -56,7 +56,7 @@
</mat-panel-description> </mat-panel-description>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<section formGroupName="credentials" class="flex flex-col"> <section formGroupName="credentials" class="flex flex-col">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.credentials-type</mat-label> <mat-label translate>rule-node-config.credentials-type</mat-label>
<mat-select formControlName="type" required> <mat-select formControlName="type" required>
@for (credentialsType of allAzureIotHubCredentialsTypes; track credentialsType) { @for (credentialsType of allAzureIotHubCredentialsTypes; track credentialsType) {
@ -75,7 +75,7 @@
@switch (azureIotHubConfigForm.get('credentials.type').value) { @switch (azureIotHubConfigForm.get('credentials.type').value) {
@case ('anonymous') {} @case ('anonymous') {}
@case ('sas') { @case ('sas') {
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.sas-key</mat-label> <mat-label translate>rule-node-config.sas-key</mat-label>
<input type="password" required matInput formControlName="sasKey" autocomplete="new-password"> <input type="password" required matInput formControlName="sasKey" autocomplete="new-password">
<tb-toggle-password matSuffix></tb-toggle-password> <tb-toggle-password matSuffix></tb-toggle-password>
@ -124,7 +124,7 @@
noFileText="rule-node-config.no-file" noFileText="rule-node-config.no-file"
dropLabel="{{'rule-node-config.drop-file' | translate}}"> dropLabel="{{'rule-node-config.drop-file' | translate}}">
</tb-file-input> </tb-file-input>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.private-key-password</mat-label> <mat-label translate>rule-node-config.private-key-password</mat-label>
<input type="password" matInput formControlName="password" autocomplete="new-password"> <input type="password" matInput formControlName="password" autocomplete="new-password">
<tb-toggle-password matSuffix></tb-toggle-password> <tb-toggle-password matSuffix></tb-toggle-password>

31
ui-ngx/src/app/modules/home/components/rule-node/external/kafka-config.component.html

@ -16,7 +16,7 @@
--> -->
<section [formGroup]="kafkaConfigForm" class="flex flex-col"> <section [formGroup]="kafkaConfigForm" class="flex flex-col">
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.topic-pattern</mat-label> <mat-label translate>rule-node-config.topic-pattern</mat-label>
<input required matInput formControlName="topicPattern"> <input required matInput formControlName="topicPattern">
@if (kafkaConfigForm.get('topicPattern').hasError('required')) { @if (kafkaConfigForm.get('topicPattern').hasError('required')) {
@ -26,13 +26,13 @@
} }
<mat-hint translate>rule-node-config.general-pattern-hint</mat-hint> <mat-hint translate>rule-node-config.general-pattern-hint</mat-hint>
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block" subscriptSizing="dynamic"> <mat-form-field class="mat-block" subscriptSizing="dynamic" appearance="outline">
<mat-label translate>rule-node-config.key-pattern</mat-label> <mat-label translate>rule-node-config.key-pattern</mat-label>
<input matInput formControlName="keyPattern"> <input matInput formControlName="keyPattern">
<mat-hint translate>rule-node-config.general-pattern-hint</mat-hint> <mat-hint translate>rule-node-config.general-pattern-hint</mat-hint>
</mat-form-field> </mat-form-field>
<div class="tb-hint" translate>rule-node-config.key-pattern-hint</div> <div class="tb-hint" translate>rule-node-config.key-pattern-hint</div>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.bootstrap-servers</mat-label> <mat-label translate>rule-node-config.bootstrap-servers</mat-label>
<input required matInput formControlName="bootstrapServers"> <input required matInput formControlName="bootstrapServers">
@if (kafkaConfigForm.get('bootstrapServers').hasError('required')) { @if (kafkaConfigForm.get('bootstrapServers').hasError('required')) {
@ -41,7 +41,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.retries</mat-label> <mat-label translate>rule-node-config.retries</mat-label>
<input type="number" step="1" min="0" matInput formControlName="retries"> <input type="number" step="1" min="0" matInput formControlName="retries">
@if (kafkaConfigForm.get('retries').hasError('min')) { @if (kafkaConfigForm.get('retries').hasError('min')) {
@ -50,7 +50,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.batch-size-bytes</mat-label> <mat-label translate>rule-node-config.batch-size-bytes</mat-label>
<input type="number" step="1" min="0" matInput formControlName="batchSize"> <input type="number" step="1" min="0" matInput formControlName="batchSize">
@if (kafkaConfigForm.get('batchSize').hasError('min')) { @if (kafkaConfigForm.get('batchSize').hasError('min')) {
@ -59,7 +59,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.linger-ms</mat-label> <mat-label translate>rule-node-config.linger-ms</mat-label>
<input type="number" step="1" min="0" matInput formControlName="linger"> <input type="number" step="1" min="0" matInput formControlName="linger">
@if (kafkaConfigForm.get('linger').hasError('min')) { @if (kafkaConfigForm.get('linger').hasError('min')) {
@ -68,7 +68,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.buffer-memory-bytes</mat-label> <mat-label translate>rule-node-config.buffer-memory-bytes</mat-label>
<input type="number" step="1" min="0" matInput formControlName="bufferMemory"> <input type="number" step="1" min="0" matInput formControlName="bufferMemory">
@if (kafkaConfigForm.get('bufferMemory').hasError('min')) { @if (kafkaConfigForm.get('bufferMemory').hasError('min')) {
@ -77,7 +77,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.acks</mat-label> <mat-label translate>rule-node-config.acks</mat-label>
<mat-select formControlName="acks" required> <mat-select formControlName="acks" required>
@for (ackValue of ackValues; track ackValue) { @for (ackValue of ackValues; track ackValue) {
@ -88,20 +88,21 @@
</mat-select> </mat-select>
</mat-form-field> </mat-form-field>
<label translate class="tb-title">rule-node-config.other-properties</label> <label translate class="tb-title">rule-node-config.other-properties</label>
<tb-kv-map-config-old <tb-kv-map-config
required="false" required="false"
formControlName="otherProperties" formControlName="otherProperties"
keyText="rule-node-config.key" [keyText]="'rule-node-config.key' | translate"
keyRequiredText="rule-node-config.key-required" [keyRequiredText]="'rule-node-config.key-required' | translate"
valText="rule-node-config.value" [valText]="'rule-node-config.value' | translate"
valRequiredText="rule-node-config.value-required"> [valRequiredText]="'rule-node-config.value-required' | translate"
</tb-kv-map-config-old> [emptyText]="'rule-node-config.other-properties-empty' | translate">
</tb-kv-map-config>
<mat-checkbox class="flex-1" formControlName="addMetadataKeyValuesAsKafkaHeaders"> <mat-checkbox class="flex-1" formControlName="addMetadataKeyValuesAsKafkaHeaders">
{{ 'rule-node-config.add-metadata-key-values-as-kafka-headers' | translate }} {{ 'rule-node-config.add-metadata-key-values-as-kafka-headers' | translate }}
</mat-checkbox> </mat-checkbox>
<div class="tb-hint" translate>rule-node-config.add-metadata-key-values-as-kafka-headers-hint</div> <div class="tb-hint" translate>rule-node-config.add-metadata-key-values-as-kafka-headers-hint</div>
@if (kafkaConfigForm.get('addMetadataKeyValuesAsKafkaHeaders').value) { @if (kafkaConfigForm.get('addMetadataKeyValuesAsKafkaHeaders').value) {
<mat-form-field class="mat-block flex-1"> <mat-form-field class="mat-block flex-1" appearance="outline">
<mat-label translate>rule-node-config.charset-encoding</mat-label> <mat-label translate>rule-node-config.charset-encoding</mat-label>
<mat-select formControlName="kafkaHeadersCharset" required> <mat-select formControlName="kafkaHeadersCharset" required>
@for (charset of ToByteStandartCharsetTypesValues; track charset) { @for (charset of ToByteStandartCharsetTypesValues; track charset) {

14
ui-ngx/src/app/modules/home/components/rule-node/external/lambda-config.component.html

@ -24,7 +24,7 @@
popupHelpLink="rulenode/node-templatization-doc"> popupHelpLink="rulenode/node-templatization-doc">
</tb-example-hint> </tb-example-hint>
<div class="tb-standard-fields"> <div class="tb-standard-fields">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label>{{'rule-node-config.function-name' | translate}}</mat-label> <mat-label>{{'rule-node-config.function-name' | translate}}</mat-label>
<input required matInput formControlName="functionName"> <input required matInput formControlName="functionName">
@if (lambdaConfigForm.get('functionName').hasError('required')) { @if (lambdaConfigForm.get('functionName').hasError('required')) {
@ -33,7 +33,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label>{{'rule-node-config.qualifier' | translate}}</mat-label> <mat-label>{{'rule-node-config.qualifier' | translate}}</mat-label>
<input matInput formControlName="qualifier"> <input matInput formControlName="qualifier">
<mat-hint translate>rule-node-config.qualifier-hint</mat-hint> <mat-hint translate>rule-node-config.qualifier-hint</mat-hint>
@ -47,7 +47,7 @@
<mat-panel-title class="tb-form-panel-title tb-required" translate>rule-node-config.aws-credentials</mat-panel-title> <mat-panel-title class="tb-form-panel-title tb-required" translate>rule-node-config.aws-credentials</mat-panel-title>
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class="tb-standard-fields"> <div class="tb-standard-fields">
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.aws-access-key-id</mat-label> <mat-label translate>rule-node-config.aws-access-key-id</mat-label>
<input required matInput formControlName="accessKey"> <input required matInput formControlName="accessKey">
@if (lambdaConfigForm.get('accessKey').hasError('required')) { @if (lambdaConfigForm.get('accessKey').hasError('required')) {
@ -56,7 +56,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.aws-secret-access-key</mat-label> <mat-label translate>rule-node-config.aws-secret-access-key</mat-label>
<input required matInput formControlName="secretKey"> <input required matInput formControlName="secretKey">
@if (lambdaConfigForm.get('secretKey').hasError('required')) { @if (lambdaConfigForm.get('secretKey').hasError('required')) {
@ -65,7 +65,7 @@
</mat-error> </mat-error>
} }
</mat-form-field> </mat-form-field>
<mat-form-field class="mat-block"> <mat-form-field class="mat-block" appearance="outline">
<mat-label translate>rule-node-config.aws-region</mat-label> <mat-label translate>rule-node-config.aws-region</mat-label>
<input required matInput formControlName="region"> <input required matInput formControlName="region">
@if (lambdaConfigForm.get('region').hasError('required')) { @if (lambdaConfigForm.get('region').hasError('required')) {
@ -84,7 +84,7 @@
</mat-expansion-panel-header> </mat-expansion-panel-header>
<div class="tb-form-panel no-border no-padding no-gap" style="margin-top: 0"> <div class="tb-form-panel no-border no-padding no-gap" style="margin-top: 0">
<div class="tb-form-row no-border same-padding tb-standard-fields"> <div class="tb-form-row no-border same-padding tb-standard-fields">
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.connection-timeout</mat-label> <mat-label translate>rule-node-config.connection-timeout</mat-label>
<input type="number" required min="0" matInput formControlName="connectionTimeout"> <input type="number" required min="0" matInput formControlName="connectionTimeout">
@if (lambdaConfigForm.get('connectionTimeout').hasError('required')) { @if (lambdaConfigForm.get('connectionTimeout').hasError('required')) {
@ -101,7 +101,7 @@
color="primary" color="primary"
matTooltip="{{ 'rule-node-config.connection-timeout-hint' | translate }}">help</mat-icon> matTooltip="{{ 'rule-node-config.connection-timeout-hint' | translate }}">help</mat-icon>
</mat-form-field> </mat-form-field>
<mat-form-field class="flex"> <mat-form-field class="flex" appearance="outline">
<mat-label translate>rule-node-config.request-timeout</mat-label> <mat-label translate>rule-node-config.request-timeout</mat-label>
<input type="number" required min="0" matInput formControlName="requestTimeout"> <input type="number" required min="0" matInput formControlName="requestTimeout">
@if (lambdaConfigForm.get('requestTimeout').hasError('required')) { @if (lambdaConfigForm.get('requestTimeout').hasError('required')) {

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save