Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard

pull/3880/head
ShvaykaD 6 years ago
parent
commit
12667d89ea
  1. 6
      application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json
  2. 12
      application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java
  3. 5
      application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java
  4. 67
      application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java
  5. 4
      application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java
  7. 13
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  8. 13
      application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java
  9. 10
      application/src/main/resources/thingsboard.yml
  10. 4
      common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java
  11. 3
      common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java
  12. 10
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  13. 2
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java
  14. 14
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java
  15. 5
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java
  16. 53
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java
  17. 4
      common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java
  18. 3
      common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java
  19. 3
      dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java
  20. 6
      dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java
  21. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java
  22. 10
      dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java
  23. 19
      dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java
  24. 15
      dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java
  25. 7
      dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java
  26. 10
      dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java
  27. 2
      dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java
  28. 2
      dao/src/main/resources/sql/schema-entities-hsql.sql
  29. 2
      dao/src/main/resources/sql/schema-entities-idx.sql
  30. 2
      dao/src/main/resources/sql/schema-entities.sql
  31. 2
      docker/docker-compose.hybrid.yml
  32. 2
      docker/docker-compose.kafka.yml
  33. 2
      docker/docker-compose.postgres.yml
  34. 2
      msa/js-executor/package.json
  35. 8
      msa/js-executor/yarn.lock
  36. 5
      pom.xml
  37. 2
      transport/coap/src/main/resources/tb-coap-transport.yml
  38. 2
      transport/http/src/main/resources/tb-http-transport.yml
  39. 2
      transport/mqtt/src/main/resources/tb-mqtt-transport.yml
  40. 5
      ui-ngx/angular.json
  41. 16
      ui-ngx/src/app/core/http/device-profile.service.ts
  42. 1
      ui-ngx/src/app/core/translate/translate-default-compiler.ts
  43. 18
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html
  44. 124
      ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts
  45. 13
      ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts
  46. 3
      ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html
  47. 1
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html
  48. 7
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts
  49. 7
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts
  50. 2
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html
  51. 4
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts
  52. 2
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html
  53. 6
      ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts
  54. 6
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html
  55. 4
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts
  56. 1
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html
  57. 4
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts
  58. 3
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.html
  59. 5
      ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts
  60. 20
      ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw
  61. 7
      ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts
  62. 5
      ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html
  63. 2
      ui-ngx/src/app/modules/home/models/services.map.ts
  64. 2
      ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html
  65. 21
      ui-ngx/src/assets/locale/locale.constant-cs_CZ.json
  66. 3
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  67. 2
      ui-ngx/src/assets/locale/locale.constant-ko_KR.json
  68. 2479
      ui-ngx/src/assets/locale/locale.constant-sl_SI.json
  69. 2
      ui-ngx/src/index.html
  70. 1
      ui-ngx/src/styles.scss

6
application/src/main/data/json/system/widget_bundles/entity_admin_widgets.json

File diff suppressed because one or more lines are too long

12
application/src/main/java/org/thingsboard/server/actors/ruleChain/DefaultTbContext.java

