Browse Source

Merge branch 'master' into feature/rule-node-debug-strategies

pull/11861/head
Yevhen Bondarenko 2 years ago
committed by GitHub
parent
commit
5beebb4959
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 2
      application/src/main/data/json/system/widget_types/rpc_remote_shell.json
  2. 3
      application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java
  3. 4
      application/src/main/resources/thingsboard.yml
  4. 3
      dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java
  5. 7
      dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java
  6. 2
      msa/js-executor/docker/Dockerfile
  7. 48
      msa/js-executor/package.json
  8. 4
      msa/js-executor/pom.xml
  9. 2
      msa/js-executor/queue/awsSqsTemplate.ts
  10. 2
      msa/js-executor/queue/serviceBusTemplate.ts
  11. 4310
      msa/js-executor/yarn.lock
  12. 4
      msa/vc-executor/src/main/resources/tb-vc-executor.yml
  13. 2
      msa/web-ui/docker/Dockerfile
  14. 39
      msa/web-ui/package.json
  15. 4
      msa/web-ui/pom.xml
  16. 1982
      msa/web-ui/yarn.lock
  17. 37
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java
  18. 32
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java
  19. 4
      transport/coap/src/main/resources/tb-coap-transport.yml
  20. 4
      transport/http/src/main/resources/tb-http-transport.yml
  21. 4
      transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml
  22. 4
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  23. 4
      transport/snmp/src/main/resources/tb-snmp-transport.yml
  24. 2
      ui-ngx/pom.xml
  25. 18
      ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts
  26. 2
      ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html
  27. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.scss
  28. 1
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.scss
  29. 3
      ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts
  30. 2
      ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html
  31. 3
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.html
  32. 8
      ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts
  33. 6
      ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts
  34. 2
      ui-ngx/src/app/modules/home/pages/customer/customer.component.html
  35. 3
      ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
  36. 1
      ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html
  37. 2
      ui-ngx/src/app/shared/components/country-autocomplete.component.html
  38. 5
      ui-ngx/src/app/shared/components/country-autocomplete.component.ts
  39. 4
      ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts
  40. 9
      ui-ngx/src/app/shared/import-export/import-export.service.ts
  41. 65
      ui-ngx/src/app/shared/models/ace/service-completion.models.ts
  42. 3
      ui-ngx/src/app/shared/models/entity.models.ts
  43. 4
      ui-ngx/src/styles.scss

2
application/src/main/data/json/system/widget_types/rpc_remote_shell.json

File diff suppressed because one or more lines are too long

3
application/src/main/java/org/thingsboard/server/service/update/DeprecationService.java