@ -244,8 +244,18 @@ class DefaultTbContext implements TbContext {
if (nodeCtx.getSelf().isDebugMode()) {
mainCtx.persistDebugOutput(nodeCtx.getTenantId(), nodeCtx.getSelf().getId(), msg, TbRelationTypes.FAILURE, th);
}
String failureMessage;
if (th != null) {
if (!StringUtils.isEmpty(th.getMessage())) {
failureMessage = th.getMessage();
} else {
failureMessage = th.getClass().getSimpleName();
}
} else {
failureMessage = null;
}
nodeCtx.getChainActor().tell(new RuleNodeToRuleChainTellNextMsg(nodeCtx.getSelf().getId(), Collections.singleton(TbRelationTypes.FAILURE),
msg, th != null ? th.getMessage() : null));
msg, failureMessage));
}
public void updateSelf(RuleNode self) {

5
application/src/main/java/org/thingsboard/server/actors/ruleChain/RuleChainActorMessageProcessor.java

@ -56,6 +56,7 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Collectors;
/**
@ -288,10 +289,10 @@ public class RuleChainActorMessageProcessor extends ComponentMsgProcessor<RuleCh
private void putToQueue(TopicPartitionInfo tpi, TbMsg msg, TbQueueCallback callbackWrapper, EntityId target) {
switch (target.getEntityType()) {
case RULE_NODE:
putToQueue(tpi, msg.copyWithRuleNodeId(entityId, new RuleNodeId(target.getId())), callbackWrapper);
putToQueue(tpi, msg.copyWithRuleNodeId(entityId, new RuleNodeId(target.getId()), UUID.randomUUID()), callbackWrapper);
break;
case RULE_CHAIN:
putToQueue(tpi, msg.copyWithRuleChainId(new RuleChainId(target.getId())), callbackWrapper);
putToQueue(tpi, msg.copyWithRuleChainId(new RuleChainId(target.getId()), UUID.randomUUID()), callbackWrapper);
break;
}
}

67
application/src/main/java/org/thingsboard/server/controller/DeviceProfileController.java

@ -16,6 +16,8 @@
package org.thingsboard.server.controller;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.PathVariable;
@ -35,21 +37,30 @@ import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.page.PageData;
import org.thingsboard.server.common.data.page.PageLink;
import org.thingsboard.server.common.data.plugin.ComponentLifecycleEvent;
import org.thingsboard.server.dao.timeseries.TimeseriesService;
import org.thingsboard.server.queue.util.TbCoreComponent;
import org.thingsboard.server.service.security.permission.Operation;
import org.thingsboard.server.service.security.permission.Resource;
import java.util.List;
import java.util.UUID;
@RestController
@TbCoreComponent
@RequestMapping("/api")
@Slf4j
public class DeviceProfileController extends BaseController {
private static final String DEVICE_PROFILE_ID = "deviceProfileId";
@Autowired
private TimeseriesService timeseriesService;
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
public DeviceProfile getDeviceProfileById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
public DeviceProfile getDeviceProfileById(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkDeviceProfileId(deviceProfileId, Operation.READ);
@ -61,8 +72,8 @@ public class DeviceProfileController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
@RequestMapping(value = "/deviceProfileInfo/{deviceProfileId}", method = RequestMethod.GET)
@ResponseBody
public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
public DeviceProfileInfo getDeviceProfileInfoById(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
return checkNotNull(deviceProfileService.findDeviceProfileInfoById(getTenantId(), deviceProfileId));
@ -82,6 +93,46 @@ public class DeviceProfileController extends BaseController {
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/devices/keys/timeseries", method = RequestMethod.GET)
@ResponseBody
public List<String> getTimeseriesKeys(
@RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException {
DeviceProfileId deviceProfileId;
if (StringUtils.isNotEmpty(deviceProfileIdStr)) {
deviceProfileId = new DeviceProfileId(UUID.fromString(deviceProfileIdStr));
checkDeviceProfileId(deviceProfileId, Operation.READ);
} else {
deviceProfileId = null;
}
try {
return timeseriesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/devices/keys/attributes", method = RequestMethod.GET)
@ResponseBody
public List<String> getAttributesKeys(
@RequestParam(name = DEVICE_PROFILE_ID, required = false) String deviceProfileIdStr) throws ThingsboardException {
DeviceProfileId deviceProfileId;
if (StringUtils.isNotEmpty(deviceProfileIdStr)) {
deviceProfileId = new DeviceProfileId(UUID.fromString(deviceProfileIdStr));
checkDeviceProfileId(deviceProfileId, Operation.READ);
} else {
deviceProfileId = null;
}
try {
return attributesService.findAllKeysByDeviceProfileId(getTenantId(), deviceProfileId);
} catch (Exception e) {
throw handleException(e);
}
}
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile", method = RequestMethod.POST)
@ResponseBody
@ -113,8 +164,8 @@ public class DeviceProfileController extends BaseController {
@PreAuthorize("hasAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}", method = RequestMethod.DELETE)
@ResponseStatus(value = HttpStatus.OK)
public void deleteDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
public void deleteDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.DELETE);
@ -139,8 +190,8 @@ public class DeviceProfileController extends BaseController {
@PreAuthorize("hasAnyAuthority('TENANT_ADMIN')")
@RequestMapping(value = "/deviceProfile/{deviceProfileId}/default", method = RequestMethod.POST)
@ResponseBody
public DeviceProfile setDefaultDeviceProfile(@PathVariable("deviceProfileId") String strDeviceProfileId) throws ThingsboardException {
checkParameter("deviceProfileId", strDeviceProfileId);
public DeviceProfile setDefaultDeviceProfile(@PathVariable(DEVICE_PROFILE_ID) String strDeviceProfileId) throws ThingsboardException {
checkParameter(DEVICE_PROFILE_ID, strDeviceProfileId);
try {
DeviceProfileId deviceProfileId = new DeviceProfileId(toUUID(strDeviceProfileId));
DeviceProfile deviceProfile = checkDeviceProfileId(deviceProfileId, Operation.WRITE);

4
application/src/main/java/org/thingsboard/server/install/ThingsboardInstallService.java

@ -186,6 +186,10 @@ public class ThingsboardInstallService {
systemDataLoaderService.updateSystemWidgets();
systemDataLoaderService.createOAuth2Templates();
break;
case "3.2.0":
log.info("Upgrading ThingsBoard from version 3.2.0 to 3.2.1 ...");
databaseEntitiesUpgradeService.upgradeDatabase("3.2.0");
break;
default:
throw new RuntimeException("Unable to upgrade ThingsBoard, unsupported fromVersion: " + upgradeFromVersion);

2
application/src/main/java/org/thingsboard/server/service/apiusage/TenantApiUsageState.java

@ -181,7 +181,7 @@ public class TenantApiUsageState {
long threshold = getProfileThreshold(recordKey);
long warnThreshold = getProfileWarnThreshold(recordKey);
ApiUsageStateValue tmpValue;
if (threshold == 0 || value < warnThreshold) {
if (threshold == 0 || value == 0 || value < warnThreshold) {
tmpValue = ApiUsageStateValue.ENABLED;
} else if (value < threshold) {
tmpValue = ApiUsageStateValue.WARNING;

13
application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java

@ -421,6 +421,19 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.error("Failed updating schema!!!", e);
}
break;
case "3.2.0":
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
log.info("Updating schema ...");
try {
conn.createStatement().execute("CREATE INDEX IF NOT EXISTS idx_device_device_profile_id ON device(tenant_id, device_profile_id);");
conn.createStatement().execute("ALTER TABLE dashboard ALTER COLUMN configuration TYPE varchar;");
conn.createStatement().execute("UPDATE tb_schema_settings SET schema_version = 3002001;");
} catch (Exception e) {
log.error("Failed updating schema!!!", e);
}
log.info("Schema updated.");
}
break;
default:
throw new RuntimeException("Unable to upgrade SQL database, unsupported fromVersion: " + fromVersion);
}

13
application/src/main/java/org/thingsboard/server/service/sms/aws/AwsSmsSender.java

@ -20,6 +20,7 @@ import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.sns.AmazonSNS;
import com.amazonaws.services.sns.AmazonSNSClient;
import com.amazonaws.services.sns.model.MessageAttributeValue;
import com.amazonaws.services.sns.model.PublishRequest;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
@ -28,9 +29,20 @@ import org.thingsboard.rule.engine.api.sms.exception.SmsException;
import org.thingsboard.rule.engine.api.sms.exception.SmsSendException;
import org.thingsboard.server.service.sms.AbstractSmsSender;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class AwsSmsSender extends AbstractSmsSender {
private static final Map<String, MessageAttributeValue> SMS_ATTRIBUTES = new HashMap<>();
static {
SMS_ATTRIBUTES.put("AWS.SNS.SMS.SMSType", new MessageAttributeValue()
.withStringValue("Transactional")
.withDataType("String"));
}
private AmazonSNS snsClient;
public AwsSmsSender(AwsSnsSmsProviderConfiguration config) {
@ -51,6 +63,7 @@ public class AwsSmsSender extends AbstractSmsSender {
message = this.prepareMessage(message);
try {
PublishRequest publishRequest = new PublishRequest()
.withMessageAttributes(SMS_ATTRIBUTES)
.withPhoneNumber(numberTo)
.withMessage(message);
this.snsClient.publish(publishRequest);

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

@ -64,9 +64,9 @@ server:
# Minimum value of the server side RPC timeout. May override value provided in the REST API call.
# Since 2.5 migration to queues, the RPC delay depends on the size of the pending messages in the queue,
# so default UI parameter of 500ms may not be sufficient for loaded environments.
min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}"
min_timeout: "${MIN_SERVER_SIDE_RPC_TIMEOUT:5000}"
# Default value of the server side RPC timeout.
default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
default_timeout: "${DEFAULT_SERVER_SIDE_RPC_TIMEOUT:10000}"
# Zookeeper connection parameters. Used for service discovery.
zk:
@ -522,7 +522,7 @@ transport:
# Maximum allowed string value length when processing Telemetry/Attributes JSON (0 value disables string value length check)
max_string_value_length: "${JSON_MAX_STRING_VALUE_LENGTH:0}"
client_side_rpc:
timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
timeout: "${CLIENT_SIDE_RPC_TIMEOUT:60000}"
# Enable/disable http/mqtt/coap transport protocols (has higher priority than certain protocol's 'enabled' property)
api_enabled: "${TB_TRANSPORT_API_ENABLED:true}"
# Local HTTP transport parameters
@ -595,7 +595,7 @@ queue:
linger.ms: "${TB_KAFKA_LINGER_MS:1}"
buffer.memory: "${TB_BUFFER_MEMORY:33554432}"
replication_factor: "${TB_QUEUE_KAFKA_REPLICATION_FACTOR:1}"
max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:0}"
max_poll_interval_ms: "${TB_QUEUE_KAFKA_MAX_POLL_INTERVAL_MS:300000}"
max_poll_records: "${TB_QUEUE_KAFKA_MAX_POLL_RECORDS:8192}"
max_partition_fetch_bytes: "${TB_QUEUE_KAFKA_MAX_PARTITION_FETCH_BYTES:16777216}"
fetch_max_bytes: "${TB_QUEUE_KAFKA_FETCH_MAX_BYTES:134217728}"
@ -695,8 +695,6 @@ queue:
max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
# JS response poll interval
response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
# JS response auto commit interval
response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
rule-engine:
topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"

4
common/dao-api/src/main/java/org/thingsboard/server/dao/attributes/AttributesService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.attributes;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -38,4 +39,7 @@ public interface AttributesService {
ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes);
ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String scope, List<String> attributeKeys);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
}

3
common/dao-api/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesService.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.timeseries;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
@ -47,4 +48,6 @@ public interface TimeseriesService {
ListenableFuture<List<Void>> removeLatest(TenantId tenantId, EntityId entityId, Collection<String> keys);
ListenableFuture<Collection<String>> removeAllLatest(TenantId tenantId, EntityId entityId);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
}

10
common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java

@ -195,11 +195,15 @@ public final class TbMsg implements Serializable {
}
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId) {
return new TbMsg(this.queueName, this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback);
return copyWithRuleChainId(ruleChainId, this.id);
}
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId) {
return new TbMsg(this.queueName, this.id, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback);
public TbMsg copyWithRuleChainId(RuleChainId ruleChainId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, null, this.ruleNodeExecCounter.get(), callback);
}
public TbMsg copyWithRuleNodeId(RuleChainId ruleChainId, RuleNodeId ruleNodeId, UUID msgId) {
return new TbMsg(this.queueName, msgId, this.ts, this.type, this.originator, this.metaData, this.dataType, this.data, ruleChainId, ruleNodeId, this.ruleNodeExecCounter.get(), callback);
}
public TbMsgCallback getCallback() {

2
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaAdmin.java

@ -42,7 +42,7 @@ public class TbKafkaAdmin implements TbQueueAdmin {
private final short replicationFactor;
public TbKafkaAdmin(TbKafkaSettings settings, Map<String, String> topicConfigs) {
client = AdminClient.create(settings.toProps());
client = AdminClient.create(settings.toAdminProps());
this.topicConfigs = topicConfigs;
try {

14
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaConsumerTemplate.java

@ -45,24 +45,14 @@ public class TbKafkaConsumerTemplate<T extends TbQueueMsg> extends AbstractTbQue
@Builder
private TbKafkaConsumerTemplate(TbKafkaSettings settings, TbKafkaDecoder<T> decoder,
String clientId, String groupId, String topic,
boolean autoCommit, int autoCommitIntervalMs,
TbQueueAdmin admin) {
super(topic);
Properties props = settings.toProps();
Properties props = settings.toConsumerProps();
props.put(ConsumerConfig.CLIENT_ID_CONFIG, clientId);
if (groupId != null) {
props.put(ConsumerConfig.GROUP_ID_CONFIG, groupId);
}
if (settings.getMaxPollIntervalMs() > 0) {
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, settings.getMaxPollIntervalMs());
}
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, settings.getMaxPollRecords());
props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, settings.getMaxPartitionFetchBytes());
props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, settings.getFetchMaxBytes());
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, autoCommit);
props.put(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, autoCommitIntervalMs);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer");
this.admin = admin;
this.consumer = new KafkaConsumer<>(props);
this.decoder = decoder;

5
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaProducerTemplate.java

@ -55,9 +55,8 @@ public class TbKafkaProducerTemplate<T extends TbQueueMsg> implements TbQueuePro
@Builder
private TbKafkaProducerTemplate(TbKafkaSettings settings, String defaultTopic, String clientId, TbQueueAdmin admin) {
Properties props = settings.toProps();
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer");
Properties props = settings.toProducerProps();
if (!StringUtils.isEmpty(clientId)) {
props.put(ProducerConfig.CLIENT_ID_CONFIG, clientId);
}

53
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaSettings.java

@ -19,9 +19,11 @@ import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.consumer.ConsumerConfig;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@ -32,7 +34,7 @@ import java.util.Properties;
* Created by ashvayka on 25.09.18.
*/
@Slf4j
@ConditionalOnExpression("'${queue.type:null}'=='kafka'")
@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka")
@ConfigurationProperties(prefix = "queue.kafka")
@Component
public class TbKafkaSettings {
@ -60,19 +62,15 @@ public class TbKafkaSettings {
private short replicationFactor;
@Value("${queue.kafka.max_poll_records:8192}")
@Getter
private int maxPollRecords;
@Value("${queue.kafka.max_poll_interval_ms:0}")
@Getter
@Value("${queue.kafka.max_poll_interval_ms:300000}")
private int maxPollIntervalMs;
@Value("${queue.kafka.max_partition_fetch_bytes:16777216}")
@Getter
private int maxPartitionFetchBytes;
@Value("${queue.kafka.fetch_max_bytes:134217728}")
@Getter
private int fetchMaxBytes;
@Value("${queue.kafka.use_confluent_cloud:false}")
@ -93,21 +91,48 @@ public class TbKafkaSettings {
@Setter
private List<TbKafkaProperty> other;
public Properties toProps() {
Properties props = new Properties();
public Properties toAdminProps() {
Properties props = toProps();
props.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
props.put(AdminClientConfig.RETRIES_CONFIG, retries);
return props;
}
public Properties toConsumerProps() {
Properties props = toProps();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, maxPollRecords);
props.put(ConsumerConfig.MAX_PARTITION_FETCH_BYTES_CONFIG, maxPartitionFetchBytes);
props.put(ConsumerConfig.FETCH_MAX_BYTES_CONFIG, fetchMaxBytes);
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, maxPollIntervalMs);
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringDeserializer");
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArrayDeserializer");
return props;
}
public Properties toProducerProps() {
Properties props = toProps();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, servers);
props.put(ProducerConfig.RETRIES_CONFIG, retries);
props.put(ProducerConfig.ACKS_CONFIG, acks);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.StringSerializer");
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, "org.apache.kafka.common.serialization.ByteArraySerializer");
return props;
}
private Properties toProps() {
Properties props = new Properties();
if (useConfluent) {
props.put("ssl.endpoint.identification.algorithm", sslAlgorithm);
props.put("sasl.mechanism", saslMechanism);
props.put("sasl.jaas.config", saslConfig);
props.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
} else {
props.put(ProducerConfig.ACKS_CONFIG, acks);
props.put(ProducerConfig.BATCH_SIZE_CONFIG, batchSize);
props.put(ProducerConfig.LINGER_MS_CONFIG, lingerMs);
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, bufferMemory);
}
if (other != null) {

4
common/queue/src/main/java/org/thingsboard/server/queue/kafka/TbKafkaTopicConfigs.java

@ -17,7 +17,7 @@ package org.thingsboard.server.queue.kafka;
import lombok.Getter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.stereotype.Component;
import javax.annotation.PostConstruct;
@ -25,7 +25,7 @@ import java.util.HashMap;
import java.util.Map;
@Component
@ConditionalOnExpression("'${queue.type:null}'=='kafka'")
@ConditionalOnProperty(prefix = "queue", value = "type", havingValue = "kafka")
public class TbKafkaTopicConfigs {
@Value("${queue.kafka.topic-properties.core}")
private String coreProperties;

3
common/queue/src/main/java/org/thingsboard/server/queue/settings/TbQueueRemoteJsInvokeSettings.java

@ -34,9 +34,6 @@ public class TbQueueRemoteJsInvokeSettings {
@Value("${queue.js.response_poll_interval}")
private int responsePollInterval;
@Value("${queue.js.response_auto_commit_interval}")
private int autoCommitInterval;
@Value("${queue.js.max_requests_timeout}")
private long maxRequestsTimeout;
}

3
dao/src/main/java/org/thingsboard/server/dao/attributes/AttributesDao.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.attributes;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -38,4 +39,6 @@ public interface AttributesDao {
ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute);
ListenableFuture<List<Void>> removeAll(TenantId tenantId, EntityId entityId, String attributeType, List<String> keys);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
}

6
dao/src/main/java/org/thingsboard/server/dao/attributes/BaseAttributesService.java

@ -20,6 +20,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -59,6 +60,11 @@ public class BaseAttributesService implements AttributesService {
return attributesDao.findAll(tenantId, entityId, scope);
}
@Override
public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
return attributesDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId);
}
@Override
public ListenableFuture<List<Void>> save(TenantId tenantId, EntityId entityId, String scope, List<AttributeKvEntry> attributes) {
validate(entityId, scope);

10
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/AttributeKvRepository.java

@ -46,5 +46,15 @@ public interface AttributeKvRepository extends CrudRepository<AttributeKvEntity,
@Param("entityId") UUID entityId,
@Param("attributeType") String attributeType,
@Param("attributeKey") String attributeKey);
@Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " +
"AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId and device_profile_id = :deviceProfileId limit 100) ORDER BY attribute_key", nativeQuery = true)
List<String> findAllKeysByDeviceProfileId(@Param("tenantId") UUID tenantId,
@Param("deviceProfileId") UUID deviceProfileId);
@Query(value = "SELECT DISTINCT attribute_key FROM attribute_kv WHERE entity_type = 'DEVICE' " +
"AND entity_id in (SELECT id FROM device WHERE tenant_id = :tenantId limit 100) ORDER BY attribute_key", nativeQuery = true)
List<String> findAllKeysByTenantId(@Param("tenantId") UUID tenantId);
}

10
dao/src/main/java/org/thingsboard/server/dao/sql/attributes/JpaAttributeDao.java

@ -22,6 +22,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.AttributeKvEntry;
@ -135,6 +136,15 @@ public class JpaAttributeDao extends JpaAbstractDaoListeningExecutorService impl
attributeType))));
}
@Override
public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
if (deviceProfileId != null) {
return attributeKvRepository.findAllKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId());
} else {
return attributeKvRepository.findAllKeysByTenantId(tenantId.getId());
}
}
@Override
public ListenableFuture<Void> save(TenantId tenantId, EntityId entityId, String attributeType, AttributeKvEntry attribute) {
AttributeKvEntity entity = new AttributeKvEntity();

19
dao/src/main/java/org/thingsboard/server/dao/sqlts/SqlTimeseriesLatestDao.java

@ -24,6 +24,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.Aggregation;
@ -51,10 +52,15 @@ import org.thingsboard.server.dao.util.SqlTsLatestAnyDao;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import java.util.*;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
import java.util.concurrent.ExecutionException;
import java.util.function.Function;
import java.util.stream.Collectors;
@Slf4j
@Component
@ -154,6 +160,15 @@ public class SqlTimeseriesLatestDao extends BaseAbstractSqlTimeseriesDao impleme
return getFindAllLatestFuture(entityId);
}
@Override
public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
if (deviceProfileId != null) {
return tsKvLatestRepository.getKeysByDeviceProfileId(tenantId.getId(), deviceProfileId.getId());
} else {
return tsKvLatestRepository.getKeysByTenantId(tenantId.getId());
}
}
private ListenableFuture<Void> getNewLatestEntryFuture(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query) {
ListenableFuture<List<TsKvEntry>> future = findNewLatestEntryFuture(tenantId, entityId, query);
return Futures.transformAsync(future, entryList -> {

15
dao/src/main/java/org/thingsboard/server/dao/sqlts/latest/TsKvLatestRepository.java

@ -15,10 +15,25 @@
*/
package org.thingsboard.server.dao.sqlts.latest;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestCompositeKey;
import org.thingsboard.server.dao.model.sqlts.latest.TsKvLatestEntity;
import java.util.List;
import java.util.UUID;
public interface TsKvLatestRepository extends CrudRepository<TsKvLatestEntity, TsKvLatestCompositeKey> {
@Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " +
"INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " +
"WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE device_profile_id = :device_profile_id AND tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true)
List<String> getKeysByDeviceProfileId(@Param("tenant_id") UUID tenantId, @Param("device_profile_id") UUID deviceProfileId);
@Query(value = "SELECT DISTINCT ts_kv_dictionary.key AS strKey FROM ts_kv_latest " +
"INNER JOIN ts_kv_dictionary ON ts_kv_latest.key = ts_kv_dictionary.key_id " +
"WHERE ts_kv_latest.entity_id IN (SELECT id FROM device WHERE tenant_id = :tenant_id limit 100) ORDER BY ts_kv_dictionary.key", nativeQuery = true)
List<String> getKeysByTenantId(@Param("tenant_id") UUID tenantId);
}

7
dao/src/main/java/org/thingsboard/server/dao/timeseries/BaseTimeseriesService.java

@ -27,6 +27,7 @@ import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.EntityView;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.EntityViewId;
import org.thingsboard.server.common.data.id.TenantId;
@ -40,7 +41,6 @@ import org.thingsboard.server.dao.entityview.EntityViewService;
import org.thingsboard.server.dao.exception.IncorrectParameterException;
import org.thingsboard.server.dao.service.Validator;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
@ -116,6 +116,11 @@ public class BaseTimeseriesService implements TimeseriesService {
return timeseriesLatestDao.findAllLatest(tenantId, entityId);
}
@Override
public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
return timeseriesLatestDao.findAllKeysByDeviceProfileId(tenantId, deviceProfileId);
}
@Override
public ListenableFuture<Integer> save(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
validate(entityId);

10
dao/src/main/java/org/thingsboard/server/dao/timeseries/CassandraBaseTimeseriesLatestDao.java

@ -18,7 +18,6 @@ package org.thingsboard.server.dao.timeseries;
import com.datastax.oss.driver.api.core.cql.BoundStatement;
import com.datastax.oss.driver.api.core.cql.BoundStatementBuilder;
import com.datastax.oss.driver.api.core.cql.PreparedStatement;
import com.datastax.oss.driver.api.core.cql.Row;
import com.datastax.oss.driver.api.core.cql.Statement;
import com.datastax.oss.driver.api.querybuilder.QueryBuilder;
import com.google.common.util.concurrent.FutureCallback;
@ -28,14 +27,13 @@ import com.google.common.util.concurrent.MoreExecutors;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.Aggregation;
import org.thingsboard.server.common.data.kv.BaseReadTsKvQuery;
import org.thingsboard.server.common.data.kv.BasicTsKvEntry;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
import org.thingsboard.server.common.data.kv.ReadTsKvQuery;
import org.thingsboard.server.common.data.kv.StringDataEntry;
import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.nosql.TbResultSet;
@ -43,6 +41,7 @@ import org.thingsboard.server.dao.sqlts.AggregationTimeseriesDao;
import org.thingsboard.server.dao.util.NoSqlTsLatestDao;
import javax.annotation.Nullable;
import java.util.Collections;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
@ -82,6 +81,11 @@ public class CassandraBaseTimeseriesLatestDao extends AbstractCassandraBaseTimes
return getFutureAsync(executeAsyncRead(tenantId, stmt), rs -> convertAsyncResultSetToTsKvEntryList(rs));
}
@Override
public List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId) {
return Collections.emptyList();
}
@Override
public ListenableFuture<Void> saveLatest(TenantId tenantId, EntityId entityId, TsKvEntry tsKvEntry) {
BoundStatementBuilder stmtBuilder = new BoundStatementBuilder(getLatestStmt().bind());

2
dao/src/main/java/org/thingsboard/server/dao/timeseries/TimeseriesLatestDao.java

@ -16,6 +16,7 @@
package org.thingsboard.server.dao.timeseries;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.id.DeviceProfileId;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.kv.DeleteTsKvQuery;
@ -33,4 +34,5 @@ public interface TimeseriesLatestDao {
ListenableFuture<Void> removeLatest(TenantId tenantId, EntityId entityId, DeleteTsKvQuery query);
List<String> findAllKeysByDeviceProfileId(TenantId tenantId, DeviceProfileId deviceProfileId);
}

2
dao/src/main/resources/sql/schema-entities-hsql.sql

@ -114,7 +114,7 @@ CREATE TABLE IF NOT EXISTS customer (
CREATE TABLE IF NOT EXISTS dashboard (
id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
created_time bigint NOT NULL,
configuration varchar(10000000),
configuration varchar,
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id uuid,

2
dao/src/main/resources/sql/schema-entities-idx.sql

@ -34,6 +34,8 @@ CREATE INDEX IF NOT EXISTS idx_device_customer_id_and_type ON device(tenant_id,
CREATE INDEX IF NOT EXISTS idx_device_type ON device(tenant_id, type);
CREATE INDEX IF NOT EXISTS idx_device_device_profile_id ON device(tenant_id, device_profile_id);
CREATE INDEX IF NOT EXISTS idx_asset_customer_id ON asset(tenant_id, customer_id);
CREATE INDEX IF NOT EXISTS idx_asset_customer_id_and_type ON asset(tenant_id, customer_id, type);

2
dao/src/main/resources/sql/schema-entities.sql

@ -132,7 +132,7 @@ CREATE TABLE IF NOT EXISTS customer (
CREATE TABLE IF NOT EXISTS dashboard (
id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
created_time bigint NOT NULL,
configuration varchar(10000000),
configuration varchar,
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id uuid,

2
docker/docker-compose.hybrid.yml

@ -19,7 +19,7 @@ version: '2.2'
services:
postgres:
restart: always
image: "postgres:11.6"
image: "postgres:12"
ports:
- "5432"
environment:

2
docker/docker-compose.kafka.yml

@ -19,7 +19,7 @@ version: '2.2'
services:
kafka:
restart: always
image: "wurstmeister/kafka:2.12-2.3.0"
image: "wurstmeister/kafka:2.13-2.6.0"
ports:
- "9092:9092"
env_file:

2
docker/docker-compose.postgres.yml

@ -19,7 +19,7 @@ version: '2.2'
services:
postgres:
restart: always
image: "postgres:11.6"
image: "postgres:12"
ports:
- "5432"
environment:

2
msa/js-executor/package.json

@ -19,7 +19,7 @@
"azure-sb": "^0.11.1",
"config": "^3.3.1",
"js-yaml": "^3.14.0",
"kafkajs": "^1.14.0",
"kafkajs": "^1.15.0",
"long": "^4.0.0",
"uuid-parse": "^1.1.0",
"uuid-random": "^1.3.2",

8
msa/js-executor/yarn.lock

@ -1665,10 +1665,10 @@ jws@^4.0.0:
jwa "^2.0.0"
safe-buffer "^5.0.1"
kafkajs@^1.14.0:
version "1.14.0"
resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.14.0.tgz#3d998a77bfde54dc502e8e88690eedf0b21a1ed6"
integrity sha512-W+WCekiooY5rJP3Me5N3gWcQ8O6uG6lw0vv9t+sI+WqXKjKwj2+CWIXJy241x+ITE+1M1D19ABSiL2J8lKja5A==
kafkajs@^1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/kafkajs/-/kafkajs-1.15.0.tgz#a5ada0d933edca2149177393562be6fb0875ec3a"
integrity sha512-yjPyEnQCkPxAuQLIJnY5dI+xnmmgXmhuOQ1GVxClG5KTOV/rJcW1qA3UfvyEJKTp/RTSqQnUR3HJsKFvHyTpNg==
keyv@^3.0.0:
version "3.1.0"

5
pom.xml

@ -92,7 +92,7 @@
</sonar.exclusions>
<elasticsearch.version>5.0.2</elasticsearch.version>
<delight-nashorn-sandbox.version>0.1.14</delight-nashorn-sandbox.version>
<kafka.version>2.3.0</kafka.version>
<kafka.version>2.6.0</kafka.version>
<bucket4j.version>4.1.1</bucket4j.version>
<fst.version>2.57</fst.version>
<antlr.version>2.7.7</antlr.version>
@ -729,7 +729,8 @@
<exclude>docker/haproxy/**</exclude>
<exclude>docker/tb-node/**</exclude>
<exclude>ui/**</exclude>
<exclude>src/browserslist</exclude>
<exclude>src/.browserslistrc</exclude>
<exclude>**/yarn.lock</exclude>
<exclude>**/*.raw</exclude>
<exclude>**/apache/cassandra/io/**</exclude>
<exclude>.run/**</exclude>

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

@ -160,8 +160,6 @@ queue:
max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
# JS response poll interval
response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
# JS response auto commit interval
response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
rule-engine:
topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"

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

@ -153,8 +153,6 @@ queue:
max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
# JS response poll interval
response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
# JS response auto commit interval
response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
rule-engine:
topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"

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

@ -182,8 +182,6 @@ queue:
max_requests_timeout: "${REMOTE_JS_MAX_REQUEST_TIMEOUT:10000}"
# JS response poll interval
response_poll_interval: "${REMOTE_JS_RESPONSE_POLL_INTERVAL_MS:25}"
# JS response auto commit interval
response_auto_commit_interval: "${REMOTE_JS_RESPONSE_AUTO_COMMIT_INTERVAL_MS:100}"
rule-engine:
topic: "${TB_QUEUE_RULE_ENGINE_TOPIC:tb_rule_engine}"
poll-interval: "${TB_QUEUE_RULE_ENGINE_POLL_INTERVAL_MS:25}"

5
ui-ngx/angular.json

@ -59,6 +59,11 @@
"glob": "marker-shadow.png",
"input": "node_modules/leaflet/dist/images/",
"output": "/"
},
{
"glob": "**/*",
"input": "node_modules/material-design-icons/iconfont/",
"output": "assets/fonts"
}
],
"styles": [

16
ui-ngx/src/app/core/http/device-profile.service.ts

@ -69,4 +69,20 @@ export class DeviceProfileService {
return this.http.get<PageData<DeviceProfileInfo>>(url, defaultHttpOptionsFromConfig(config));
}
public getDeviceProfileDevicesAttributesKeys(deviceProfileId?: string, config?: RequestConfig): Observable<Array<string>> {
let url = `/api/deviceProfile/devices/keys/attributes`;
if (isDefinedAndNotNull(deviceProfileId)) {
url += `?deviceProfileId=${deviceProfileId}`;
}
return this.http.get<Array<string>>(url, defaultHttpOptionsFromConfig(config));
}
public getDeviceProfileDevicesTimeseriesKeys(deviceProfileId?: string, config?: RequestConfig): Observable<Array<string>> {
let url = `/api/deviceProfile/devices/keys/timeseries`;
if (isDefinedAndNotNull(deviceProfileId)) {
url += `?deviceProfileId=${deviceProfileId}`;
}
return this.http.get<Array<string>>(url, defaultHttpOptionsFromConfig(config));
}
}

1
ui-ngx/src/app/core/translate/translate-default-compiler.ts

@ -65,6 +65,7 @@ export class TranslateDefaultCompiler extends TranslateMessageFormatCompiler {
} catch (e) {
console.warn(`Failed to parse source: ${src}`);
console.error(e);
return false;
}
const res = tokens.filter(
(value) => typeof value !== 'string' && value.type === 'plural'

18
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.html

@ -40,11 +40,19 @@
<mat-form-field fxFlex="60" class="mat-block">
<mat-label translate>filter.key-name</mat-label>
<input matInput required formControlName="key"
[matAutocomplete]="auto"
[matAutocompleteDisabled]="keyFilterFormGroup.get('key.type').value !== entityField">
<mat-autocomplete autoActiveFirstOption #auto="matAutocomplete">
<mat-option *ngFor="let option of filteredEntityFields | async" [value]="option">
{{option}}
#keyNameInput
(focusin)="onFocus()"
[matAutocomplete]="keyName"
[matAutocompleteDisabled]="!showAutocomplete">
<button *ngIf="keyFilterFormGroup.get('key.key').value && showAutocomplete"
type="button"
matSuffix mat-button mat-icon-button aria-label="Clear"
(click)="clear()">
<mat-icon class="material-icons">close</mat-icon>
</button>
<mat-autocomplete autoActiveFirstOption #keyName="matAutocomplete">
<mat-option *ngFor="let keyName of filteredKeysName | async" [value]="keyName">
<span [innerHTML]="keyName | highlight:searchText"></span>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="keyFilterFormGroup.get('key.key').hasError('required')">

124
ui-ngx/src/app/modules/home/components/filter/key-filter-dialog.component.ts

@ -14,7 +14,7 @@
/// limitations under the License.
///
import { Component, Inject, OnInit, SkipSelf } from '@angular/core';
import { Component, ElementRef, Inject, OnDestroy, OnInit, SkipSelf, ViewChild } from '@angular/core';
import { ErrorStateMatcher } from '@angular/material/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material/dialog';
import { Store } from '@ngrx/store';
@ -32,9 +32,12 @@ import {
} from '@shared/models/query/query.models';
import { DialogService } from '@core/services/dialog.service';
import { TranslateService } from '@ngx-translate/core';
import { EntityField, entityFields } from '@shared/models/entity.models';
import { Observable } from 'rxjs';
import { filter, map, startWith } from 'rxjs/operators';
import { entityFields } from '@shared/models/entity.models';
import { Observable, of, Subject } from 'rxjs';
import { filter, map, mergeMap, publishReplay, refCount, startWith, takeUntil } from 'rxjs/operators';
import { isDefined } from '@core/utils';
import { EntityId } from '@shared/models/id/entity-id';
import { DeviceProfileService } from '@core/http/device-profile.service';
export interface KeyFilterDialogData {
keyFilter: KeyFilterInfo;
@ -43,6 +46,7 @@ export interface KeyFilterDialogData {
allowUserDynamicSource: boolean;
readonly: boolean;
telemetryKeysOnly: boolean;
entityId?: EntityId;
}
@Component({
@ -53,7 +57,13 @@ export interface KeyFilterDialogData {
})
export class KeyFilterDialogComponent extends
DialogComponent<KeyFilterDialogComponent, KeyFilterInfo>
implements OnInit, ErrorStateMatcher {
implements OnInit, OnDestroy, ErrorStateMatcher {
@ViewChild('keyNameInput', {static: true}) private keyNameInput: ElementRef;
private dirty = false;
private entityKeysName: Observable<Array<string>>;
private destroy$ = new Subject();
keyFilterFormGroup: FormGroup;
@ -72,19 +82,18 @@ export class KeyFilterDialogComponent extends
submitted = false;
entityFields: { [fieldName: string]: EntityField };
entityFieldsList: string[];
showAutocomplete = false;
readonly entityField = EntityKeyType.ENTITY_FIELD;
filteredKeysName: Observable<Array<string>>;
filteredEntityFields: Observable<string[]>;
searchText = '';
constructor(protected store: Store<AppState>,
protected router: Router,
@Inject(MAT_DIALOG_DATA) public data: KeyFilterDialogData,
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<KeyFilterDialogComponent, KeyFilterInfo>,
private deviceProfileService: DeviceProfileService,
private dialogs: DialogService,
private translate: TranslateService,
private fb: FormBuilder) {
@ -104,7 +113,9 @@ export class KeyFilterDialogComponent extends
);
if (!this.data.readonly) {
this.keyFilterFormGroup.get('valueType').valueChanges.subscribe((valueType: EntityKeyValueType) => {
this.keyFilterFormGroup.get('valueType').valueChanges.pipe(
takeUntil(this.destroy$)
).subscribe((valueType: EntityKeyValueType) => {
const prevValue: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
const predicates: KeyFilterPredicate[] = this.keyFilterFormGroup.get('predicates').value;
if (prevValue && prevValue !== valueType && predicates && predicates.length) {
@ -121,11 +132,26 @@ export class KeyFilterDialogComponent extends
}
});
this.keyFilterFormGroup.get('key.type').valueChanges.pipe(
startWith(this.data.keyFilter.key.type),
takeUntil(this.destroy$)
).subscribe((type: EntityKeyType) => {
if (type === EntityKeyType.ENTITY_FIELD || isDefined(this.data.entityId)) {
this.entityKeysName = null;
this.dirty = false;
this.showAutocomplete = true;
} else {
this.showAutocomplete = false;
}
});
this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
filter((keyName) => this.keyFilterFormGroup.get('key.type').value === this.entityField && this.entityFields.hasOwnProperty(keyName))
filter((keyName) =>
this.keyFilterFormGroup.get('key.type').value === EntityKeyType.ENTITY_FIELD && entityFields.hasOwnProperty(keyName)),
takeUntil(this.destroy$)
).subscribe((keyName: string) => {
const prevValueType: EntityKeyValueType = this.keyFilterFormGroup.value.valueType;
const newValueType = this.entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
const newValueType = entityFields[keyName]?.time ? EntityKeyValueType.DATE_TIME : EntityKeyValueType.STRING;
if (prevValueType !== newValueType) {
this.keyFilterFormGroup.get('valueType').patchValue(newValueType, {emitEvent: false});
}
@ -133,18 +159,20 @@ export class KeyFilterDialogComponent extends
} else {
this.keyFilterFormGroup.disable({emitEvent: false});
}
}
this.entityFields = entityFields;
this.entityFieldsList = Object.values(entityFields).map(entityField => entityField.keyName).sort();
ngOnInit() {
this.filteredKeysName = this.keyFilterFormGroup.get('key.key').valueChanges
.pipe(
map(value => value ? value : ''),
mergeMap(name => this.fetchEntityName(name))
);
}
ngOnInit(): void {
this.filteredEntityFields = this.keyFilterFormGroup.get('key.key').valueChanges.pipe(
startWith(''),
map(value => {
return this.entityFieldsList.filter(option => option.startsWith(value));
})
);
ngOnDestroy() {
super.ngOnDestroy();
this.destroy$.next();
this.destroy$.complete();
}
isErrorState(control: FormControl | null, form: FormGroupDirective | NgForm | null): boolean {
@ -157,6 +185,21 @@ export class KeyFilterDialogComponent extends
this.dialogRef.close(null);
}
clear() {
this.keyFilterFormGroup.get('key.key').patchValue('', {emitEvent: true});
setTimeout(() => {
this.keyNameInput.nativeElement.blur();
this.keyNameInput.nativeElement.focus();
}, 0);
}
onFocus() {
if (!this.dirty && this.showAutocomplete) {
this.keyFilterFormGroup.get('key.key').updateValueAndValidity({onlySelf: true, emitEvent: true});
this.dirty = true;
}
}
save(): void {
this.submitted = true;
if (this.keyFilterFormGroup.valid) {
@ -164,4 +207,41 @@ export class KeyFilterDialogComponent extends
this.dialogRef.close(keyFilter);
}
}
private fetchEntityName(searchText?: string): Observable<Array<string>> {
this.searchText = searchText;
return this.getEntityKeys().pipe(
map(keys => searchText ? keys.filter(key => key.toUpperCase().startsWith(searchText.toUpperCase())) : keys)
);
}
private getEntityKeys(): Observable<Array<string>> {
if (!this.entityKeysName) {
let keyNameObservable: Observable<Array<string>>;
switch (this.keyFilterFormGroup.get('key.type').value) {
case EntityKeyType.ENTITY_FIELD:
keyNameObservable = of(Object.values(entityFields).map(entityField => entityField.keyName).sort());
break;
case EntityKeyType.ATTRIBUTE:
keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesAttributesKeys(
this.data.entityId?.id,
{ignoreLoading: true}
);
break;
case EntityKeyType.TIME_SERIES:
keyNameObservable = this.deviceProfileService.getDeviceProfileDevicesTimeseriesKeys(
this.data.entityId?.id,
{ignoreLoading: true}
);
break;
default:
keyNameObservable = of([]);
}
this.entityKeysName = keyNameObservable.pipe(
publishReplay(1),
refCount()
);
}
return this.entityKeysName;
}
}

13
ui-ngx/src/app/modules/home/components/filter/key-filter-list.component.ts

@ -19,7 +19,8 @@ import {
AbstractControl,
ControlValueAccessor,
FormArray,
FormBuilder, FormControl,
FormBuilder,
FormControl,
FormGroup,
NG_VALUE_ACCESSOR,
Validators
@ -28,12 +29,13 @@ import { Observable, Subscription } from 'rxjs';
import {
EntityKeyType,
entityKeyTypeTranslationMap,
KeyFilter,
KeyFilterInfo, keyFilterInfosToKeyFilters
KeyFilterInfo,
keyFilterInfosToKeyFilters
} from '@shared/models/query/query.models';
import { MatDialog } from '@angular/material/dialog';
import { deepClone } from '@core/utils';
import { KeyFilterDialogComponent, KeyFilterDialogData } from '@home/components/filter/key-filter-dialog.component';
import { EntityId } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-key-filter-list',
@ -57,6 +59,8 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
@Input() telemetryKeysOnly = false;
@Input() entityId: EntityId;
keyFilterListFormGroup: FormGroup;
entityKeyTypeTranslations = entityKeyTypeTranslationMap;
@ -170,7 +174,8 @@ export class KeyFilterListComponent implements ControlValueAccessor, OnInit {
readonly: this.disabled,
displayUserParameters: this.displayUserParameters,
allowUserDynamicSource: this.allowUserDynamicSource,
telemetryKeysOnly: this.telemetryKeysOnly
telemetryKeysOnly: this.telemetryKeysOnly,
entityId: this.entityId
}
}).afterClosed();
}

3
ui-ngx/src/app/modules/home/components/profile/add-device-profile-dialog.component.html

@ -96,7 +96,8 @@
{count: alarmRulesFormGroup.get('alarms').value ?
alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
<tb-device-profile-alarms
formControlName="alarms">
formControlName="alarms"
[deviceProfileId]="null">
</tb-device-profile-alarms>
</form>
</mat-step>

1
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.html

@ -34,6 +34,7 @@
[displayUserParameters]="false"
[allowUserDynamicSource]="false"
[telemetryKeysOnly]="true"
[entityId]="entityId"
formControlName="keyFilters">
</tb-key-filter-list>
<section formGroupName="spec" class="row">

7
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition-dialog.component.ts

@ -22,15 +22,16 @@ import { AppState } from '@core/core.state';
import { FormBuilder, FormControl, FormGroup, FormGroupDirective, NgForm, Validators } from '@angular/forms';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import { UtilsService } from '@core/services/utils.service';
import { TranslateService } from '@ngx-translate/core';
import { KeyFilter, keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models';
import { keyFilterInfosToKeyFilters, keyFiltersToKeyFilterInfos } from '@shared/models/query/query.models';
import { AlarmCondition, AlarmConditionType, AlarmConditionTypeTranslationMap } from '@shared/models/device.models';
import { TimeUnit, timeUnitTranslationMap } from '@shared/models/time/time.models';
import { EntityId } from '@shared/models/id/entity-id';
export interface AlarmRuleConditionDialogData {
readonly: boolean;
condition: AlarmCondition;
entityId?: EntityId;
}
@Component({
@ -50,6 +51,7 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
readonly = this.data.readonly;
condition = this.data.condition;
entityId = this.data.entityId;
conditionFormGroup: FormGroup;
@ -61,7 +63,6 @@ export class AlarmRuleConditionDialogComponent extends DialogComponent<AlarmRule
@SkipSelf() private errorStateMatcher: ErrorStateMatcher,
public dialogRef: MatDialogRef<AlarmRuleConditionDialogComponent, AlarmCondition>,
private fb: FormBuilder,
private utils: UtilsService,
public translate: TranslateService) {
super(store, router, dialogRef);

7
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.ts

@ -33,6 +33,7 @@ import {
AlarmRuleConditionDialogData
} from '@home/components/profile/alarm/alarm-rule-condition-dialog.component';
import { TimeUnit } from '@shared/models/time/time.models';
import { EntityId } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-alarm-rule-condition',
@ -56,6 +57,9 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
@Input()
disabled: boolean;
@Input()
deviceProfileId: EntityId;
alarmRuleConditionFormGroup: FormGroup;
specText = '';
@ -123,7 +127,8 @@ export class AlarmRuleConditionComponent implements ControlValueAccessor, OnInit
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
readonly: this.disabled,
condition: this.disabled ? this.modelValue : deepClone(this.modelValue)
condition: this.disabled ? this.modelValue : deepClone(this.modelValue),
entityId: this.deviceProfileId
}
}).afterClosed().subscribe((result) => {
if (result) {

2
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.html

@ -16,7 +16,7 @@
-->
<div fxLayout="column" [formGroup]="alarmRuleFormGroup">
<tb-alarm-rule-condition formControlName="condition">
<tb-alarm-rule-condition formControlName="condition" [deviceProfileId]="deviceProfileId">
</tb-alarm-rule-condition>
<tb-alarm-schedule-info formControlName="schedule">
</tb-alarm-schedule-info>

4
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule.component.ts

@ -33,6 +33,7 @@ import {
EditAlarmDetailsDialogComponent,
EditAlarmDetailsDialogData
} from '@home/components/profile/alarm/edit-alarm-details-dialog.component';
import { EntityId } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-alarm-rule',
@ -65,6 +66,9 @@ export class AlarmRuleComponent implements ControlValueAccessor, OnInit, Validat
this.requiredValue = coerceBooleanProperty(value);
}
@Input()
deviceProfileId: EntityId;
private modelValue: AlarmRule;
alarmRuleFormGroup: FormGroup;

2
ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.html

@ -35,7 +35,7 @@
</mat-error>
</mat-form-field>
<mat-divider vertical></mat-divider>
<tb-alarm-rule formControlName="alarmRule" required fxFlex>
<tb-alarm-rule formControlName="alarmRule" [deviceProfileId]="deviceProfileId" required fxFlex>
</tb-alarm-rule>
</div>
<button *ngIf="!disabled"

6
ui-ngx/src/app/modules/home/components/profile/alarm/create-alarm-rules.component.ts

@ -30,7 +30,8 @@ import {
import { AlarmRule, alarmRuleValidator } from '@shared/models/device.models';
import { MatDialog } from '@angular/material/dialog';
import { Subscription } from 'rxjs';
import { AlarmSeverity, alarmSeverityTranslations } from '../../../../../shared/models/alarm.models';
import { AlarmSeverity, alarmSeverityTranslations } from '@shared/models/alarm.models';
import { EntityId } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-create-alarm-rules',
@ -59,6 +60,9 @@ export class CreateAlarmRulesComponent implements ControlValueAccessor, OnInit,
@Input()
disabled: boolean;
@Input()
deviceProfileId: EntityId;
createAlarmRulesFormGroup: FormGroup;
private usedSeverities: AlarmSeverity[] = [];

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

@ -80,14 +80,16 @@
</mat-expansion-panel>
<div fxFlex fxLayout="column">
<div translate class="tb-small" style="padding-bottom: 8px;">device-profile.create-alarm-rules</div>
<tb-create-alarm-rules formControlName="createRules" style="padding-bottom: 16px;">
<tb-create-alarm-rules formControlName="createRules"
style="padding-bottom: 16px;"
[deviceProfileId]="deviceProfileId">
</tb-create-alarm-rules>
<div translate class="tb-small" style="padding-bottom: 8px;">device-profile.clear-alarm-rule</div>
<div fxLayout="row" fxLayoutGap="8px;" fxLayoutAlign="start center"
[fxShow]="alarmFormGroup.get('clearRule').value"
style="padding-bottom: 8px;">
<div class="clear-alarm-rule" fxFlex fxLayout="row">
<tb-alarm-rule formControlName="clearRule" fxFlex>
<tb-alarm-rule formControlName="clearRule" fxFlex [deviceProfileId]="deviceProfileId">
</tb-alarm-rule>
</div>
<button *ngIf="!disabled"

4
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts

@ -29,6 +29,7 @@ import { AlarmRule, DeviceProfileAlarm, deviceProfileAlarmValidator } from '@sha
import { MatDialog } from '@angular/material/dialog';
import { COMMA, ENTER, SEMICOLON } from '@angular/cdk/keycodes';
import { MatChipInputEvent } from '@angular/material/chips';
import { EntityId } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-device-profile-alarm',
@ -60,6 +61,9 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
@Input()
expanded = false;
@Input()
deviceProfileId: EntityId;
private modelValue: DeviceProfileAlarm;
alarmFormGroup: FormGroup;

1
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.html

@ -22,6 +22,7 @@
fxLayout="column" [ngStyle]="!isLast ? {paddingBottom: '8px'} : {}">
<tb-device-profile-alarm [formControl]="alarmControl"
[expanded]="$index === 0"
[deviceProfileId]="deviceProfileId"
(removeAlarm)="removeAlarm($index)">
</tb-device-profile-alarm>
</div>

4
ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarms.component.ts

@ -34,6 +34,7 @@ import { DeviceProfileAlarm, deviceProfileAlarmValidator } from '@shared/models/
import { guid } from '@core/utils';
import { Subscription } from 'rxjs';
import { MatDialog } from '@angular/material/dialog';
import { EntityId } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-device-profile-alarms',
@ -68,6 +69,9 @@ export class DeviceProfileAlarmsComponent implements ControlValueAccessor, OnIni
@Input()
disabled: boolean;
@Input()
deviceProfileId: EntityId;
private valueChangeSubscription: Subscription = null;
private propagateChange = (v: any) => { };

3
ui-ngx/src/app/modules/home/components/profile/device-profile.component.html

@ -116,7 +116,8 @@
</mat-panel-title>
</mat-expansion-panel-header>
<tb-device-profile-alarms
formControlName="alarms">
formControlName="alarms"
[deviceProfileId]="deviceProfileId">
</tb-device-profile-alarms>
</mat-expansion-panel>
<mat-expansion-panel [expanded]="true">

5
ui-ngx/src/app/modules/home/components/profile/device-profile.component.ts

@ -39,6 +39,7 @@ import {
import { EntityType } from '@shared/models/entity-type.models';
import { RuleChainId } from '@shared/models/id/rule-chain-id';
import { ServiceType } from '@shared/models/queue.models';
import { EntityId } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-device-profile',
@ -66,6 +67,8 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
serviceType = ServiceType.TB_RULE_ENGINE;
deviceProfileId: EntityId;
constructor(protected store: Store<AppState>,
protected translate: TranslateService,
@Optional() @Inject('entity') protected entityValue: DeviceProfile,
@ -83,6 +86,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
}
buildForm(entity: DeviceProfile): FormGroup {
this.deviceProfileId = entity?.id ? entity.id : null;
this.displayProfileConfiguration = entity && entity.type &&
deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration;
this.displayTransportConfiguration = entity && entity.transportType &&
@ -157,6 +161,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> {
}
updateForm(entity: DeviceProfile) {
this.deviceProfileId = entity.id;
this.displayProfileConfiguration = entity.type &&
deviceProfileTypeConfigurationInfoMap.get(entity.type).hasProfileConfiguration;
this.displayTransportConfiguration = entity.transportType &&

20
ui-ngx/src/app/modules/home/components/widget/action/custom-sample-html.raw

@ -178,17 +178,17 @@
<!-- </div>-->
<!-- </div>-->
<!-- <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">-->
<!-- <button mat-button mat-raised-button color="primary"-->
<!-- type="submit"-->
<!-- [disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">-->
<!-- Save-->
<!-- </button>-->
<!-- <button mat-button color="primary"-->
<!-- type="button"-->
<!-- [disabled]="(isLoading$ | async)"-->
<!-- (click)="cancel()" cdkFocusInitial>-->
<!-- Cancel-->
<!-- </button>-->
<!-- <button mat-button mat-raised-button color="primary"-->
<!-- type="submit"-->
<!-- [disabled]="(isLoading$ | async) || editEntityForm.invalid || !editEntityForm.dirty">-->
<!-- Save-->
<!-- </button>-->
<!-- </div>-->
<!--</form>-->
<!---->
@ -338,16 +338,16 @@
<!-- </div>-->
<!-- </div>-->
<!-- <div mat-dialog-actions fxLayout="row" fxLayoutAlign="end center">-->
<!-- <button mat-button mat-raised-button color="primary"-->
<!-- type="submit"-->
<!-- [disabled]="(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty">-->
<!-- Create-->
<!-- </button>-->
<!-- <button mat-button color="primary"-->
<!-- type="button"-->
<!-- [disabled]="(isLoading$ | async)"-->
<!-- (click)="cancel()" cdkFocusInitial>-->
<!-- Cancel-->
<!-- </button>-->
<!-- <button mat-button mat-raised-button color="primary"-->
<!-- type="submit"-->
<!-- [disabled]="(isLoading$ | async) || addEntityForm.invalid || !addEntityForm.dirty">-->
<!-- Create-->
<!-- </button>-->
<!-- </div>-->
<!--</form>-->

7
ui-ngx/src/app/modules/home/components/widget/lib/photo-camera-input.component.ts

@ -283,6 +283,13 @@ export class PhotoCameraInputWidgetComponent extends PageComponent implements On
window.navigator.mediaDevices.getUserMedia(videoTrackConstraints).then((stream: MediaStream) => {
if (init) {
this.isShowCamera = true;
if (this.availableVideoInputs.find((device) => device.deviceId === '')) {
PhotoCameraInputWidgetComponent.getAvailableVideoInputs().then((devices) => {
this.singleDevice = devices.length < 2;
this.availableVideoInputs = devices;
this.ctx.detectChanges();
});
}
}
this.mediaStream = stream;
this.videoElement.srcObject = stream;

5
ui-ngx/src/app/modules/home/components/wizard/device-wizard-dialog.component.html

@ -76,7 +76,7 @@
[required]="!createProfile"
[transportType]="deviceWizardFormGroup.get('transportType').value"
formControlName="deviceProfileId"
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
[ngClass]="{invisible: deviceWizardFormGroup.get('addProfileType').value !== 0}"
(deviceProfileChanged)="$event?.transportType ? deviceWizardFormGroup.get('transportType').patchValue($event?.transportType) : {}"
[addNewProfile]="false"
[selectDefaultProfile]="true"
@ -133,7 +133,8 @@
{count: alarmRulesFormGroup.get('alarms').value ?
alarmRulesFormGroup.get('alarms').value.length : 0} }}</ng-template>
<tb-device-profile-alarms
formControlName="alarms">
formControlName="alarms"
[deviceProfileId]="null">
</tb-device-profile-alarms>
</form>
</mat-step>

2
ui-ngx/src/app/modules/home/models/services.map.ts

@ -32,9 +32,11 @@ import { DashboardService } from '@core/http/dashboard.service';
import { UserService } from '@core/http/user.service';
import { AlarmService } from '@core/http/alarm.service';
import { Router } from '@angular/router';
import { BroadcastService } from "@core/services/broadcast.service";
export const ServicesMap = new Map<string, Type<any>>(
[
['broadcastService', BroadcastService],
['deviceService', DeviceService],
['alarmService', AlarmService],
['assetService', AssetService],

2
ui-ngx/src/app/modules/home/pages/device-profile/device-profile-tabs.component.html

@ -46,7 +46,7 @@
}}" #alarmRules="matTab">
<div class="mat-padding" [formGroup]="detailsForm">
<div formGroupName="profileData">
<tb-device-profile-alarms formControlName="alarms"></tb-device-profile-alarms>
<tb-device-profile-alarms formControlName="alarms" [deviceProfileId]="entity.id"></tb-device-profile-alarms>
</div>
</div>
</mat-tab>

21
ui-ngx/src/assets/locale/locale.constant-cs_CZ.json

@ -1937,25 +1937,6 @@
}
},
"language": {
"language": "Jazyk",
"locales": {
"de_DE": "Deutsch",
"fr_FR": "Français",
"zh_CN": "简体中文",
"zh_TW": "繁體中文",
"en_US": "English",
"it_IT": "Italiano",
"ko_KR": "한글",
"ru_RU": "Русский",
"es_ES": "Español",
"ja_JA": "日本語",
"tr_TR": "Türkçe",
"fa_IR": "فارسي",
"uk_UA": "Українська",
"cs_CZ": "Česky",
"el_GR": "Ελληνικά",
"ro_RO": "Română",
"lv_LV": "Latviešu"
}
"language": "Jazyk"
}
}

3
ui-ngx/src/assets/locale/locale.constant-en_US.json

@ -2494,7 +2494,8 @@
"ro_RO": "Română",
"lv_LV": "Latviešu",
"ka_GE": "ქართული",
"pt_BR": "Português do Brasil"
"pt_BR": "Português do Brasil",
"sl_SI": "Slovenščina"
}
}
}

2
ui-ngx/src/assets/locale/locale.constant-ko_KR.json

@ -266,7 +266,7 @@
"add-to-dashboard": "대시보드에 추가",
"add-widget-to-dashboard": "대시보드에 위젯 추가",
"selected-attributes": "{ count, plural, 1 {속성 1개} other {속성 #개} } 선택됨",
"selected-telemetry": "{ count, plural, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨"
"selected-telemetry": "{ count, plural, 1 {최근 데이터 1개} other {최근 데이터 #개} } 선택됨",
"no-attributes-text": "아무 속성도 찾을 수 없습니다",
"no-telemetry-text": "아무 텔레메트리도 찾을 수 없습니다."
},

2479
ui-ngx/src/assets/locale/locale.constant-sl_SI.json

File diff suppressed because it is too large

2
ui-ngx/src/index.html

@ -24,6 +24,8 @@
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="icon" type="image/x-icon" href="thingsboard.ico">
<link rel="preload" href="assets/fonts/MaterialIcons-Regular.woff2" as="font" type="font/woff2" crossorigin="anonymous" />
<link rel="stylesheet" href="assets/fonts/material-icons.css" />
<style type="text/css">
body, html {

1
ui-ngx/src/styles.scss

@ -15,7 +15,6 @@
*/
/* You can add global styles to this file, and also import other style files */
@import '~material-design-icons/iconfont/material-icons.css';
@import '~typeface-roboto/index.css';
@import '~font-awesome/css/font-awesome.min.css';
@import 'theme.scss';

Loading…
Cancel
Save