@ -55,7 +55,8 @@ public class DeprecationService {
}
}
log.warn("WARNING: {} queue type is deprecated and will be removed in ThingsBoard 4.0. Please migrate to Apache Kafka", queueTypeName);
log.warn("WARNING: Starting with ThingsBoard 4.0, {} will no longer be supported as a message queue for microservices. " +
"Please migrate to Apache Kafka. This change will not impact any rule nodes", queueTypeName);
notificationCenter.sendGeneralWebNotification(TenantId.SYS_TENANT_ID, new SystemAdministratorsFilter(),
DefaultNotifications.queueTypeDeprecation.toTemplate(), new GeneralNotificationInfo(Map.of(
"queueType", queueTypeName

4
application/src/main/resources/thingsboard.yml

@ -1453,7 +1453,9 @@ swagger:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:in-memory}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
# in-memory or kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:in-memory}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory:
stats:

3
dao/src/main/java/org/thingsboard/server/dao/notification/DefaultNotifications.java

@ -376,7 +376,8 @@ public class DefaultNotifications {
.name("Queue type deprecation")
.type(NotificationType.GENERAL)
.subject("WARNING: ${queueType} deprecation")
.text("${queueType} queue type is deprecated and will be removed in ThingsBoard 4.0. Please migrate to Apache Kafka")
.text("Starting with ThingsBoard 4.0, ${queueType} will no longer be supported as a message queue for microservices. " +
"Please migrate to Apache Kafka. This change will not impact any rule nodes.")
.icon("warning").color(RED_COLOR)
.build();

7
dao/src/test/java/org/thingsboard/server/dao/service/timeseries/sql/LatestTimeseriesPerformanceTest.java

@ -56,7 +56,8 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
private static final String LONG_KEY = "longKey";
private static final String DOUBLE_KEY = "doubleKey";
private static final String BOOLEAN_KEY = "booleanKey";
public static final int AMOUNT_OF_UNIQ_KEY = 10000;
private static final int AMOUNT_OF_UNIQ_KEY = 10000;
private static final int TIMEOUT = 100;
private final Random random = new Random();
@ -102,7 +103,7 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
futures.add(save(generateDblEntry(getRandomKey())));
futures.add(save(generateBoolEntry(getRandomKey())));
}
Futures.allAsList(futures).get(60, TimeUnit.SECONDS);
Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
long endTime = System.currentTimeMillis();
long totalTime = endTime - startTime;
@ -120,7 +121,7 @@ public class LatestTimeseriesPerformanceTest extends AbstractServiceTest {
futures.add(save(generateDblEntry(i)));
futures.add(save(generateBoolEntry(i)));
}
Futures.allAsList(futures).get(60, TimeUnit.SECONDS);
Futures.allAsList(futures).get(TIMEOUT, TimeUnit.SECONDS);
}
private ListenableFuture<?> save(TsKvEntry tsKvEntry) {

2
msa/js-executor/docker/Dockerfile

@ -14,7 +14,7 @@
# limitations under the License.
#
FROM thingsboard/node:16.20.2-bookworm-slim
FROM thingsboard/node:20.18.0-bookworm-slim
ENV NODE_ENV production
ENV DOCKER_MODE true

48
msa/js-executor/package.json

@ -6,26 +6,26 @@
"main": "server.ts",
"bin": "server.js",
"scripts": {
"pkg": "tsc && pkg -t node16-linux-x64,node16-win-x64 --out-path ./target ./target/src && node install.js",
"pkg": "tsc && pkg -t node18-linux-x64,node18-win-x64 --out-path ./target ./target/src && node install.js",
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --watch '.' --ext 'ts' --exec 'ts-node server.ts'",
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'NODE_ENV=production ts-node server.ts'",
"build": "tsc"
},
"dependencies": {
"@aws-sdk/client-sqs": "^3.121.0",
"@azure/service-bus": "^7.5.1",
"@google-cloud/pubsub": "^4.3.3",
"amqplib": "^0.10.0",
"config": "^3.3.7",
"express": "^4.18.1",
"@aws-sdk/client-sqs": "^3.682.0",
"@azure/service-bus": "^7.9.5",
"@google-cloud/pubsub": "^4.8.0",
"amqplib": "^0.10.4",
"config": "^3.3.12",
"express": "^4.21.1",
"js-yaml": "^4.1.0",
"kafkajs": "^2.1.0",
"long": "^5.2.0",
"kafkajs": "^2.2.4",
"long": "^5.2.3",
"uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2",
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
"winston": "^3.16.0",
"winston-daily-rotate-file": "^5.0.0"
},
"nyc": {
"exclude": [
@ -36,26 +36,20 @@
]
},
"devDependencies": {
"@types/amqplib": "^0.8.2",
"@types/config": "^0.0.41",
"@types/express": "^4.17.13",
"@types/node": "^18.0.1",
"@types/uuid-parse": "^1.0.0",
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
"@types/amqplib": "^0.10.5",
"@types/config": "^3.3.5",
"@types/express": "~4.17.21",
"@types/node": "~20.17.6",
"@types/uuid-parse": "^1.0.2",
"fs-extra": "^11.2.0",
"nodemon": "^3.1.7",
"pkg": "^5.8.1",
"ts-node": "^10.9.2",
"typescript": "5.5.4"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"ansi-regex": "^5.0.1",
"color-string": "^1.5.5",
"minimist": "^1.2.6",
"node-fetch": "^2.6.7"
}
}

4
msa/js-executor/pom.xml

@ -71,8 +71,8 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v16.20.2</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
<nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.22</yarnVersion>
</configuration>
</execution>
<execution>

2
msa/js-executor/queue/awsSqsTemplate.ts

@ -52,7 +52,7 @@ export class AwsSqsTemplate implements IQueue {
private queueAttributes: { [n: string]: string } = {
FifoQueue: 'true'
};
private timer: NodeJS.Timer;
private timer: NodeJS.Timeout;
name = 'AWS SQS';

2
msa/js-executor/queue/serviceBusTemplate.ts

@ -57,7 +57,7 @@ export class ServiceBusTemplate implements IQueue {
this.parseQueueProperties();
const listQueues = await this.serviceBusService.listQueues();
const listQueues = this.serviceBusService.listQueues();
for await (const queue of listQueues) {
this.queues.push(queue.name);
}

4310
msa/js-executor/yarn.lock

File diff suppressed because it is too large

4
msa/vc-executor/src/main/resources/tb-vc-executor.yml

@ -47,7 +47,9 @@ zk:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # in-memory or kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
# kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
in_memory:
stats:

2
msa/web-ui/docker/Dockerfile

@ -14,7 +14,7 @@
# limitations under the License.
#
FROM thingsboard/node:16.20.2-bookworm-slim
FROM thingsboard/node:20.18.0-bookworm-slim
ENV NODE_ENV production
ENV DOCKER_MODE true

39
msa/web-ui/package.json

@ -6,22 +6,22 @@
"main": "server.ts",
"bin": "server.js",
"scripts": {
"pkg": "tsc && pkg -t node16-linux-x64,node16-win-x64 --out-path ./target ./target/src && node install.js",
"pkg": "tsc && pkg -t node18-linux-x64,node18-win-x64 --out-path ./target ./target/src && node install.js",
"test": "echo \"Error: no test specified\" && exit 1",
"start": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web ts-node server.ts'",
"start-prod": "nodemon --watch '.' --ext 'ts' --exec 'WEB_FOLDER=./target/web NODE_ENV=production ts-node server.ts'",
"build": "tsc"
},
"dependencies": {
"compression": "^1.7.4",
"config": "^3.3.7",
"compression": "^1.7.5",
"config": "^3.3.12",
"connect-history-api-fallback": "^1.6.0",
"express": "^4.18.1",
"express": "^4.21.1",
"http": "0.0.0",
"http-proxy": "^1.18.1",
"js-yaml": "^4.1.0",
"winston": "^3.7.2",
"winston-daily-rotate-file": "^4.7.1"
"winston": "^3.16.0",
"winston-daily-rotate-file": "^5.0.0"
},
"nyc": {
"exclude": [
@ -32,26 +32,21 @@
]
},
"devDependencies": {
"@types/compression": "^1.7.2",
"@types/config": "^0.0.41",
"@types/connect-history-api-fallback": "^1.3.5",
"@types/express": "^4.17.13",
"@types/http-proxy": "^1.17.9",
"@types/node": "^18.0.0",
"fs-extra": "^10.1.0",
"nodemon": "^2.0.16",
"pkg": "^5.7.0",
"ts-node": "^10.8.1",
"typescript": "^4.7.4"
"@types/compression": "^1.7.5",
"@types/config": "^3.3.5",
"@types/connect-history-api-fallback": "^1.5.4",
"@types/express": "~4.17.21",
"@types/http-proxy": "^1.17.15",
"@types/node": "~20.17.6",
"fs-extra": "^11.2.0",
"nodemon": "^3.1.7",
"pkg": "^5.8.1",
"ts-node": "^10.9.2",
"typescript": "5.5.4"
},
"pkg": {
"assets": [
"node_modules/config/**/*.*"
]
},
"resolutions": {
"color-string": "^1.5.5",
"follow-redirects": "^1.14.8",
"minimist": "^1.2.6"
}
}

4
msa/web-ui/pom.xml

@ -80,8 +80,8 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v16.20.2</nodeVersion>
<yarnVersion>v1.22.17</yarnVersion>
<nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.22</yarnVersion>
</configuration>
</execution>
<execution>

1982
msa/web-ui/yarn.lock

File diff suppressed because it is too large

37
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNode.java

@ -52,7 +52,7 @@ import java.util.stream.Collectors;
@RuleNode(type = ComponentType.ENRICHMENT,
name = "originator telemetry",
configClazz = TbGetTelemetryNodeConfiguration.class,
version = 1,
version = 2,
nodeDescription = "Adds message originator telemetry for selected time range into message metadata",
nodeDetails = "Useful when you need to get telemetry data set from the message originator for a specific time range " +
"instead of fetching just the latest telemetry or if you need to get the closest telemetry to the fetch interval start or end. " +
@ -232,21 +232,21 @@ public class TbGetTelemetryNode implements TbNode {
public TbPair<Boolean, JsonNode> upgrade(int fromVersion, JsonNode oldConfiguration) throws TbNodeException {
boolean hasChanges = false;
switch (fromVersion) {
case 0 -> {
case 0: {
if (oldConfiguration.hasNonNull("fetchMode")) {
String fetchMode = oldConfiguration.get("fetchMode").asText();
switch (fetchMode) {
case "FIRST":
case "FIRST" -> {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
break;
case "LAST":
}
case "LAST" -> {
((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
break;
case "ALL":
}
case "ALL" -> {
if (oldConfiguration.has("orderBy") &&
(oldConfiguration.get("orderBy").isNull() || oldConfiguration.get("orderBy").asText().isEmpty())) {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
@ -257,16 +257,33 @@ public class TbGetTelemetryNode implements TbNode {
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
}
break;
default:
}
default -> {
((ObjectNode) oldConfiguration).put("fetchMode", FetchMode.LAST.name());
((ObjectNode) oldConfiguration).put("orderBy", Direction.DESC.name());
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
break;
}
}
}
}
case 1: {
if (!oldConfiguration.hasNonNull("limit")) {
((ObjectNode) oldConfiguration).put("limit", 1000);
hasChanges = true;
}
if (oldConfiguration.has("fetchMode") && oldConfiguration.get("fetchMode").asText().equals("ALL")) {
if (!oldConfiguration.hasNonNull("aggregation")) {
((ObjectNode) oldConfiguration).put("aggregation", Aggregation.NONE.name());
hasChanges = true;
}
if (!oldConfiguration.hasNonNull("orderBy")) {
((ObjectNode) oldConfiguration).put("orderBy", Direction.ASC.name());
hasChanges = true;
}
}
break;
}
}
return new TbPair<>(hasChanges, oldConfiguration);
}

32
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/metadata/TbGetTelemetryNodeTest.java

@ -637,6 +637,38 @@ public class TbGetTelemetryNodeTest extends AbstractRuleNodeUpgradeTest {
"endInterval": 1,
"endIntervalTimeUnit": "MINUTES"
}
"""),
// config for version 0 (fetchMode is 'ALL' and limit, aggregation and orderBy do not exist)
Arguments.of(0,
"""
{
"latestTsKeyNames": ["key"],
"fetchMode": "ALL",
"useMetadataIntervalPatterns": false,
"startIntervalPattern": "",
"endIntervalPattern": "",
"startInterval": 2,
"startIntervalTimeUnit": "MINUTES",
"endInterval": 1,
"endIntervalTimeUnit": "MINUTES"
}
""",
true,
"""
{
"latestTsKeyNames": ["key"],
"aggregation": "NONE",
"fetchMode": "ALL",
"orderBy": "ASC",
"limit": 1000,
"useMetadataIntervalPatterns": false,
"startIntervalPattern": "",
"endIntervalPattern": "",
"startInterval": 2,
"startIntervalTimeUnit": "MINUTES",
"endInterval": 1,
"endIntervalTimeUnit": "MINUTES"
}
""")
);
}

4
transport/coap/src/main/resources/tb-coap-transport.yml

@ -232,7 +232,9 @@ coap:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
# kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers

4
transport/http/src/main/resources/tb-http-transport.yml

@ -202,7 +202,9 @@ transport:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
# kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka) .
kafka:
# Kafka Bootstrap Servers

4
transport/lwm2m/src/main/resources/tb-lwm2m-transport.yml

@ -302,7 +302,9 @@ transport:
# Queue configuration properties
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
# kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers

4
transport/mqtt/src/main/resources/tb-mqtt-transport.yml

@ -235,7 +235,9 @@ transport:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
# kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers

4
transport/snmp/src/main/resources/tb-snmp-transport.yml

@ -181,7 +181,9 @@ transport:
# Queue configuration parameters
queue:
type: "${TB_QUEUE_TYPE:kafka}" # kafka (Apache Kafka) or aws-sqs (AWS SQS) or pubsub (PubSub) or service-bus (Azure Service Bus) or rabbitmq (RabbitMQ)
# kafka (Apache Kafka). The following queue types are deprecated and will no longer be supported in ThingsBoard 4.0:
# aws-sqs (AWS SQS), pubsub (PubSub), service-bus (Azure Service Bus), rabbitmq (RabbitMQ)
type: "${TB_QUEUE_TYPE:kafka}"
prefix: "${TB_QUEUE_PREFIX:}" # Global queue prefix. If specified, prefix is added before default topic name: 'prefix.default_topic_name'. Prefix is applied to all topics (and consumer groups for kafka).
kafka:
# Kafka Bootstrap Servers

2
ui-ngx/pom.xml

@ -56,7 +56,7 @@
<goal>install-node-and-yarn</goal>
</goals>
<configuration>
<nodeVersion>v20.11.1</nodeVersion>
<nodeVersion>v20.18.0</nodeVersion>
<yarnVersion>v1.22.22</yarnVersion>
</configuration>
</execution>

18
ui-ngx/src/app/core/interceptors/entity-conflict.interceptor.ts

@ -22,11 +22,11 @@ import { MatDialog } from '@angular/material/dialog';
import {
EntityConflictDialogComponent
} from '@shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component';
import { HasId } from '@shared/models/base-data';
import { HasVersion } from '@shared/models/entity.models';
import { EntityInfoData, VersionedEntity } from '@shared/models/entity.models';
import { getInterceptorConfig } from './interceptor.util';
import { isDefined } from '@core/utils';
import { InterceptorConfig } from '@core/interceptors/interceptor-config';
import { RuleChainMetaData } from '@shared/models/rule-chain.models';
@Injectable()
export class EntityConflictInterceptor implements HttpInterceptor {
@ -35,7 +35,7 @@ export class EntityConflictInterceptor implements HttpInterceptor {
private dialog: MatDialog,
) {}
intercept(request: HttpRequest<unknown & HasId & HasVersion>, next: HttpHandler): Observable<HttpEvent<unknown>> {
intercept(request: HttpRequest<VersionedEntity>, next: HttpHandler): Observable<HttpEvent<unknown>> {
if (!request.url.startsWith('/api/')) {
return next.handle(request);
}
@ -52,11 +52,11 @@ export class EntityConflictInterceptor implements HttpInterceptor {
}
private handleConflictError(
request: HttpRequest<unknown & HasId & HasVersion>,
request: HttpRequest<VersionedEntity>,
next: HttpHandler,
error: HttpErrorResponse
): Observable<HttpEvent<unknown>> {
if (getInterceptorConfig(request).ignoreVersionConflict) {
if (getInterceptorConfig(request).ignoreVersionConflict || !this.isVersionedEntity(request.body)) {
return throwError(() => error);
}
@ -74,12 +74,16 @@ export class EntityConflictInterceptor implements HttpInterceptor {
);
}
private updateRequestVersion(request: HttpRequest<unknown & HasId & HasVersion>): HttpRequest<unknown & HasId & HasVersion> {
private updateRequestVersion(request: HttpRequest<VersionedEntity>): HttpRequest<VersionedEntity> {
const body = { ...request.body, version: null };
return request.clone({ body });
}
private openConflictDialog(entity: unknown & HasId & HasVersion, message: string): Observable<boolean> {
private isVersionedEntity(entity: VersionedEntity): boolean {
return !!((entity as EntityInfoData)?.id ?? (entity as RuleChainMetaData)?.ruleChainId)
}
private openConflictDialog(entity: VersionedEntity, message: string): Observable<boolean> {
const dialogRef = this.dialog.open(EntityConflictDialogComponent, {
disableClose: true,
data: { message, entity },

2
ui-ngx/src/app/modules/home/components/alarm/alarm-filter-config.component.html

@ -73,7 +73,7 @@
</mat-chip-listbox>
</div>
<div class="tb-form-row column-xs">
<div class="fixed-title-width" ranslate>alarm.alarm-type-list</div>
<div class="fixed-title-width" translate>alarm.alarm-type-list</div>
<tb-entity-subtype-list subscriptSizing="dynamic"
formControlName="typeList"
appearance="outline"

1
ui-ngx/src/app/modules/home/components/dashboard-page/layout/select-dashboard-breakpoint.component.scss

@ -26,6 +26,7 @@
.mat-mdc-select.select-dashboard-breakpoint {
.mat-mdc-select-value {
max-width: 200px;
font-size: 14px;
}
.mat-mdc-select-arrow {
width: 24px;

1
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.scss

@ -22,6 +22,7 @@
.mat-mdc-select.default-state-controller {
.mat-mdc-select-value {
max-width: 200px;
font-size: 14px;
}
.mat-mdc-select-arrow {
width: 24px;

3
ui-ngx/src/app/modules/home/components/dashboard-page/states/default-state-controller.component.ts

@ -187,7 +187,8 @@ export class DefaultStateControllerComponent extends StateControllerComponent im
}
public getStateName(id: string, state: DashboardState): string {
return this.utils.customTranslation(state.name, id);
const name = this.utils.customTranslation(state.name, id);
return name === this.stateControllerId() ? name.charAt(0).toUpperCase() + name.slice(1) : name;
}
public getCurrentStateName(): string {

2
ui-ngx/src/app/modules/home/components/filter/filter-predicate-value.component.html

@ -43,7 +43,7 @@
</div>
<div class="tb-hint">{{ hintText | translate }}</div>
</div>
<div class="flex flex-col" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource">
<div class="flex flex-col flex-1" [class.!hidden]="!dynamicMode && !onlyUserDynamicSource">
<div formGroupName="dynamicValue" class="flex flex-row items-center justify-start gap-2">
<div class="flex max-w-35% flex-full flex-col">
<mat-form-field hideRequiredMarker class="mat-block">

3
ui-ngx/src/app/modules/home/components/widget/widget-container.component.html

@ -38,6 +38,7 @@
[class]="{'tb-widget-actions-absolute': !(widget.showWidgetTitlePanel && !widgetComponent.widgetContext?.embedTitlePanel && (widget.showTitle||widget.hasAggregation))}"
(mousedown)="$event.stopPropagation()">
<button mat-icon-button *ngFor="let action of widget.customHeaderActions"
type="button"
[class.!hidden]="isEdit"
(click)="action.onAction($event)"
matTooltip="{{ action.displayName }}"
@ -45,6 +46,7 @@
<tb-icon>{{ action.icon }}</tb-icon>
</button>
<button mat-icon-button *ngFor="let action of widget.widgetActions"
type="button"
[class.!hidden]="isEdit || !action.show"
(click)="action.onAction($event)"
matTooltip="{{ action.name | translate }}"
@ -52,6 +54,7 @@
<tb-icon>{{ action.icon }}</tb-icon>
</button>
<button mat-icon-button
type="button"
[class.!hidden]="isEdit || !widget.enableFullscreen"
(click)="$event.stopPropagation(); widget.isFullscreen = !widget.isFullscreen; updateEditWidgetActionsTooltipState()"
matTooltip="{{(widget.isFullscreen ? 'fullscreen.exit' : 'fullscreen.expand') | translate}}"

8
ui-ngx/src/app/modules/home/components/widget/widget-container.component.ts

@ -213,8 +213,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
});
}
onClicked(event: MouseEvent) {
if (event) {
onClicked(event: MouseEvent): void {
if (event && this.isEdit) {
event.stopPropagation();
}
this.widgetComponentAction.emit({
@ -223,8 +223,8 @@ export class WidgetContainerComponent extends PageComponent implements OnInit, O
});
}
onContextMenu(event: TbContextMenuEvent) {
if (event) {
onContextMenu(event: TbContextMenuEvent): void {
if (event && this.isEdit) {
event.stopPropagation();
}
this.widgetComponentAction.emit({

6
ui-ngx/src/app/modules/home/pages/admin/oauth2/clients/client.component.ts

@ -214,7 +214,11 @@ export class ClientComponent extends EntityComponent<OAuth2Client, PageLink, OAu
const mapperConfig = control.get('mapperConfig') as UntypedFormGroup;
if (type === MapperType.CUSTOM) {
mapperConfig.removeControl('basic');
mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
if (!mapperConfig.get('custom')) {
mapperConfig.addControl('custom', this.formCustomGroup(predefinedValue?.custom));
} else {
mapperConfig.get('custom').patchValue(predefinedValue.custom, {emitEvent: false});
}
} else {
mapperConfig.removeControl('custom');
if (!mapperConfig.get('basic')) {

2
ui-ngx/src/app/modules/home/pages/customer/customer.component.html

@ -49,7 +49,7 @@
<button mat-raised-button color="primary"
[disabled]="(isLoading$ | async)"
(click)="onEntityAction($event, 'manageEdges')"
[class.!hidden]="isEdit">
[class.!hidden]="isEdit"
*ngIf="edgesSupportEnabled()">
{{'customer.manage-edges' | translate }}
</button>

3
ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts

@ -127,7 +127,8 @@ export class RuleChainsTableConfigResolver {
const columns: Array<EntityColumn<RuleChain>> = [];
columns.push(
new DateEntityTableColumn<RuleChain>('createdTime', 'common.created-time', this.datePipe, '150px'),
new EntityTableColumn<RuleChain>('name', 'rulechain.name', '100%')
new EntityTableColumn<RuleChain>('name', 'rulechain.name', '50%'),
new EntityTableColumn<RuleChain>('description', 'rulechain.description', '50%', entity => entity.additionalInfo?.description ?? '')
);
if (ruleChainScope === 'tenant' || ruleChainScope === 'edge') {
columns.push(

1
ui-ngx/src/app/modules/home/pages/widget/select-widget-type-dialog.component.html

@ -19,6 +19,7 @@
<mat-toolbar color="primary">
<h2 translate>widget.select-widget-type</h2>
<span class="flex-1"></span>
<div tb-help="widgetTypes"></div>
<button mat-icon-button
(click)="cancel()"
type="button">

2
ui-ngx/src/app/shared/components/country-autocomplete.component.html

@ -15,7 +15,7 @@
limitations under the License.
-->
<mat-form-field [formGroup]="countryFormGroup" class="mat-block" subscriptSizing="{{subscriptSizing}}">
<mat-form-field [formGroup]="countryFormGroup" class="mat-block" subscriptSizing="{{subscriptSizing}}" [appearance]="appearance">
<mat-label>{{ labelText }}</mat-label>
<input matInput type="text"
#countryInput

5
ui-ngx/src/app/shared/components/country-autocomplete.component.ts

@ -28,7 +28,7 @@ import {
import { isNotEmptyStr } from '@core/utils';
import { Observable, of } from 'rxjs';
import { debounceTime, distinctUntilChanged, map, share, switchMap, tap } from 'rxjs/operators';
import { SubscriptSizing } from '@angular/material/form-field';
import { SubscriptSizing, MatFormFieldAppearance } from '@angular/material/form-field';
import { coerceBoolean } from '@shared/decorators/coercion';
import { TranslateService } from '@ngx-translate/core';
@ -71,6 +71,9 @@ export class CountryAutocompleteComponent implements OnInit, ControlValueAccesso
@coerceBoolean()
required = false;
@Input()
appearance: MatFormFieldAppearance = 'fill';
@Input()
subscriptSizing: SubscriptSizing = 'fixed';

4
ui-ngx/src/app/shared/components/dialog/entity-conflict-dialog/entity-conflict-dialog.component.ts

@ -20,13 +20,13 @@ import { SharedModule } from '@shared/shared.module';
import { ImportExportService } from '@shared/import-export/import-export.service';
import { CommonModule } from '@angular/common';
import { entityTypeTranslations } from '@shared/models/entity-type.models';
import { EntityInfoData } from '@shared/models/entity.models';
import { EntityInfoData, VersionedEntity } from '@shared/models/entity.models';
import { EntityId } from '@shared/models/id/entity-id';
import { RuleChainMetaData } from '@shared/models/rule-chain.models';
interface EntityConflictDialogData {
message: string;
entity: EntityInfoData | RuleChainMetaData;
entity: VersionedEntity;
}
@Component({

9
ui-ngx/src/app/shared/import-export/import-export.service.ts

@ -55,7 +55,12 @@ import { EntityType } from '@shared/models/entity-type.models';
import { UtilsService } from '@core/services/utils.service';
import { WidgetService } from '@core/http/widget.service';
import { WidgetsBundle } from '@shared/models/widgets-bundle.model';
import { EntityInfoData, ImportEntitiesResultInfo, ImportEntityData } from '@shared/models/entity.models';
import {
EntityInfoData,
ImportEntitiesResultInfo,
ImportEntityData,
VersionedEntity
} from '@shared/models/entity.models';
import { RequestConfig } from '@core/http/http-utils';
import { RuleChain, RuleChainImport, RuleChainMetaData, RuleChainType } from '@shared/models/rule-chain.models';
import { RuleChainService } from '@core/http/rule-chain.service';
@ -361,7 +366,7 @@ export class ImportExportService {
});
}
public exportEntity(entityData: EntityInfoData | RuleChainMetaData): void {
public exportEntity(entityData: VersionedEntity): void {
const id = (entityData as EntityInfoData).id ?? (entityData as RuleChainMetaData).ruleChainId;
let fileName = (entityData as EntityInfoData).name;
let preparedData;

65
ui-ngx/src/app/shared/models/ace/service-completion.models.ts

@ -102,6 +102,10 @@ export const customDialogComponentHref = '<a href="https://github.com/thingsboar
export const resourceInfoHref = '<a href="https://github.com/thingsboard/thingsboard/blob/b033b51712244d08e0f5e0beb8be60c9f8fa4cd2/ui-ngx/src/app/shared/models/resource.models.ts#L51" target="_blank">Resource info</a>';
export const bulkImportResultHref = '<a href="https://github.com/thingsboard/thingsboard/blob/1abaa6f1188e5adc80912e7475ccb6347a822c8d/ui-ngx/src/app/shared/import-export/import-export.models.ts#L135" target="_blank">Bulk import result</a>';
export const bulkImportRequestHref = '<a href="https://github.com/thingsboard/thingsboard/blob/1abaa6f1188e5adc80912e7475ccb6347a822c8d/ui-ngx/src/app/shared/import-export/import-export.models.ts#L125" target="_blank">Bulk import request</a>';
export const pageLinkArg: FunctionArg = {
name: 'pageLink',
type: '<a href="https://github.com/thingsboard/thingsboard/blob/13e6b10b7ab830e64d31b99614a9d95a1a25928a/ui-ngx/src/app/shared/models/page/page-link.ts#L68" target="_blank">PageLink</a>',
@ -387,6 +391,67 @@ export const serviceCompletions: TbEditorCompletions = {
],
return: observablePageDataReturnType(assetInfoHref)
},
getTenantAssetInfosByAssetProfileId: {
description: 'Get tenant asset infos by asset profile ID',
meta: 'function',
args: [
pageLinkArg,
{ name: 'assetProfileId', type: 'string', optional: true, description: 'ID of the asset profile' },
requestConfigArg
],
return: observablePageDataReturnType(assetInfoHref)
},
getCustomerAssetInfosByAssetProfileId: {
description: 'Get customer asset infos by asset profile ID',
meta: 'function',
args: [
{ name: 'customerId', type: 'string', description: 'ID of the customer' },
pageLinkArg,
{ name: 'assetProfileId', type: 'string', optional: true, description: 'ID of the asset profile' },
requestConfigArg
],
return: observablePageDataReturnType(assetInfoHref)
},
assignAssetToEdge: {
description: 'Assign an asset to an edge',
meta: 'function',
args: [
{ name: 'edgeId', type: 'string', description: 'ID of the edge' },
{ name: 'assetId', type: 'string', description: 'ID of the asset' },
requestConfigArg
],
return: observableReturnType(assetHref)
},
unassignAssetFromEdge: {
description: 'Unassign an asset from an edge',
meta: 'function',
args: [
{ name: 'edgeId', type: 'string', description: 'ID of the edge' },
{ name: 'assetId', type: 'string', description: 'ID of the asset' },
requestConfigArg
],
return: observableVoid()
},
getEdgeAssets: {
description: 'Get assets assigned to an edge',
meta: 'function',
args: [
{ name: 'edgeId', type: 'string', description: 'ID of the edge' },
pageLinkArg,
{ name: 'type', type: 'string', optional: true, description: 'Asset type' },
requestConfigArg
],
return: observablePageDataReturnType(assetInfoHref)
},
bulkImportAssets: {
description: 'Bulk import assets with provided entities data',
meta: 'function',
args: [
{ name: 'entitiesData', type: bulkImportRequestHref, description: 'Data for bulk importing assets' },
requestConfigArg
],
return: observableReturnType(bulkImportResultHref)
},
getAsset: {
description: 'Get asset by id',
meta: 'function',

3
ui-ngx/src/app/shared/models/entity.models.ts

@ -20,6 +20,7 @@ import { EntityId } from '@shared/models/id/entity-id';
import { DeviceCredentialMQTTBasic } from '@shared/models/device.models';
import { Lwm2mSecurityConfigModels } from '@shared/models/lwm2m-security-config.models';
import { TenantId } from '@shared/models/id/tenant-id';
import { RuleChainMetaData } from '@shared/models/rule-chain.models';
export interface EntityInfo {
name?: string;
@ -191,3 +192,5 @@ export interface HasTenantId {
export interface HasVersion {
version?: number;
}
export type VersionedEntity = EntityInfoData & HasVersion | RuleChainMetaData;

4
ui-ngx/src/styles.scss

@ -74,7 +74,7 @@ body {
line-height: normal;
}
a:not(.mat-mdc-button-base):not(.mdc-tab) {
a:not(.mat-mdc-button-base, .mdc-tab) {
font-weight: 400;
color: #106cc8;
text-decoration: none;
@ -85,7 +85,7 @@ a:not(.mat-mdc-button-base):not(.mdc-tab) {
a:hover,
a:focus {
&:not(.mat-mdc-button-base):not(.mdc-tab) {
&:not(.mat-mdc-button-base, .mdc-tab) {
border-bottom: 1px solid #4054b2;
}
}

Loading…
Cancel
Save