Browse Source

Merge branch 'master' of github.com:thingsboard/thingsboard into feature/californium-update

# Conflicts:
#	common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
#	common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java
pull/5930/head
YevhenBondarenko 5 years ago
parent
commit
57510b4d54
  1. 4
      application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java
  2. 56
      application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java
  3. 32
      application/src/main/java/org/thingsboard/server/service/install/HsqlTsDatabaseSchemaService.java
  4. 6
      application/src/main/java/org/thingsboard/server/service/install/SqlDatabaseUpgradeService.java
  5. 3
      application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java
  6. 2
      application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java
  7. 4
      application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java
  8. 2
      common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java
  9. 8
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java
  10. 2
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
  11. 8
      common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java
  12. 2
      common/edge-api/src/main/proto/edge.proto
  13. 12
      common/message/src/main/java/org/thingsboard/server/common/msg/TbMsg.java
  14. 30
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java
  15. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java
  16. 27
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java
  17. 162
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/TbLwM2MDtlsBootstrapCertificateVerifier.java
  18. 137
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java
  19. 8
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java
  20. 80
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java
  21. 5
      common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java
  22. 3
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  23. 45
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  24. 2
      dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
  25. 14
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  26. 4
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  27. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java
  28. 2
      dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmQueryRepository.java
  29. 32
      dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java
  30. 599
      dao/src/main/resources/sql/schema-entities-hsql.sql
  31. 4
      dao/src/main/resources/sql/schema-entities.sql
  32. 41
      dao/src/main/resources/sql/schema-ts-hsql.sql
  33. 20
      dao/src/main/resources/sql/schema-types-hsql.sql
  34. 78
      dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java
  35. 2
      pom.xml
  36. 2
      rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java
  37. 10
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java
  38. 4
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java
  39. 172
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java
  40. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java
  41. 2
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java
  42. 3
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java
  43. 16
      rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java
  44. 2
      rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js
  45. 163
      rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java
  46. 3
      ui-ngx/angular.json
  47. 1
      ui-ngx/package.json
  48. 2
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts
  49. 2
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts
  50. 15
      ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts
  51. 3
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.html
  52. 8
      ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts
  53. 61
      ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts
  54. 30
      ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts
  55. 38
      ui-ngx/src/app/modules/home/components/event/event-table-config.ts
  56. 2
      ui-ngx/src/app/modules/home/components/event/event-table-header.component.ts
  57. 17
      ui-ngx/src/app/modules/home/components/event/event-table.component.ts
  58. 5
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.html
  59. 5
      ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss
  60. 6
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.html
  61. 4
      ui-ngx/src/app/modules/home/components/profile/alarm/device-profile-alarm.component.ts
  62. 1
      ui-ngx/src/app/modules/home/components/profile/alarm/edit-alarm-details-dialog.component.html
  63. 2
      ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss
  64. 5
      ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts
  65. 40
      ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts
  66. 19
      ui-ngx/src/app/modules/home/models/entity/entity-details-page-component.models.ts
  67. 2
      ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts
  68. 25
      ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts
  69. 2
      ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts
  70. 28
      ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts
  71. 6
      ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts
  72. 2
      ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts
  73. 2
      ui-ngx/src/app/modules/home/pages/device/device.component.ts
  74. 24
      ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts
  75. 2
      ui-ngx/src/app/modules/home/pages/edge/edge-table-header.component.ts
  76. 16
      ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts
  77. 2
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts
  78. 18
      ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts
  79. 20
      ui-ngx/src/app/modules/home/pages/rulechain/rulechains-table-config.resolver.ts
  80. 6
      ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts
  81. 2
      ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts
  82. 2
      ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts
  83. 2
      ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html
  84. 2
      ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts
  85. 4
      ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts
  86. 9
      ui-ngx/src/app/shared/models/base-data.ts
  87. 2
      ui-ngx/src/app/shared/models/device.models.ts
  88. 1
      ui-ngx/src/app/shared/models/public-api.ts
  89. 37
      ui-ngx/src/app/shared/pipe/safe.pipe.ts
  90. 4
      ui-ngx/src/app/shared/shared.module.ts
  91. 9
      ui-ngx/src/assets/locale/locale.constant-en_US.json
  92. 4
      ui-ngx/src/assets/locale/locale.constant-ru_RU.json
  93. 4
      ui-ngx/src/assets/locale/locale.constant-uk_UA.json
  94. 5
      ui-ngx/yarn.lock

4
application/src/main/java/org/thingsboard/server/service/edge/rpc/constructor/AlarmMsgConstructor.java

@ -71,7 +71,9 @@ public class AlarmMsgConstructor {
.setAckTs(alarm.getAckTs()) .setAckTs(alarm.getAckTs())
.setClearTs(alarm.getClearTs()) .setClearTs(alarm.getClearTs())
.setDetails(JacksonUtil.toString(alarm.getDetails())) .setDetails(JacksonUtil.toString(alarm.getDetails()))
.setPropagate(alarm.isPropagate()); .setPropagate(alarm.isPropagate())
.setPropagateToOwner(alarm.isPropagateToOwner())
.setPropagateToTenant(alarm.isPropagateToTenant());
return builder.build(); return builder.build();
} }

56
application/src/main/java/org/thingsboard/server/service/install/HsqlEntityDatabaseSchemaService.java

@ -1,56 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.HsqlDao;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.sql.DriverManager;
@Service
@Slf4j
@HsqlDao
@Profile("install")
public class HsqlEntityDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements EntityDatabaseSchemaService {
protected HsqlEntityDatabaseSchemaService() {
super("schema-entities-hsql.sql", "schema-entities-idx.sql");
}
private final String schemaTypesSql = "schema-types-hsql.sql";
@Override
public void createDatabaseSchema(boolean createIndexes) throws Exception {
log.info("Installing SQL DataBase types part: " + schemaTypesSql);
Path schemaFile = Paths.get(installScripts.getDataDir(), SQL_DIR, schemaTypesSql);
try (Connection conn = DriverManager.getConnection(dbUrl, dbUserName, dbPassword)) {
String sql = new String(Files.readAllBytes(schemaFile), Charset.forName("UTF-8"));
conn.createStatement().execute(sql); //NOSONAR, ignoring because method used to load initial thingsboard database schema
}
super.createDatabaseSchema(createIndexes);
}
}

32
application/src/main/java/org/thingsboard/server/service/install/HsqlTsDatabaseSchemaService.java

@ -1,32 +0,0 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.service.install;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Service;
import org.thingsboard.server.dao.util.HsqlDao;
import org.thingsboard.server.dao.util.SqlTsDao;
@Service
@SqlTsDao
@HsqlDao
@Profile("install")
public class HsqlTsDatabaseSchemaService extends SqlAbstractDatabaseSchemaService
implements TsDatabaseSchemaService {
public HsqlTsDatabaseSchemaService() {
super("schema-ts-hsql.sql", null);
}
}

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

@ -475,6 +475,12 @@ public class SqlDatabaseUpgradeService implements DatabaseEntitiesUpgradeService
log.info("Updating schema ..."); log.info("Updating schema ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL); schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL);
loadSql(schemaUpdateFile, conn); loadSql(schemaUpdateFile, conn);
try {
conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_to_owner boolean DEFAULT false;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
conn.createStatement().execute("ALTER TABLE alarm ADD COLUMN propagate_to_tenant boolean DEFAULT false;"); //NOSONAR, ignoring because method used to execute thingsboard database upgrade script
} catch (Exception ignored) {
}
try { try {
conn.createStatement().execute("insert into entity_alarm(tenant_id, entity_id, created_time, alarm_type, customer_id, alarm_id)" + conn.createStatement().execute("insert into entity_alarm(tenant_id, entity_id, created_time, alarm_type, customer_id, alarm_id)" +
" select tenant_id, originator_id, created_time, type, customer_id, id from alarm ON CONFLICT DO NOTHING;"); " select tenant_id, originator_id, created_time, type, customer_id, id from alarm ON CONFLICT DO NOTHING;");

3
application/src/main/java/org/thingsboard/server/service/query/DefaultEntityQueryService.java

@ -106,8 +106,7 @@ public class DefaultEntityQueryService implements EntityQueryService {
for (EntityData entityData : entities.getData()) { for (EntityData entityData : entities.getData()) {
entitiesMap.put(entityData.getEntityId(), entityData); entitiesMap.put(entityData.getEntityId(), entityData);
} }
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(securityUser.getTenantId(), PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(securityUser.getTenantId(), query, entitiesMap.keySet());
securityUser.getCustomerId(), query, entitiesMap.keySet());
for (AlarmData alarmData : alarms.getData()) { for (AlarmData alarmData : alarms.getData()) {
EntityId entityId = alarmData.getEntityId(); EntityId entityId = alarmData.getEntityId();
if (entityId != null) { if (entityId != null) {

2
application/src/main/java/org/thingsboard/server/service/subscription/TbAlarmDataSubCtx.java

@ -106,7 +106,7 @@ public class TbAlarmDataSubCtx extends TbAbstractDataSubCtx<AlarmDataQuery> {
AlarmDataUpdate update; AlarmDataUpdate update;
if (!entitiesMap.isEmpty()) { if (!entitiesMap.isEmpty()) {
long start = System.currentTimeMillis(); long start = System.currentTimeMillis();
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), getCustomerId(), query, getOrderedEntityIds()); PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(getTenantId(), query, getOrderedEntityIds());
long end = System.currentTimeMillis(); long end = System.currentTimeMillis();
stats.getAlarmQueryInvocationCnt().incrementAndGet(); stats.getAlarmQueryInvocationCnt().incrementAndGet();
stats.getAlarmQueryTimeSpent().addAndGet(end - start); stats.getAlarmQueryTimeSpent().addAndGet(end - start);

4
application/src/main/java/org/thingsboard/server/service/telemetry/DefaultAlarmSubscriptionService.java

@ -150,8 +150,8 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService
} }
@Override @Override
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) { public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return alarmService.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds); return alarmService.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
} }
@Override @Override

2
common/dao-api/src/main/java/org/thingsboard/server/dao/alarm/AlarmService.java

@ -61,7 +61,7 @@ public interface AlarmService {
ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type); ListenableFuture<Alarm> findLatestByOriginatorAndType(TenantId tenantId, EntityId originator, String type);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds); AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId); void deleteEntityAlarmRelations(TenantId tenantId, EntityId entityId);

8
common/data/src/main/java/org/thingsboard/server/common/data/alarm/Alarm.java

@ -70,7 +70,11 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
private transient JsonNode details; private transient JsonNode details;
@ApiModelProperty(position = 15, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") @ApiModelProperty(position = 15, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
private boolean propagate; private boolean propagate;
@ApiModelProperty(position = 16, value = "JSON array of relation types that should be used for propagation. " + @ApiModelProperty(position = 16, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
private boolean propagateToOwner;
@ApiModelProperty(position = 17, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
private boolean propagateToTenant;
@ApiModelProperty(position = 18, value = "JSON array of relation types that should be used for propagation. " +
"By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " + "By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " +
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")
private List<String> propagateRelationTypes; private List<String> propagateRelationTypes;
@ -98,6 +102,8 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
this.clearTs = alarm.getClearTs(); this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails(); this.details = alarm.getDetails();
this.propagate = alarm.isPropagate(); this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant();
this.propagateRelationTypes = alarm.getPropagateRelationTypes(); this.propagateRelationTypes = alarm.getPropagateRelationTypes();
} }

2
common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java

@ -23,7 +23,7 @@ public class AlarmInfo extends Alarm {
private static final long serialVersionUID = 2807343093519543363L; private static final long serialVersionUID = 2807343093519543363L;
@ApiModelProperty(position = 17, value = "Alarm originator name", example = "Thermostat") @ApiModelProperty(position = 19, value = "Alarm originator name", example = "Thermostat")
private String originatorName; private String originatorName;
public AlarmInfo() { public AlarmInfo() {

8
common/data/src/main/java/org/thingsboard/server/common/data/device/profile/DeviceProfileAlarm.java

@ -49,8 +49,14 @@ public class DeviceProfileAlarm implements Serializable {
// Hidden in advanced settings // Hidden in advanced settings
@ApiModelProperty(position = 5, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true") @ApiModelProperty(position = 5, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
private boolean propagate; private boolean propagate;
@ApiModelProperty(position = 6, value = "JSON array of relation types that should be used for propagation. " + @ApiModelProperty(position = 6, value = "Propagation flag to specify if alarm should be propagated to the owner (tenant or customer) of alarm originator", example = "true")
private boolean propagateToOwner;
@ApiModelProperty(position = 7, value = "Propagation flag to specify if alarm should be propagated to the tenant entity", example = "true")
private boolean propagateToTenant;
@ApiModelProperty(position = 8, value = "JSON array of relation types that should be used for propagation. " +
"By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " + "By default, 'propagateRelationTypes' array is empty which means that the alarm will be propagated based on any relation type to parent entities. " +
"This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.") "This parameter should be used only in case when 'propagate' parameter is set to true, otherwise, 'propagateRelationTypes' array will be ignored.")
private List<String> propagateRelationTypes; private List<String> propagateRelationTypes;
} }

2
common/edge-api/src/main/proto/edge.proto

@ -269,6 +269,8 @@ message AlarmUpdateMsg {
int64 clearTs = 13; int64 clearTs = 13;
string details = 14; string details = 14;
bool propagate = 15; bool propagate = 15;
bool propagateToOwner = 16;
bool propagateToTenant = 17;
} }
message CustomerUpdateMsg { message CustomerUpdateMsg {

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

@ -22,6 +22,7 @@ import lombok.AccessLevel;
import lombok.Data; import lombok.Data;
import lombok.Getter; import lombok.Getter;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
@ -296,4 +297,15 @@ public final class TbMsg implements Serializable {
public boolean isValid() { public boolean isValid() {
return getCallback().isMsgValid(); return getCallback().isMsgValid();
} }
public long getMetaDataTs() {
String tsStr = metaData.getValue("ts");
if (!StringUtils.isEmpty(tsStr)) {
try {
return Long.parseLong(tsStr);
} catch (NumberFormatException ignored) {
}
}
return ts;
}
} }

30
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/LwM2MTransportBootstrapService.java

@ -27,6 +27,7 @@ import org.thingsboard.server.common.transport.TransportService;
import org.thingsboard.server.common.transport.config.ssl.SslCredentials; import org.thingsboard.server.common.transport.config.ssl.SslCredentials;
import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent; import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager; import org.thingsboard.server.transport.lwm2m.bootstrap.secure.LwM2mDefaultBootstrapSessionManager;
import org.thingsboard.server.transport.lwm2m.bootstrap.secure.TbLwM2MDtlsBootstrapCertificateVerifier;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore; import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBootstrapConfigStore; import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MInMemoryBootstrapConfigStore;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig; import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportBootstrapConfig;
@ -39,6 +40,7 @@ import java.security.cert.X509Certificate;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CIPHER_SUITES_ONLY;
import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY; import static org.eclipse.californium.scandium.config.DtlsConfig.DTLS_RECOMMENDED_CURVES_ONLY;
import static org.thingsboard.server.transport.lwm2m.server.DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES;
import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig; import static org.thingsboard.server.transport.lwm2m.server.LwM2MNetworkConfig.getCoapConfig;
@Slf4j @Slf4j
@ -53,6 +55,7 @@ public class LwM2MTransportBootstrapService {
private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore; private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore; private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
private final TransportService transportService; private final TransportService transportService;
private final TbLwM2MDtlsBootstrapCertificateVerifier certificateVerifier;
private LeshanBootstrapServer server; private LeshanBootstrapServer server;
@PostConstruct @PostConstruct
@ -78,25 +81,24 @@ public class LwM2MTransportBootstrapService {
/* Create CoAP Config */ /* Create CoAP Config */
builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); builder.setCoapConfig(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
/* Create credentials */
this.setServerWithCredentials(builder);
/* Set securityStore with new ConfigStore */
builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
/* SecurityStore */
builder.setSecurityStore(lwM2MBootstrapSecurityStore);
/* Create and Set DTLS Config */ /* Create and Set DTLS Config */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig)); DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups()); dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups());
dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers()); dtlsConfig.set(DTLS_RECOMMENDED_CIPHER_SUITES_ONLY, serverConfig.isRecommendedCiphers());
dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, this.pskMode ? DefaultLwM2mTransportService.PSK_CIPHER_SUITES : DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES); dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, this.pskMode ? DefaultLwM2mTransportService.PSK_CIPHER_SUITES : DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES);
setServerWithCredentials(builder, dtlsConfig);
/* Set DTLS Config */ /* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig); builder.setDtlsConfig(dtlsConfig);
/* Set securityStore with new ConfigStore */
builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
/* SecurityStore */
builder.setSecurityStore(lwM2MBootstrapSecurityStore);
BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService); BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService);
builder.setSessionManager(sessionManager); builder.setSessionManager(sessionManager);
@ -104,18 +106,14 @@ public class LwM2MTransportBootstrapService {
return builder.build(); return builder.build();
} }
private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) { private void setServerWithCredentials(LeshanBootstrapServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) {
if (this.bootstrapConfig.getSslCredentials() != null) { if (this.bootstrapConfig.getSslCredentials() != null) {
SslCredentials sslCredentials = this.bootstrapConfig.getSslCredentials(); SslCredentials sslCredentials = this.bootstrapConfig.getSslCredentials();
builder.setPublicKey(sslCredentials.getPublicKey()); builder.setPublicKey(sslCredentials.getPublicKey());
builder.setPrivateKey(sslCredentials.getPrivateKey()); builder.setPrivateKey(sslCredentials.getPrivateKey());
builder.setCertificateChain(sslCredentials.getCertificateChain()); builder.setCertificateChain(sslCredentials.getCertificateChain());
if (this.serverConfig.getTrustSslCredentials() != null) { dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, this.pskMode ? DefaultLwM2mTransportService.PSK_CIPHER_SUITES : DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES);
builder.setTrustedCertificates(this.serverConfig.getTrustSslCredentials().getTrustedCertificates()); dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier);
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
} else { } else {
/* by default trust all */ /* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]); builder.setTrustedCertificates(new X509Certificate[0]);

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2MBootstrapConfig.java

@ -31,9 +31,7 @@ import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.Abstrac
import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential; import org.thingsboard.server.common.data.device.profile.lwm2m.bootstrap.LwM2MBootstrapServerCredential;
import java.io.Serializable; import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.List; import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j @Slf4j
@Data @Data
@ -97,7 +95,6 @@ public class LwM2MBootstrapConfig implements Serializable {
byte[] secretKey = new byte[]{}; byte[] secretKey = new byte[]{};
byte[] serverPublicKey = new byte[]{}; byte[] serverPublicKey = new byte[]{};
serverSecurity.serverId = serverCredential.getShortServerId(); serverSecurity.serverId = serverCredential.getShortServerId();
log.info("serverId = [{}]", serverSecurity.serverId);
serverSecurity.securityMode = SecurityMode.valueOf(securityMode.name()); serverSecurity.securityMode = SecurityMode.valueOf(securityMode.name());
serverSecurity.bootstrapServer = serverCredential.isBootstrapServerIs(); serverSecurity.bootstrapServer = serverCredential.isBootstrapServerIs();
if (!LwM2MSecurityMode.NO_SEC.equals(securityMode)) { if (!LwM2MSecurityMode.NO_SEC.equals(securityMode)) {
@ -112,22 +109,17 @@ public class LwM2MBootstrapConfig implements Serializable {
if (LwM2MSecurityMode.PSK.equals(securityMode)) { if (LwM2MSecurityMode.PSK.equals(securityMode)) {
publicKeyOrId = server.getClientPublicKeyOrId().getBytes(); publicKeyOrId = server.getClientPublicKeyOrId().getBytes();
secretKey = Hex.decodeHex(server.getClientSecretKey().toCharArray()); secretKey = Hex.decodeHex(server.getClientSecretKey().toCharArray());
log.info("publicKeyOrId [{}]", new String(publicKeyOrId, StandardCharsets.UTF_8));
} else { } else {
publicKeyOrId = server.getDecodedClientPublicKeyOrId(); publicKeyOrId = server.getDecodedClientPublicKeyOrId();
secretKey = server.getDecodedClientSecretKey(); secretKey = server.getDecodedClientSecretKey();
log.info("publicKeyOrId [{}]", Hex.encodeHexString(publicKeyOrId));
} }
serverPublicKey = serverCredential.getDecodedCServerPublicKey(); serverPublicKey = serverCredential.getDecodedCServerPublicKey();
} }
log.info("secretKey [{}]", Hex.encodeHexString(secretKey));
serverUri += (((serverCredential.getHost().equals("0.0.0.0") ? "localhost" : serverCredential.getHost()) + ":" + serverCredential.getPort())); serverUri += (((serverCredential.getHost().equals("0.0.0.0") ? "localhost" : serverCredential.getHost()) + ":" + serverCredential.getPort()));
log.info("serverSecurity.uri = [{}]", serverUri);
serverSecurity.uri = serverUri; serverSecurity.uri = serverUri;
serverSecurity.publicKeyOrId = publicKeyOrId; serverSecurity.publicKeyOrId = publicKeyOrId;
serverSecurity.secretKey = secretKey; serverSecurity.secretKey = secretKey;
serverSecurity.serverPublicKey = serverPublicKey; serverSecurity.serverPublicKey = serverPublicKey;
log.info("server [{}]", Hex.encodeHexString(serverSecurity.serverPublicKey));
return serverSecurity; return serverSecurity;
} }

27
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/LwM2mDefaultBootstrapSessionManager.java

@ -38,6 +38,7 @@ import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecu
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
@ -83,15 +84,16 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
@Override @Override
public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) { public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
boolean authorized = true; boolean authorized = true;
Iterator<SecurityInfo> securityInfos; Iterator<SecurityInfo> securityInfos = null;
try { try {
if (bsSecurityStore != null && securityChecker != null) { if (bsSecurityStore != null && securityChecker != null) {
if (clientIdentity.isSecure() && clientIdentity.isPSK()) { if (clientIdentity.isPSK()) {
securityInfos = bsSecurityStore.getAllByEndpoint(clientIdentity.getPskIdentity()); SecurityInfo securityInfo = bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity());
} else { securityInfos = Collections.singletonList(securityInfo).iterator();
} else if (!clientIdentity.isX509()) {
securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName()); securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName());
} }
authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos); authorized = this.checkSecurityInfo(request.getEndpointName(), clientIdentity, securityInfos);
} }
} catch (LwM2MAuthException e) { } catch (LwM2MAuthException e) {
authorized = false; authorized = false;
@ -187,7 +189,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
// store response // store response
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession; DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
session.getResponses().add(response); session.getResponses().add(response);
this.sendLogs (bsSession.getEndpoint(), this.sendLogs(bsSession.getEndpoint(),
String.format("%s: %s %s receives error response %s ", LOG_LWM2M_INFO, String.format("%s: %s %s receives error response %s ", LOG_LWM2M_INFO,
request.getClass().getSimpleName(), request.getClass().getSimpleName(),
request.getPath().toString(), response.toString())); request.getPath().toString(), response.toString()));
@ -195,7 +197,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
return BootstrapPolicy.continueWith(nextRequest(bsSession)); return BootstrapPolicy.continueWith(nextRequest(bsSession));
} else { } else {
// on response error for bootstrap finish request we stop the session // on response error for bootstrap finish request we stop the session
this.sendLogs (bsSession.getEndpoint(), this.sendLogs(bsSession.getEndpoint(),
String.format("%s: error response for request bootstrap finish. Stop the session: %s", LOG_LWM2M_ERROR, bsSession.toString())); String.format("%s: error response for request bootstrap finish. Stop the session: %s", LOG_LWM2M_ERROR, bsSession.toString()));
return BootstrapPolicy.failed(); return BootstrapPolicy.failed();
} }
@ -222,7 +224,16 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
} }
private void sendLogs(String endpointName, String logMsg) { private void sendLogs(String endpointName, String logMsg) {
log.info(logMsg); log.info("Endpoint: [{}] [{}]", endpointName, logMsg);
transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg); transportService.log(((LwM2MBootstrapSecurityStore) bsSecurityStore).getSessionByEndpoint(endpointName), logMsg);
} }
private boolean checkSecurityInfo(String endpoint, Identity clientIdentity, Iterator<SecurityInfo> securityInfos) {
if (clientIdentity.isX509()) {
return clientIdentity.getX509CommonName().equals(endpoint)
& ((LwM2MBootstrapSecurityStore) bsSecurityStore).getBootstrapConfigByEndpoint(endpoint) != null;
} else {
return securityChecker.checkSecurityInfos(endpoint, clientIdentity, securityInfos);
}
}
} }

162
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/secure/TbLwM2MDtlsBootstrapCertificateVerifier.java

@ -0,0 +1,162 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.server.transport.lwm2m.bootstrap.secure;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.eclipse.californium.elements.auth.RawPublicKeyIdentity;
import org.eclipse.californium.elements.util.CertPathUtil;
import org.eclipse.californium.scandium.dtls.AlertMessage;
import org.eclipse.californium.scandium.dtls.CertificateMessage;
import org.eclipse.californium.scandium.dtls.CertificateType;
import org.eclipse.californium.scandium.dtls.CertificateVerificationResult;
import org.eclipse.californium.scandium.dtls.ConnectionId;
import org.eclipse.californium.scandium.dtls.HandshakeException;
import org.eclipse.californium.scandium.dtls.HandshakeResultHandler;
import org.eclipse.californium.scandium.dtls.x509.NewAdvancedCertificateVerifier;
import org.eclipse.californium.scandium.dtls.x509.StaticNewAdvancedCertificateVerifier;
import org.eclipse.californium.scandium.util.ServerNames;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.thingsboard.server.common.data.StringUtils;
import org.thingsboard.server.common.msg.EncryptionUtil;
import org.thingsboard.server.common.transport.auth.ValidateDeviceCredentialsResponse;
import org.thingsboard.server.common.transport.util.SslUtil;
import org.thingsboard.server.queue.util.TbLwM2mBootstrapTransportComponent;
import org.thingsboard.server.transport.lwm2m.bootstrap.store.LwM2MBootstrapSecurityStore;
import org.thingsboard.server.transport.lwm2m.config.LwM2MTransportServerConfig;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import javax.annotation.PostConstruct;
import javax.security.auth.x500.X500Principal;
import java.net.InetSocketAddress;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.List;
@Slf4j
@Component
@TbLwM2mBootstrapTransportComponent
@RequiredArgsConstructor
public class TbLwM2MDtlsBootstrapCertificateVerifier implements NewAdvancedCertificateVerifier {
private final LwM2MTransportServerConfig config;
private final LwM2MBootstrapSecurityStore bsSecurityStore;
private StaticNewAdvancedCertificateVerifier staticCertificateVerifier;
@Value("${transport.lwm2m.server.security.skip_validity_check_for_client_cert:false}")
private boolean skipValidityCheckForClientCert;
@Override
public List<CertificateType> getSupportedCertificateTypes() {
return Arrays.asList(CertificateType.X_509, CertificateType.RAW_PUBLIC_KEY);
}
@SuppressWarnings("deprecation")
@PostConstruct
public void init() {
try {
/* by default trust all */
if (config.getTrustSslCredentials() != null) {
X509Certificate[] trustedCertificates = config.getTrustSslCredentials().getTrustedCertificates();
staticCertificateVerifier = new StaticNewAdvancedCertificateVerifier(trustedCertificates, new RawPublicKeyIdentity[0], null);
}
} catch (Exception e) {
log.warn("ailed to initialize the LwM2M certificate verifier", e);
}
}
@Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, InetSocketAddress remotePeer,
boolean clientUsage, boolean verifySubject, boolean truncateCertificatePath,
CertificateMessage message) {
CertPath certChain = message.getCertificateChain();
if (certChain == null) {
//We trust all RPK on this layer, and use TbLwM2MAuthorizer
PublicKey publicKey = message.getPublicKey();
return new CertificateVerificationResult(cid, publicKey, null);
} else {
try {
boolean x509CredentialsFound = false;
X509Certificate[] chain = certChain.getCertificates().toArray(new X509Certificate[0]);
for (X509Certificate cert : chain) {
try {
if (!skipValidityCheckForClientCert) {
cert.checkValidity();
}
TbLwM2MSecurityInfo securityInfo = null;
// verify if trust
if (staticCertificateVerifier != null) {
HandshakeException exception = staticCertificateVerifier.verifyCertificate(cid, serverName, remotePeer, clientUsage, verifySubject, truncateCertificatePath, message).getException();
if (exception == null) {
String endpoint = config.getTrustSslCredentials().getValueFromSubjectNameByKey(cert.getSubjectX500Principal().getName(), "CN");
if (StringUtils.isNotEmpty(endpoint)) {
securityInfo = bsSecurityStore.getX509ByEndpoint(endpoint);
}
} else {
log.trace("Certificate validation failed.", exception);
}
}
// if not trust or cert trust securityInfo == null
if (securityInfo == null || securityInfo.getMsg() == null) {
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
try {
securityInfo = bsSecurityStore.getX509ByEndpoint(sha3Hash);
} catch (LwM2MAuthException e) {
log.trace("Failed to find security info: [{}]", sha3Hash, e);
}
}
ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null;
if (msg != null && StringUtils.isNotEmpty(msg.getCredentials())) {
x509CredentialsFound = true;
break;
}
} catch (CertificateEncodingException |
CertificateExpiredException |
CertificateNotYetValidException e) {
log.trace("Failed to find security info: [{}]", cert.getSubjectX500Principal().getName(), e);
}
}
if (!x509CredentialsFound) {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR);
throw new HandshakeException("x509 verification not enabled!", alert);
}
return new CertificateVerificationResult(cid, certChain, null);
} catch (HandshakeException e) {
log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null);
}
}
}
@Override
public List<X500Principal> getAcceptedIssuers() {
return CertPathUtil.toSubjects(null);
}
@Override
public void setResultHandler(HandshakeResultHandler resultHandler) {
}
}

137
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/bootstrap/store/LwM2MBootstrapSecurityStore.java

@ -33,6 +33,7 @@ import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener; import org.thingsboard.server.transport.lwm2m.server.LwM2mSessionMsgListener;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper; import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.Collections; import java.util.Collections;
import java.util.Iterator; import java.util.Iterator;
@ -67,67 +68,44 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
} }
@Override @Override
public Iterator<SecurityInfo> getAllByEndpoint(String endPoint) { public Iterator<SecurityInfo> getAllByEndpoint(String endpoint) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP); TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endpoint, BOOTSTRAP);
if (store.getBootstrapCredentialConfig() != null) { SecurityInfo securityInfo = this.addValueToStore(store, endpoint);
/* add value to store from BootstrapJson */ return securityInfo == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator();
this.setBootstrapConfigScurityInfo(store);
endPoint = store.getEndpoint();
BootstrapConfig bsConfigNew = store.getBootstrapConfig();
if (bsConfigNew != null) {
try {
boolean bootstrapServerUpdateEnable = ((Lwm2mDeviceProfileTransportConfiguration)store.getDeviceProfile().getProfileData().getTransportConfiguration()).isBootstrapServerUpdateEnable();
if (!bootstrapServerUpdateEnable) {
Optional<Map.Entry<Integer, BootstrapConfig.ServerSecurity>> securities = bsConfigNew.security.entrySet().stream().filter(sec -> sec.getValue().bootstrapServer).findAny();
if (securities.isPresent()) {
bsConfigNew.security.entrySet().remove(securities.get());
int serverSortId = securities.get().getValue().serverId;
Optional<Map.Entry<Integer, BootstrapConfig.ServerConfig>> serverConfigs = bsConfigNew.servers.entrySet().stream().filter(serv -> ((BootstrapConfig.ServerConfig)serv.getValue()).shortId==serverSortId).findAny();
if (serverConfigs.isPresent()) {
bsConfigNew.servers.entrySet().remove(serverConfigs.get());
}
}
}
for (String config : bootstrapConfigStore.getAll().keySet()) {
if (config.equals(endPoint)) {
bootstrapConfigStore.remove(config);
}
}
bootstrapConfigStore.add(endPoint, bsConfigNew);
} catch (InvalidConfigurationException e) {
if (e.getMessage().contains("Psk identity") && e.getMessage().contains("already used for this bootstrap server")) {
log.trace("Invalid Bootstrap Configuration", e);
}
else {
log.error("Invalid Bootstrap Configuration", e);
}
}
return store.getSecurityInfo() == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator();
}
}
return null;
} }
@Override @Override
public SecurityInfo getByIdentity(String identity) { public SecurityInfo getByIdentity(String identity) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, BOOTSTRAP); try {
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) { TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, BOOTSTRAP);
/* add value to store from BootstrapJson */ if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
this.setBootstrapConfigScurityInfo(store); /* add value to store from BootstrapJson */
BootstrapConfig bsConfig = store.getBootstrapConfig(); this.setBootstrapConfigSecurityInfo(store);
if (bsConfig.security != null) { BootstrapConfig bsConfig = store.getBootstrapConfig();
try { if (bsConfig.security != null) {
bootstrapConfigStore.add(store.getEndpoint(), bsConfig); try {
} catch (InvalidConfigurationException e) { bootstrapConfigStore.add(store.getEndpoint(), bsConfig);
log.error("Invalid Bootstrap Configuration", e); } catch (InvalidConfigurationException e) {
log.trace("Invalid Bootstrap Configuration", e);
return null;
}
} }
return store.getSecurityInfo();
} }
return store.getSecurityInfo();
} catch (LwM2MAuthException e) {
log.trace("Bootstrap Registration failed: No pre-shared key found for [identity: {}]", identity);
return null;
} }
return null;
} }
private void setBootstrapConfigScurityInfo(TbLwM2MSecurityInfo store) { public TbLwM2MSecurityInfo getX509ByEndpoint(String endPoint) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP);
this.addValueToStore(store, store.getEndpoint());
return store;
}
private void setBootstrapConfigSecurityInfo(TbLwM2MSecurityInfo store) {
/* BootstrapConfig */ /* BootstrapConfig */
LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store); LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store);
if (lwM2MBootstrapConfig != null) { if (lwM2MBootstrapConfig != null) {
@ -163,25 +141,23 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
* *
* @return false if not sync between SecurityMode of Bootstrap credential and profile * @return false if not sync between SecurityMode of Bootstrap credential and profile
*/ */
// private boolean getValidatedSecurityMode(LwM2MServerBootstrap bootstrapFromCredential, LwM2MServerBootstrap bootstrapServerProfile, LwM2MServerBootstrap lwm2mFromCredential, LwM2MServerBootstrap profileLwm2mServer) {
private boolean getValidatedSecurityMode(LwM2MBootstrapConfig lwM2MBootstrapConfig) { private boolean getValidatedSecurityMode(LwM2MBootstrapConfig lwM2MBootstrapConfig) {
LwM2MSecurityMode bootstrapServerSecurityMode = lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode(); LwM2MSecurityMode bootstrapServerSecurityMode = lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode();
LwM2MSecurityMode lwm2mServerSecurityMode = lwM2MBootstrapConfig.getLwm2mServer().getSecurityMode(); LwM2MSecurityMode lwm2mServerSecurityMode = lwM2MBootstrapConfig.getLwm2mServer().getSecurityMode();
AtomicBoolean validBs = new AtomicBoolean(true); AtomicBoolean validBs = new AtomicBoolean(true);
AtomicBoolean validLw = new AtomicBoolean(true); AtomicBoolean validLw = new AtomicBoolean(true);
lwM2MBootstrapConfig.getServerConfiguration().forEach(serverCredential -> { lwM2MBootstrapConfig.getServerConfiguration().forEach(serverCredential -> {
if (((AbstractLwM2MBootstrapServerCredential)serverCredential).isBootstrapServerIs()) { if (((AbstractLwM2MBootstrapServerCredential) serverCredential).isBootstrapServerIs()) {
if (!bootstrapServerSecurityMode.equals(serverCredential.getSecurityMode())) { if (!bootstrapServerSecurityMode.equals(serverCredential.getSecurityMode())) {
validBs.set(false); validBs.set(false);
} }
} } else {
else {
if (!lwm2mServerSecurityMode.equals(serverCredential.getSecurityMode())) { if (!lwm2mServerSecurityMode.equals(serverCredential.getSecurityMode())) {
validLw.set(false); validLw.set(false);
} }
} }
}); });
return validBs.get()&validLw.get(); return validBs.get() && validLw.get();
} }
public TransportProtos.SessionInfoProto getSessionByEndpoint(String endpoint) { public TransportProtos.SessionInfoProto getSessionByEndpoint(String endpoint) {
@ -191,4 +167,47 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
public TransportProtos.SessionInfoProto removeSessionByEndpoint(String endpoint) { public TransportProtos.SessionInfoProto removeSessionByEndpoint(String endpoint) {
return bsSessions.remove(endpoint); return bsSessions.remove(endpoint);
} }
}
public BootstrapConfig getBootstrapConfigByEndpoint(String endpoint) {
return bootstrapConfigStore.getAll().get(endpoint);
}
public SecurityInfo addValueToStore(TbLwM2MSecurityInfo store, String endpoint) {
/* add value to store from BootstrapJson */
SecurityInfo securityInfo = null;
if (store != null && store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
securityInfo = store.getSecurityInfo();
this.setBootstrapConfigSecurityInfo(store);
BootstrapConfig bsConfigNew = store.getBootstrapConfig();
if (bsConfigNew != null) {
try {
boolean bootstrapServerUpdateEnable = ((Lwm2mDeviceProfileTransportConfiguration) store.getDeviceProfile().getProfileData().getTransportConfiguration()).isBootstrapServerUpdateEnable();
if (!bootstrapServerUpdateEnable) {
Optional<Map.Entry<Integer, BootstrapConfig.ServerSecurity>> securities = bsConfigNew.security.entrySet().stream().filter(sec -> sec.getValue().bootstrapServer).findAny();
if (securities.isPresent()) {
bsConfigNew.security.entrySet().remove(securities.get());
int serverSortId = securities.get().getValue().serverId;
Optional<Map.Entry<Integer, BootstrapConfig.ServerConfig>> serverConfigs = bsConfigNew.servers.entrySet().stream().filter(serv -> (serv.getValue()).shortId == serverSortId).findAny();
if (serverConfigs.isPresent()) {
bsConfigNew.servers.entrySet().remove(serverConfigs.get());
}
}
}
for (String config : bootstrapConfigStore.getAll().keySet()) {
if (config.equals(endpoint)) {
bootstrapConfigStore.remove(config);
}
}
bootstrapConfigStore.add(endpoint, bsConfigNew);
} catch (InvalidConfigurationException e) {
if (e.getMessage().contains("Psk identity") && e.getMessage().contains("already used for this bootstrap server")) {
log.trace("Invalid Bootstrap Configuration", e);
} else {
log.error("Invalid Bootstrap Configuration", e);
}
}
}
}
return securityInfo;
}
}

8
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/LwM2mCredentialsSecurityInfoValidator.java

@ -49,7 +49,6 @@ import static org.eclipse.leshan.core.SecurityMode.PSK;
import static org.eclipse.leshan.core.SecurityMode.RPK; import static org.eclipse.leshan.core.SecurityMode.RPK;
import static org.eclipse.leshan.core.SecurityMode.X509; import static org.eclipse.leshan.core.SecurityMode.X509;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP; import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
@Slf4j @Slf4j
@Component @Component
@ -89,7 +88,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
} }
TbLwM2MSecurityInfo securityInfo = resultSecurityStore[0]; TbLwM2MSecurityInfo securityInfo = resultSecurityStore[0];
if ((CLIENT.equals(keyValue) && securityInfo.getSecurityMode() == null)) { if (securityInfo.getSecurityMode() == null) {
throw new LwM2MAuthException(); throw new LwM2MAuthException();
} }
return securityInfo; return securityInfo;
@ -119,12 +118,11 @@ public class LwM2mCredentialsSecurityInfoValidator {
createClientSecurityInfoRPK(result, endpoint, credentials.getClient()); createClientSecurityInfoRPK(result, endpoint, credentials.getClient());
break; break;
case X509: case X509:
createClientSecurityInfoX509(result, endpoint, credentials.getClient()); createClientSecurityInfoX509(result, endpoint);
break; break;
default: default:
break; break;
} }
// } else
if (keyValue.equals(BOOTSTRAP)) { if (keyValue.equals(BOOTSTRAP)) {
LwM2MBootstrapConfig bootstrapCredentialConfig = new LwM2MBootstrapConfig(((Lwm2mDeviceProfileTransportConfiguration) msg.getDeviceProfile().getProfileData().getTransportConfiguration()).getBootstrap(), LwM2MBootstrapConfig bootstrapCredentialConfig = new LwM2MBootstrapConfig(((Lwm2mDeviceProfileTransportConfiguration) msg.getDeviceProfile().getProfileData().getTransportConfiguration()).getBootstrap(),
credentials.getBootstrap().getBootstrapServer(), credentials.getBootstrap().getLwm2mServer()); credentials.getBootstrap().getBootstrapServer(), credentials.getBootstrap().getLwm2mServer());
@ -173,7 +171,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
} }
} }
private void createClientSecurityInfoX509(TbLwM2MSecurityInfo result, String endpoint, LwM2MClientCredential clientCredentialsConfig) { private void createClientSecurityInfoX509(TbLwM2MSecurityInfo result, String endpoint) {
result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint)); result.setSecurityInfo(SecurityInfo.newX509CertInfo(endpoint));
result.setSecurityMode(X509); result.setSecurityMode(X509);
} }

80
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/secure/TbLwM2MDtlsCertificateVerifier.java

@ -50,22 +50,13 @@ import org.thingsboard.server.transport.lwm2m.server.store.TbMainSecurityStore;
import javax.annotation.PostConstruct; import javax.annotation.PostConstruct;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey; import java.security.PublicKey;
import java.security.cert.CertPath; import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException; import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException; import java.security.cert.CertificateNotYetValidException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.List; import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT; import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
@ -95,18 +86,19 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
public void init() { public void init() {
try { try {
/* by default trust all */ /* by default trust all */
X509Certificate[] trustedCertificates = new X509Certificate[0];
if (config.getTrustSslCredentials() != null) { if (config.getTrustSslCredentials() != null) {
trustedCertificates = config.getTrustSslCredentials().getTrustedCertificates(); X509Certificate[] trustedCertificates = config.getTrustSslCredentials().getTrustedCertificates();
staticCertificateVerifier = new StaticNewAdvancedCertificateVerifier(trustedCertificates, new RawPublicKeyIdentity[0], null);
} }
staticCertificateVerifier = new StaticNewAdvancedCertificateVerifier(trustedCertificates, new RawPublicKeyIdentity[]{}, null);
} catch (Exception e) { } catch (Exception e) {
log.info("Failed to initialize the "); log.warn("Failed to initialize the LwM2M certificate verifier", e);
} }
} }
@Override @Override
public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, InetSocketAddress remotePeer, boolean clientUsage, boolean verifySubject, boolean truncateCertificatePath, CertificateMessage message) { public CertificateVerificationResult verifyCertificate(ConnectionId cid, ServerNames serverName, InetSocketAddress remotePeer,
boolean clientUsage, boolean verifySubject, boolean truncateCertificatePath,
CertificateMessage message) {
CertPath certChain = message.getCertificateChain(); CertPath certChain = message.getCertificateChain();
if (certChain == null) { if (certChain == null) {
//We trust all RPK on this layer, and use TbLwM2MAuthorizer //We trust all RPK on this layer, and use TbLwM2MAuthorizer
@ -115,27 +107,32 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
} else { } else {
try { try {
boolean x509CredentialsFound = false; boolean x509CredentialsFound = false;
CertPath certpath = message.getCertificateChain(); X509Certificate[] chain = certChain.getCertificates().toArray(new X509Certificate[0]);
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
for (X509Certificate cert : chain) { for (X509Certificate cert : chain) {
try { try {
if (!skipValidityCheckForClientCert) { if (!skipValidityCheckForClientCert) {
cert.checkValidity(); cert.checkValidity();
} }
TbLwM2MSecurityInfo securityInfo = null; TbLwM2MSecurityInfo securityInfo = null;
// verify if trust if (staticCertificateVerifier != null) {
if (config.getTrustSslCredentials() != null && config.getTrustSslCredentials().getTrustedCertificates().length > 0) { HandshakeException exception = staticCertificateVerifier.verifyCertificate(cid, serverName, remotePeer, clientUsage, verifySubject, truncateCertificatePath, message).getException();
if (verifyTrust(cert, config.getTrustSslCredentials().getTrustedCertificates()) != null) { if (exception == null) {
String endpoint = config.getTrustSslCredentials().getValueFromSubjectNameByKey(cert.getSubjectX500Principal().getName(), "CN"); try {
securityInfo = StringUtils.isNotEmpty(endpoint) ? securityInfoValidator.getEndpointSecurityInfoByCredentialsId(endpoint, CLIENT) : null; String endpoint = config.getTrustSslCredentials().getValueFromSubjectNameByKey(cert.getSubjectX500Principal().getName(), "CN");
if (StringUtils.isNotEmpty(endpoint)) {
securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(endpoint, CLIENT);
}
} catch (LwM2MAuthException e) {
log.trace("Certificate trust validation failed.", e);
}
} else {
log.trace("Certificate trust validation failed.", exception);
} }
} }
// if not trust or cert trust securityInfo == null // if not trust or cert trust securityInfo == null
String strCert = SslUtil.getCertificateString(cert); String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert); String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
if (securityInfo == null) { if (securityInfo == null || securityInfo.getMsg() == null) {
try { try {
securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT); securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT);
} catch (LwM2MAuthException e) { } catch (LwM2MAuthException e) {
@ -143,7 +140,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
} }
} }
ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null; ValidateDeviceCredentialsResponse msg = securityInfo != null ? securityInfo.getMsg() : null;
if (msg != null && org.thingsboard.server.common.data.StringUtils.isNotEmpty(msg.getCredentials())) { if (msg != null && StringUtils.isNotEmpty(msg.getCredentials())) {
LwM2MClientCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MClientCredentials.class); LwM2MClientCredentials credentials = JacksonUtil.fromString(msg.getCredentials(), LwM2MClientCredentials.class);
if (!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)) { if (!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)) {
continue; continue;
@ -174,14 +171,10 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
} }
} }
if (!x509CredentialsFound) { if (!x509CredentialsFound) {
if (staticCertificateVerifier != null) { AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR);
staticCertificateVerifier.verifyCertificate(cid, serverName, remotePeer, clientUsage, verifySubject, truncateCertificatePath, message); throw new HandshakeException("x509 verification not enabled!", alert);
} else {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR);
throw new HandshakeException("x509 verification not enabled!", alert);
}
} }
return new CertificateVerificationResult(cid, certpath, null); return new CertificateVerificationResult(cid, certChain, null);
} catch (HandshakeException e) { } catch (HandshakeException e) {
log.trace("Certificate validation failed!", e); log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null); return new CertificateVerificationResult(cid, e, null);
@ -198,27 +191,4 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
public void setResultHandler(HandshakeResultHandler resultHandler) { public void setResultHandler(HandshakeResultHandler resultHandler) {
} }
private X509Certificate verifyTrust(X509Certificate certificate, X509Certificate[] certificates) {
try {
CertificateFactory cf = CertificateFactory.getInstance("X.509");
CertPath cp = cf.generateCertPath(Arrays.asList(new X509Certificate[]{certificate}));
for (int index = 0; index < certificates.length; ++index) {
X509Certificate caCert = certificates[index];
try {
TrustAnchor trustAnchor = new TrustAnchor(caCert, null);
CertPathValidator cpv = CertPathValidator.getInstance("PKIX");
PKIXParameters pkixParams = new PKIXParameters(
Collections.singleton(trustAnchor));
pkixParams.setRevocationEnabled(false);
if (cpv.validate(cp, pkixParams) != null) return certificate;
} catch (NoSuchAlgorithmException | InvalidAlgorithmParameterException | CertPathValidatorException e) {
log.trace("[{}]. [{}]", certificate.getSubjectDN(), e.getMessage());
}
}
} catch (CertificateException e) {
log.trace("[{}] certPath not valid. [{}]", certificate.getSubjectDN(), e.getMessage());
}
return null;
}
} }

5
common/transport/lwm2m/src/main/java/org/thingsboard/server/transport/lwm2m/server/store/TbLwM2mSecurityStore.java

@ -23,7 +23,6 @@ import org.jetbrains.annotations.Nullable;
import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator; import org.thingsboard.server.transport.lwm2m.secure.LwM2mCredentialsSecurityInfoValidator;
import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo; import org.thingsboard.server.transport.lwm2m.secure.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException; import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
@ -75,8 +74,8 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore {
try { try {
securityInfo = fetchAndPutSecurityInfo(pskIdentity); securityInfo = fetchAndPutSecurityInfo(pskIdentity);
} catch (LwM2MAuthException e) { } catch (LwM2MAuthException e) {
log.info("Registration failed: FORBIDDEN, endpointId: [{}]", pskIdentity); log.trace("Registration failed: No pre-shared key found for [identity: {}]", pskIdentity);
securityInfo = SecurityInfo.newPreSharedKeyInfo(pskIdentity, pskIdentity, new byte[]{0x00}); return null;
} }
} }
return securityInfo; return securityInfo;

3
dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java

@ -54,8 +54,7 @@ public interface AlarmDao extends Dao<Alarm> {
PageData<AlarmInfo> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); PageData<AlarmInfo> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status); Set<AlarmSeverity> findAlarmSeverities(TenantId tenantId, EntityId entityId, Set<AlarmStatus> status);

45
dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java

@ -59,6 +59,7 @@ import javax.annotation.PreDestroy;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
@ -139,11 +140,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
@Override @Override
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds) { AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId); validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); return alarmDao.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
return alarmDao.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds);
} }
@Override @Override
@ -171,25 +171,25 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException { private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException {
List<EntityId> propagatedEntitiesList; Set<EntityId> propagatedEntitiesSet = new LinkedHashSet<>();
propagatedEntitiesSet.add(alarm.getOriginator());
if (alarm.isPropagate()) { if (alarm.isPropagate()) {
Set<EntityId> parentEntities = getParentEntities(alarm); propagatedEntitiesSet.addAll(getRelatedEntities(alarm));
propagatedEntitiesList = new ArrayList<>(parentEntities.size() + 1); }
for (EntityId parentId : parentEntities) { if (alarm.isPropagateToOwner()) {
propagatedEntitiesList.add(parentId); propagatedEntitiesSet.add(alarm.getCustomerId() != null ? alarm.getCustomerId() : alarm.getTenantId());
createEntityAlarmRecord(alarm.getTenantId(), parentId, alarm);
}
propagatedEntitiesList.add(alarm.getOriginator());
} else {
propagatedEntitiesList = Collections.singletonList(alarm.getOriginator());
} }
createEntityAlarmRecord(alarm.getTenantId(), alarm.getOriginator(), alarm); if (alarm.isPropagateToTenant()) {
return propagatedEntitiesList; propagatedEntitiesSet.add(alarm.getTenantId());
}
for (EntityId entityId : propagatedEntitiesSet) {
createEntityAlarmRecord(alarm.getTenantId(), entityId, alarm);
}
return new ArrayList<>(propagatedEntitiesSet);
} }
private Set<EntityId> getParentEntities(Alarm alarm) throws InterruptedException, ExecutionException { private Set<EntityId> getRelatedEntities(Alarm alarm) throws InterruptedException, ExecutionException {
EntityRelationsQuery query = new EntityRelationsQuery(); EntityRelationsQuery query = new EntityRelationsQuery();
//TODO 3.1: @dlandiak we need to fetch max 3 levels and then fetch more if needed and there is at least one non-duplicate.
RelationsSearchParameters parameters = new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE, false); RelationsSearchParameters parameters = new RelationsSearchParameters(alarm.getOriginator(), EntitySearchDirection.TO, Integer.MAX_VALUE, false);
query.setParameters(parameters); query.setParameters(parameters);
List<String> propagateRelationTypes = alarm.getPropagateRelationTypes(); List<String> propagateRelationTypes = alarm.getPropagateRelationTypes();
@ -216,11 +216,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
private AlarmOperationResult updateAlarm(Alarm oldAlarm, Alarm newAlarm) { private AlarmOperationResult updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
boolean oldPropagate = oldAlarm.isPropagate(); boolean propagationEnabled = !oldAlarm.isPropagate() && newAlarm.isPropagate();
boolean newPropagate = newAlarm.isPropagate(); boolean propagationToOwnerEnabled = !oldAlarm.isPropagateToOwner() && newAlarm.isPropagateToOwner();
boolean propagationToTenantEnabled = !oldAlarm.isPropagateToTenant() && newAlarm.isPropagateToTenant();
Alarm result = alarmDao.save(newAlarm.getTenantId(), merge(oldAlarm, newAlarm)); Alarm result = alarmDao.save(newAlarm.getTenantId(), merge(oldAlarm, newAlarm));
List<EntityId> propagatedEntitiesList; List<EntityId> propagatedEntitiesList;
if (!oldPropagate && newPropagate) { if (propagationEnabled || propagationToOwnerEnabled || propagationToTenantEnabled) {
try { try {
propagatedEntitiesList = createEntityAlarmRecords(result); propagatedEntitiesList = createEntityAlarmRecords(result);
} catch (InterruptedException | ExecutionException e) { } catch (InterruptedException | ExecutionException e) {
@ -372,6 +373,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
existing.setDetails(alarm.getDetails()); existing.setDetails(alarm.getDetails());
existing.setCustomerId(alarm.getCustomerId()); existing.setCustomerId(alarm.getCustomerId());
existing.setPropagate(existing.isPropagate() || alarm.isPropagate()); existing.setPropagate(existing.isPropagate() || alarm.isPropagate());
existing.setPropagateToOwner(existing.isPropagateToOwner() || alarm.isPropagateToOwner());
existing.setPropagateToTenant(existing.isPropagateToTenant() || alarm.isPropagateToTenant());
List<String> existingPropagateRelationTypes = existing.getPropagateRelationTypes(); List<String> existingPropagateRelationTypes = existing.getPropagateRelationTypes();
List<String> newRelationTypes = alarm.getPropagateRelationTypes(); List<String> newRelationTypes = alarm.getPropagateRelationTypes();
if (!CollectionUtils.isEmpty(newRelationTypes)) { if (!CollectionUtils.isEmpty(newRelationTypes)) {
@ -387,7 +390,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
} }
private Set<EntityId> getPropagationEntityIds(Alarm alarm) { private Set<EntityId> getPropagationEntityIds(Alarm alarm) {
if (alarm.isPropagate()) { if (alarm.isPropagate() || alarm.isPropagateToOwner() || alarm.isPropagateToTenant()) {
List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(alarm.getTenantId(), alarm.getId()); List<EntityAlarm> entityAlarms = alarmDao.findEntityAlarmRecords(alarm.getTenantId(), alarm.getId());
return entityAlarms.stream().map(EntityAlarm::getEntityId).collect(Collectors.toSet()); return entityAlarms.stream().map(EntityAlarm::getEntityId).collect(Collectors.toSet());
} else { } else {

2
dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java

@ -273,6 +273,8 @@ public class ModelConstants {
public static final String ALARM_ACK_TS_PROPERTY = "ack_ts"; public static final String ALARM_ACK_TS_PROPERTY = "ack_ts";
public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts"; public static final String ALARM_CLEAR_TS_PROPERTY = "clear_ts";
public static final String ALARM_PROPAGATE_PROPERTY = "propagate"; public static final String ALARM_PROPAGATE_PROPERTY = "propagate";
public static final String ALARM_PROPAGATE_TO_OWNER_PROPERTY = "propagate_to_owner";
public static final String ALARM_PROPAGATE_TO_TENANT_PROPERTY = "propagate_to_tenant";
public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types"; public static final String ALARM_PROPAGATE_RELATION_TYPES = "propagate_relation_types";
public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id"; public static final String ALARM_BY_ID_VIEW_NAME = "alarm_by_id";

14
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java

@ -51,6 +51,8 @@ import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_I
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_RELATION_TYPES; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_RELATION_TYPES;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_TO_OWNER_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY; import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY;
@ -105,6 +107,12 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
@Column(name = ALARM_PROPAGATE_PROPERTY) @Column(name = ALARM_PROPAGATE_PROPERTY)
private Boolean propagate; private Boolean propagate;
@Column(name = ALARM_PROPAGATE_TO_OWNER_PROPERTY)
private Boolean propagateToOwner;
@Column(name = ALARM_PROPAGATE_TO_TENANT_PROPERTY)
private Boolean propagateToTenant;
@Column(name = ALARM_PROPAGATE_RELATION_TYPES) @Column(name = ALARM_PROPAGATE_RELATION_TYPES)
private String propagateRelationTypes; private String propagateRelationTypes;
@ -130,6 +138,8 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.severity = alarm.getSeverity(); this.severity = alarm.getSeverity();
this.status = alarm.getStatus(); this.status = alarm.getStatus();
this.propagate = alarm.isPropagate(); this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant();
this.startTs = alarm.getStartTs(); this.startTs = alarm.getStartTs();
this.endTs = alarm.getEndTs(); this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs(); this.ackTs = alarm.getAckTs();
@ -154,6 +164,8 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.severity = alarmEntity.getSeverity(); this.severity = alarmEntity.getSeverity();
this.status = alarmEntity.getStatus(); this.status = alarmEntity.getStatus();
this.propagate = alarmEntity.getPropagate(); this.propagate = alarmEntity.getPropagate();
this.propagateToOwner = alarmEntity.getPropagateToOwner();
this.propagateToTenant = alarmEntity.getPropagateToTenant();
this.startTs = alarmEntity.getStartTs(); this.startTs = alarmEntity.getStartTs();
this.endTs = alarmEntity.getEndTs(); this.endTs = alarmEntity.getEndTs();
this.ackTs = alarmEntity.getAckTs(); this.ackTs = alarmEntity.getAckTs();
@ -176,6 +188,8 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
alarm.setSeverity(severity); alarm.setSeverity(severity);
alarm.setStatus(status); alarm.setStatus(status);
alarm.setPropagate(propagate); alarm.setPropagate(propagate);
alarm.setPropagateToOwner(propagateToOwner);
alarm.setPropagateToTenant(propagateToTenant);
alarm.setStartTs(startTs); alarm.setStartTs(startTs);
alarm.setEndTs(endTs); alarm.setEndTs(endTs);
alarm.setAckTs(ackTs); alarm.setAckTs(ackTs);

4
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java

@ -157,8 +157,8 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
} }
@Override @Override
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) { public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return alarmQueryRepository.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds); return alarmQueryRepository.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
} }
@Override @Override

2
dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmDataAdapter.java

@ -83,6 +83,8 @@ public class AlarmDataAdapter {
UUID originatorId = (UUID) row.get(ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY); UUID originatorId = (UUID) row.get(ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY);
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId)); alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId));
alarm.setPropagate((boolean) row.get(ModelConstants.ALARM_PROPAGATE_PROPERTY)); alarm.setPropagate((boolean) row.get(ModelConstants.ALARM_PROPAGATE_PROPERTY));
alarm.setPropagateToOwner((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_OWNER_PROPERTY));
alarm.setPropagateToTenant((boolean) row.get(ModelConstants.ALARM_PROPAGATE_TO_TENANT_PROPERTY));
alarm.setType(row.get(ModelConstants.ALARM_TYPE_PROPERTY).toString()); alarm.setType(row.get(ModelConstants.ALARM_TYPE_PROPERTY).toString());
alarm.setSeverity(AlarmSeverity.valueOf(row.get(ModelConstants.ALARM_SEVERITY_PROPERTY).toString())); alarm.setSeverity(AlarmSeverity.valueOf(row.get(ModelConstants.ALARM_SEVERITY_PROPERTY).toString()));
alarm.setStatus(AlarmStatus.valueOf(row.get(ModelConstants.ALARM_STATUS_PROPERTY).toString())); alarm.setStatus(AlarmStatus.valueOf(row.get(ModelConstants.ALARM_STATUS_PROPERTY).toString()));

2
dao/src/main/java/org/thingsboard/server/dao/sql/query/AlarmQueryRepository.java

@ -27,7 +27,7 @@ import java.util.Collection;
public interface AlarmQueryRepository { public interface AlarmQueryRepository {
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds); AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
} }

32
dao/src/main/java/org/thingsboard/server/dao/sql/query/DefaultAlarmQueryRepository.java

@ -97,6 +97,8 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
" a.originator_id as originator_id," + " a.originator_id as originator_id," +
" a.originator_type as originator_type," + " a.originator_type as originator_type," +
" a.propagate as propagate," + " a.propagate as propagate," +
" a.propagate_to_owner as propagate_to_owner," +
" a.propagate_to_tenant as propagate_to_tenant," +
" a.severity as severity," + " a.severity as severity," +
" a.start_ts as start_ts," + " a.start_ts as start_ts," +
" a.status as status, " + " a.status as status, " +
@ -119,11 +121,10 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
} }
@Override @Override
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return transactionTemplate.execute(status -> { return transactionTemplate.execute(status -> {
AlarmDataPageLink pageLink = query.getPageLink(); AlarmDataPageLink pageLink = query.getPageLink();
QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, customerId, EntityType.ALARM)); QueryContext ctx = new QueryContext(new QuerySecurityContext(tenantId, null, EntityType.ALARM));
ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList())); ctx.addUuidListParameter("entity_ids", orderedEntityIds.stream().map(EntityId::getId).collect(Collectors.toList()));
StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION); StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION);
StringBuilder fromPart = new StringBuilder(" from alarm a "); StringBuilder fromPart = new StringBuilder(" from alarm a ");
@ -134,7 +135,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
if (pageLink.isSearchPropagatedAlarms()) { if (pageLink.isSearchPropagatedAlarms()) {
selectPart.append(" ea.entity_id as entity_id "); selectPart.append(" ea.entity_id as entity_id ");
fromPart.append(JOIN_ENTITY_ALARMS); fromPart.append(JOIN_ENTITY_ALARMS);
wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx)); wherePart.append(buildPermissionsQuery(tenantId, ctx));
addAnd = true; addAnd = true;
} else { } else {
selectPart.append(" a.originator_id as entity_id "); selectPart.append(" a.originator_id as entity_id ");
@ -285,31 +286,10 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
} }
} }
private String buildPermissionsQuery(TenantId tenantId, CustomerId customerId, QueryContext ctx) { private String buildPermissionsQuery(TenantId tenantId, QueryContext ctx) {
StringBuilder permissionsQuery = new StringBuilder(); StringBuilder permissionsQuery = new StringBuilder();
ctx.addUuidParameter("permissions_tenant_id", tenantId.getId()); ctx.addUuidParameter("permissions_tenant_id", tenantId.getId());
permissionsQuery.append(" a.tenant_id = :permissions_tenant_id and ea.tenant_id = :permissions_tenant_id "); permissionsQuery.append(" a.tenant_id = :permissions_tenant_id and ea.tenant_id = :permissions_tenant_id ");
/*
No need to check the customer id, because we already use entity id list that passed security check when we were evaluating the data query.
*/
// if (customerId != null && !customerId.isNullUid()) {
// ctx.addUuidParameter("permissions_customer_id", customerId.getId());
// ctx.addUuidParameter("permissions_device_customer_id", customerId.getId());
// ctx.addUuidParameter("permissions_asset_customer_id", customerId.getId());
// ctx.addUuidParameter("permissions_user_customer_id", customerId.getId());
// ctx.addUuidParameter("permissions_entity_view_customer_id", customerId.getId());
// permissionsQuery.append(" and (");
// permissionsQuery.append("(a.originator_type = '").append(EntityType.DEVICE.ordinal()).append("' and exists (select 1 from device cd where cd.id = a.originator_id and cd.customer_id = :permissions_device_customer_id))");
// permissionsQuery.append(" or ");
// permissionsQuery.append("(a.originator_type = '").append(EntityType.ASSET.ordinal()).append("' and exists (select 1 from asset ca where ca.id = a.originator_id and ca.customer_id = :permissions_device_customer_id))");
// permissionsQuery.append(" or ");
// permissionsQuery.append("(a.originator_type = '").append(EntityType.CUSTOMER.ordinal()).append("' and exists (select 1 from customer cc where cc.id = a.originator_id and cc.id = :permissions_customer_id))");
// permissionsQuery.append(" or ");
// permissionsQuery.append("(a.originator_type = '").append(EntityType.USER.ordinal()).append("' and exists (select 1 from tb_user cu where cu.id = a.originator_id and cu.customer_id = :permissions_user_customer_id))");
// permissionsQuery.append(" or ");
// permissionsQuery.append("(a.originator_type = '").append(EntityType.ENTITY_VIEW.ordinal()).append("' and exists (select 1 from entity_view cv where cv.id = a.originator_id and cv.customer_id = :permissions_entity_view_customer_id))");
// permissionsQuery.append(")");
// }
return permissionsQuery.toString(); return permissionsQuery.toString();
} }

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

@ -1,599 +0,0 @@
--
-- Copyright © 2016-2022 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
SET DATABASE SQL SYNTAX PGS TRUE;
SET DATABASE TRANSACTION CONTROL MVCC;
CREATE TABLE IF NOT EXISTS admin_settings (
id uuid NOT NULL CONSTRAINT admin_settings_pkey PRIMARY KEY,
created_time bigint NOT NULL,
json_value varchar,
key varchar(255)
);
CREATE TABLE IF NOT EXISTS alarm (
id uuid NOT NULL CONSTRAINT alarm_pkey PRIMARY KEY,
created_time bigint NOT NULL,
ack_ts bigint,
clear_ts bigint,
additional_info varchar,
end_ts bigint,
originator_id uuid,
originator_type integer,
propagate boolean,
severity varchar(255),
start_ts bigint,
status varchar(255),
tenant_id uuid,
customer_id uuid,
propagate_relation_types varchar,
type varchar(255)
);
CREATE TABLE IF NOT EXISTS entity_alarm (
tenant_id uuid NOT NULL,
entity_type varchar(32),
entity_id uuid NOT NULL,
created_time bigint NOT NULL,
alarm_type varchar(255) NOT NULL,
customer_id uuid,
alarm_id uuid,
CONSTRAINT entity_alarm_pkey PRIMARY KEY(entity_id, alarm_id),
CONSTRAINT fk_entity_alarm_id FOREIGN KEY (alarm_id) REFERENCES alarm(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS asset (
id uuid NOT NULL CONSTRAINT asset_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
customer_id uuid,
name varchar(255),
label varchar(255),
search_text varchar(255),
tenant_id uuid,
type varchar(255),
CONSTRAINT asset_name_unq_key UNIQUE (tenant_id, name)
);
CREATE TABLE IF NOT EXISTS audit_log (
id uuid NOT NULL CONSTRAINT audit_log_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid,
customer_id uuid,
entity_id uuid,
entity_type varchar(255),
entity_name varchar(255),
user_id uuid,
user_name varchar(255),
action_type varchar(255),
action_data varchar(1000000),
action_status varchar(255),
action_failure_details varchar(1000000)
);
CREATE TABLE IF NOT EXISTS attribute_kv (
entity_type varchar(255),
entity_id uuid,
attribute_type varchar(255),
attribute_key varchar(255),
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v varchar(10000000),
last_update_ts bigint,
CONSTRAINT attribute_kv_pkey PRIMARY KEY (entity_type, entity_id, attribute_type, attribute_key)
);
CREATE TABLE IF NOT EXISTS component_descriptor (
id uuid NOT NULL CONSTRAINT component_descriptor_pkey PRIMARY KEY,
created_time bigint NOT NULL,
actions varchar(255),
clazz varchar UNIQUE,
configuration_descriptor varchar,
name varchar(255),
scope varchar(255),
search_text varchar(255),
type varchar(255)
);
CREATE TABLE IF NOT EXISTS customer (
id uuid NOT NULL CONSTRAINT customer_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
address varchar,
address2 varchar,
city varchar(255),
country varchar(255),
email varchar(255),
phone varchar(255),
search_text varchar(255),
state varchar(255),
tenant_id uuid,
title varchar(255),
zip varchar(255)
);
CREATE TABLE IF NOT EXISTS dashboard (
id uuid NOT NULL CONSTRAINT dashboard_pkey PRIMARY KEY,
created_time bigint NOT NULL,
configuration varchar(10000000),
assigned_customers varchar(1000000),
search_text varchar(255),
tenant_id uuid,
title varchar(255),
mobile_hide boolean DEFAULT false,
mobile_order int,
image varchar(1000000)
);
CREATE TABLE IF NOT EXISTS rule_chain (
id uuid NOT NULL CONSTRAINT rule_chain_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
configuration varchar(10000000),
name varchar(255),
type varchar(255),
first_rule_node_id uuid,
root boolean,
debug_mode boolean,
search_text varchar(255),
tenant_id uuid
);
CREATE TABLE IF NOT EXISTS rule_node (
id uuid NOT NULL CONSTRAINT rule_node_pkey PRIMARY KEY,
created_time bigint NOT NULL,
rule_chain_id uuid,
additional_info varchar,
configuration varchar(10000000),
type varchar(255),
name varchar(255),
debug_mode boolean,
search_text varchar(255)
);
CREATE TABLE IF NOT EXISTS rule_node_state (
id uuid NOT NULL CONSTRAINT rule_node_state_pkey PRIMARY KEY,
created_time bigint NOT NULL,
rule_node_id uuid NOT NULL,
entity_type varchar(32) NOT NULL,
entity_id uuid NOT NULL,
state_data varchar(16384) NOT NULL,
CONSTRAINT rule_node_state_unq_key UNIQUE (rule_node_id, entity_id),
CONSTRAINT fk_rule_node_state_node_id FOREIGN KEY (rule_node_id) REFERENCES rule_node(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS ota_package (
id uuid NOT NULL CONSTRAINT ota_package_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
device_profile_id uuid ,
type varchar(32) NOT NULL,
title varchar(255) NOT NULL,
version varchar(255) NOT NULL,
tag varchar(255),
url varchar(255),
file_name varchar(255),
content_type varchar(255),
checksum_algorithm varchar(32),
checksum varchar(1020),
data binary,
data_size bigint,
additional_info varchar,
search_text varchar(255),
CONSTRAINT ota_package_tenant_title_version_unq_key UNIQUE (tenant_id, title, version)
);
CREATE TABLE IF NOT EXISTS device_profile (
id uuid NOT NULL CONSTRAINT device_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
name varchar(255),
type varchar(255),
image varchar(1000000),
transport_type varchar(255),
provision_type varchar(255),
profile_data jsonb,
description varchar,
search_text varchar(255),
is_default boolean,
tenant_id uuid,
firmware_id uuid,
software_id uuid,
default_rule_chain_id uuid,
default_dashboard_id uuid,
default_queue_name varchar(255),
provision_device_key varchar,
CONSTRAINT device_profile_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT device_provision_key_unq_key UNIQUE (provision_device_key),
CONSTRAINT fk_default_rule_chain_device_profile FOREIGN KEY (default_rule_chain_id) REFERENCES rule_chain(id),
CONSTRAINT fk_default_dashboard_device_profile FOREIGN KEY (default_dashboard_id) REFERENCES dashboard(id),
CONSTRAINT fk_firmware_device_profile FOREIGN KEY (firmware_id) REFERENCES ota_package(id),
CONSTRAINT fk_software_device_profile FOREIGN KEY (software_id) REFERENCES ota_package(id)
);
CREATE TABLE IF NOT EXISTS device (
id uuid NOT NULL CONSTRAINT device_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
customer_id uuid,
device_profile_id uuid NOT NULL,
device_data jsonb,
type varchar(255),
name varchar(255),
label varchar(255),
search_text varchar(255),
tenant_id uuid,
firmware_id uuid,
software_id uuid,
CONSTRAINT device_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT fk_device_profile FOREIGN KEY (device_profile_id) REFERENCES device_profile(id),
CONSTRAINT fk_firmware_device FOREIGN KEY (firmware_id) REFERENCES ota_package(id),
CONSTRAINT fk_software_device FOREIGN KEY (software_id) REFERENCES ota_package(id)
);
CREATE TABLE IF NOT EXISTS device_credentials (
id uuid NOT NULL CONSTRAINT device_credentials_pkey PRIMARY KEY,
created_time bigint NOT NULL,
credentials_id varchar,
credentials_type varchar(255),
credentials_value varchar,
device_id uuid,
CONSTRAINT device_credentials_id_unq_key UNIQUE (credentials_id),
CONSTRAINT device_credentials_device_id_unq_key UNIQUE (device_id)
);
CREATE TABLE IF NOT EXISTS event (
id uuid NOT NULL CONSTRAINT event_pkey PRIMARY KEY,
created_time bigint NOT NULL,
body varchar(10000000),
entity_id uuid,
entity_type varchar(255),
event_type varchar(255),
event_uid varchar(255),
tenant_id uuid,
ts bigint NOT NULL,
CONSTRAINT event_unq_key UNIQUE (tenant_id, entity_type, entity_id, event_type, event_uid)
);
CREATE TABLE IF NOT EXISTS relation (
from_id uuid,
from_type varchar(255),
to_id uuid,
to_type varchar(255),
relation_type_group varchar(255),
relation_type varchar(255),
additional_info varchar,
CONSTRAINT relation_pkey PRIMARY KEY (from_id, from_type, relation_type_group, relation_type, to_id, to_type)
);
CREATE TABLE IF NOT EXISTS tb_user (
id uuid NOT NULL CONSTRAINT tb_user_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
authority varchar(255),
customer_id uuid,
email varchar(255) UNIQUE,
first_name varchar(255),
last_name varchar(255),
search_text varchar(255),
tenant_id uuid
);
CREATE TABLE IF NOT EXISTS tenant_profile (
id uuid NOT NULL CONSTRAINT tenant_profile_pkey PRIMARY KEY,
created_time bigint NOT NULL,
name varchar(255),
profile_data jsonb,
description varchar,
search_text varchar(255),
is_default boolean,
isolated_tb_core boolean,
isolated_tb_rule_engine boolean,
CONSTRAINT tenant_profile_name_unq_key UNIQUE (name)
);
CREATE TABLE IF NOT EXISTS tenant (
id uuid NOT NULL CONSTRAINT tenant_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
tenant_profile_id uuid NOT NULL,
address varchar,
address2 varchar,
city varchar(255),
country varchar(255),
email varchar(255),
phone varchar(255),
region varchar(255),
search_text varchar(255),
state varchar(255),
title varchar(255),
zip varchar(255),
CONSTRAINT fk_tenant_profile FOREIGN KEY (tenant_profile_id) REFERENCES tenant_profile(id)
);
CREATE TABLE IF NOT EXISTS user_credentials (
id uuid NOT NULL CONSTRAINT user_credentials_pkey PRIMARY KEY,
created_time bigint NOT NULL,
activate_token varchar(255) UNIQUE,
enabled boolean,
password varchar(255),
reset_token varchar(255) UNIQUE,
user_id uuid UNIQUE
);
CREATE TABLE IF NOT EXISTS widget_type (
id uuid NOT NULL CONSTRAINT widget_type_pkey PRIMARY KEY,
created_time bigint NOT NULL,
alias varchar(255),
bundle_alias varchar(255),
descriptor varchar(1000000),
name varchar(255),
tenant_id uuid,
image varchar(1000000),
description varchar(255)
);
CREATE TABLE IF NOT EXISTS widgets_bundle (
id uuid NOT NULL CONSTRAINT widgets_bundle_pkey PRIMARY KEY,
created_time bigint NOT NULL,
alias varchar(255),
search_text varchar(255),
tenant_id uuid,
title varchar(255),
image varchar(1000000),
description varchar(255)
);
CREATE TABLE IF NOT EXISTS entity_view (
id uuid NOT NULL CONSTRAINT entity_view_pkey PRIMARY KEY,
created_time bigint NOT NULL,
entity_id uuid,
entity_type varchar(255),
tenant_id uuid,
customer_id uuid,
type varchar(255),
name varchar(255),
keys varchar(10000000),
start_ts bigint,
end_ts bigint,
search_text varchar(255),
additional_info varchar
);
CREATE TABLE IF NOT EXISTS ts_kv_latest (
entity_id uuid NOT NULL,
key int NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v varchar(10000000),
CONSTRAINT ts_kv_latest_pkey PRIMARY KEY (entity_id, key)
);
CREATE TABLE IF NOT EXISTS ts_kv_dictionary (
key varchar(255) NOT NULL,
key_id int GENERATED BY DEFAULT AS IDENTITY(start with 0 increment by 1) UNIQUE,
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);
CREATE TABLE IF NOT EXISTS oauth2_params (
id uuid NOT NULL CONSTRAINT oauth2_params_pkey PRIMARY KEY,
enabled boolean,
tenant_id uuid,
created_time bigint NOT NULL
);
CREATE TABLE IF NOT EXISTS oauth2_registration (
id uuid NOT NULL CONSTRAINT oauth2_registration_pkey PRIMARY KEY,
oauth2_params_id uuid NOT NULL,
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(2048),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
platforms varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
login_button_label varchar(255),
login_button_icon varchar(255),
allow_user_creation boolean,
activate_user boolean,
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
custom_url varchar(255),
custom_username varchar(255),
custom_password varchar(255),
custom_send_token boolean,
CONSTRAINT fk_registration_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS oauth2_domain (
id uuid NOT NULL CONSTRAINT oauth2_domain_pkey PRIMARY KEY,
oauth2_params_id uuid NOT NULL,
created_time bigint NOT NULL,
domain_name varchar(255),
domain_scheme varchar(31),
CONSTRAINT fk_domain_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
CONSTRAINT oauth2_domain_unq_key UNIQUE (oauth2_params_id, domain_name, domain_scheme)
);
CREATE TABLE IF NOT EXISTS oauth2_mobile (
id uuid NOT NULL CONSTRAINT oauth2_mobile_pkey PRIMARY KEY,
oauth2_params_id uuid NOT NULL,
created_time bigint NOT NULL,
pkg_name varchar(255),
app_secret varchar(2048),
CONSTRAINT fk_mobile_oauth2_params FOREIGN KEY (oauth2_params_id) REFERENCES oauth2_params(id) ON DELETE CASCADE,
CONSTRAINT oauth2_mobile_unq_key UNIQUE (oauth2_params_id, pkg_name)
);
CREATE TABLE IF NOT EXISTS oauth2_client_registration_template (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_template_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
provider_id varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
comment varchar,
login_button_icon varchar(255),
login_button_label varchar(255),
help_link varchar(255),
CONSTRAINT oauth2_template_provider_id_unq_key UNIQUE (provider_id)
);
-- Deprecated
CREATE TABLE IF NOT EXISTS oauth2_client_registration_info (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_info_pkey PRIMARY KEY,
enabled boolean,
created_time bigint NOT NULL,
additional_info varchar,
client_id varchar(255),
client_secret varchar(255),
authorization_uri varchar(255),
token_uri varchar(255),
scope varchar(255),
user_info_uri varchar(255),
user_name_attribute_name varchar(255),
jwk_set_uri varchar(255),
client_authentication_method varchar(255),
login_button_label varchar(255),
login_button_icon varchar(255),
allow_user_creation boolean,
activate_user boolean,
type varchar(31),
basic_email_attribute_key varchar(31),
basic_first_name_attribute_key varchar(31),
basic_last_name_attribute_key varchar(31),
basic_tenant_name_strategy varchar(31),
basic_tenant_name_pattern varchar(255),
basic_customer_name_pattern varchar(255),
basic_default_dashboard_name varchar(255),
basic_always_full_screen boolean,
custom_url varchar(255),
custom_username varchar(255),
custom_password varchar(255),
custom_send_token boolean
);
-- Deprecated
CREATE TABLE IF NOT EXISTS oauth2_client_registration (
id uuid NOT NULL CONSTRAINT oauth2_client_registration_pkey PRIMARY KEY,
created_time bigint NOT NULL,
domain_name varchar(255),
domain_scheme varchar(31),
client_registration_info_id uuid
);
CREATE TABLE IF NOT EXISTS api_usage_state (
id uuid NOT NULL CONSTRAINT usage_record_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid,
entity_type varchar(32),
entity_id uuid,
transport varchar(32),
db_storage varchar(32),
re_exec varchar(32),
js_exec varchar(32),
email_exec varchar(32),
sms_exec varchar(32),
alarm_exec varchar(32),
CONSTRAINT api_usage_state_unq_key UNIQUE (tenant_id, entity_id)
);
CREATE TABLE IF NOT EXISTS resource (
id uuid NOT NULL CONSTRAINT resource_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
title varchar(255) NOT NULL,
resource_type varchar(32) NOT NULL,
resource_key varchar(255) NOT NULL,
search_text varchar(255),
file_name varchar(255) NOT NULL,
data varchar,
CONSTRAINT resource_unq_key UNIQUE (tenant_id, resource_type, resource_key)
);
CREATE TABLE IF NOT EXISTS edge (
id uuid NOT NULL CONSTRAINT edge_pkey PRIMARY KEY,
created_time bigint NOT NULL,
additional_info varchar,
customer_id uuid,
root_rule_chain_id uuid,
type varchar(255),
name varchar(255),
label varchar(255),
routing_key varchar(255),
secret varchar(255),
edge_license_key varchar(30),
cloud_endpoint varchar(255),
search_text varchar(255),
tenant_id uuid,
CONSTRAINT edge_name_unq_key UNIQUE (tenant_id, name),
CONSTRAINT edge_routing_key_unq_key UNIQUE (routing_key)
);
CREATE TABLE IF NOT EXISTS edge_event (
id uuid NOT NULL CONSTRAINT edge_event_pkey PRIMARY KEY,
created_time bigint NOT NULL,
edge_id uuid,
edge_event_type varchar(255),
edge_event_uid varchar(255),
entity_id uuid,
edge_event_action varchar(255),
body varchar(10000000),
tenant_id uuid,
ts bigint NOT NULL
);
CREATE TABLE IF NOT EXISTS rpc (
id uuid NOT NULL CONSTRAINT rpc_pkey PRIMARY KEY,
created_time bigint NOT NULL,
tenant_id uuid NOT NULL,
device_id uuid NOT NULL,
expiration_time bigint NOT NULL,
request varchar(10000000) NOT NULL,
response varchar(10000000),
additional_info varchar(10000000),
status varchar(255) NOT NULL
);

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

@ -55,7 +55,9 @@ CREATE TABLE IF NOT EXISTS alarm (
tenant_id uuid, tenant_id uuid,
customer_id uuid, customer_id uuid,
propagate_relation_types varchar, propagate_relation_types varchar,
type varchar(255) type varchar(255),
propagate_to_owner boolean,
propagate_to_tenant boolean
); );
CREATE TABLE IF NOT EXISTS entity_alarm ( CREATE TABLE IF NOT EXISTS entity_alarm (

41
dao/src/main/resources/sql/schema-ts-hsql.sql

@ -1,41 +0,0 @@
--
-- Copyright © 2016-2022 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
SET DATABASE SQL SYNTAX PGS TRUE;
SET DATABASE TRANSACTION CONTROL MVCC;
CREATE TABLE IF NOT EXISTS ts_kv (
entity_id uuid NOT NULL,
key int NOT NULL,
ts bigint NOT NULL,
bool_v boolean,
str_v varchar(10000000),
long_v bigint,
dbl_v double precision,
json_v varchar(10000000),
CONSTRAINT ts_kv_pkey PRIMARY KEY (entity_id, key, ts)
);
CREATE TABLE IF NOT EXISTS ts_kv_dictionary (
key varchar(255) NOT NULL,
key_id int GENERATED BY DEFAULT AS IDENTITY(start with 0 increment by 1) UNIQUE,
CONSTRAINT ts_key_id_pkey PRIMARY KEY (key)
);
CREATE FUNCTION to_uuid(IN entity_id varchar)
RETURNS UUID
RETURN UUID(substring(entity_id, 8, 8) || '-' || substring(entity_id, 4, 4) || '-1' || substring(entity_id, 1, 3) ||
'-' || substring(entity_id, 16, 4) || '-' || substring(entity_id, 20, 12));

20
dao/src/main/resources/sql/schema-types-hsql.sql

@ -1,20 +0,0 @@
--
-- Copyright © 2016-2022 The Thingsboard Authors
--
-- Licensed under the Apache License, Version 2.0 (the "License");
-- you may not use this file except in compliance with the License.
-- You may obtain a copy of the License at
--
-- http://www.apache.org/licenses/LICENSE-2.0
--
-- Unless required by applicable law or agreed to in writing, software
-- distributed under the License is distributed on an "AS IS" BASIS,
-- WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-- See the License for the specific language governing permissions and
-- limitations under the License.
--
DROP TYPE json IF EXISTS;
CREATE TYPE json AS varchar;
DROP TYPE jsonb IF EXISTS;
CREATE TYPE jsonb AS other;

78
dao/src/test/java/org/thingsboard/server/dao/service/BaseAlarmServiceTest.java

@ -264,10 +264,10 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
PageData<AlarmData> tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId())); PageData<AlarmData> tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Arrays.asList(tenantDevice.getId(), customerDevice.getId()));
Assert.assertEquals(2, tenantAlarms.getData().size()); Assert.assertEquals(2, tenantAlarms.getData().size());
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerDevice.getId())); PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerDevice.getId()));
Assert.assertEquals(1, customerAlarms.getData().size()); Assert.assertEquals(1, customerAlarms.getData().size());
Assert.assertEquals(deviceAlarm, customerAlarms.getData().get(0)); Assert.assertEquals(deviceAlarm, customerAlarms.getData().get(0));
@ -283,7 +283,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
} }
@Test @Test
public void testFindPopagatedCustomerAlarm() throws ExecutionException, InterruptedException { public void testFindPropagatedCustomerAssetAlarm() throws ExecutionException, InterruptedException {
Customer customer = new Customer(); Customer customer = new Customer();
customer.setTitle("TestCustomer"); customer.setTitle("TestCustomer");
customer.setTenantId(tenantId); customer.setTenantId(tenantId);
@ -341,7 +341,63 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
//TEST that propagated alarms are visible on the asset level. //TEST that propagated alarms are visible on the asset level.
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, customer.getId(), toQuery(pageLink), Collections.singletonList(customerAsset.getId())); PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customerAsset.getId()));
Assert.assertEquals(1, customerAlarms.getData().size());
Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0));
}
@Test
public void testFindPropagatedToOwnerAndTenantAlarm() {
Customer customer = new Customer();
customer.setTitle("TestCustomer");
customer.setTenantId(tenantId);
customer = customerService.saveCustomer(customer);
Device device = new Device();
device.setName("TestTenantDevice");
device.setType("default");
device.setTenantId(tenantId);
device.setCustomerId(customer.getId());
device = deviceService.saveDevice(device);
long ts = System.currentTimeMillis();
Alarm tenantAlarm = Alarm.builder().tenantId(tenantId)
.originator(device.getId())
.type("Propagated To Tenant")
.propagateToTenant(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
AlarmOperationResult result = alarmService.createOrUpdateAlarm(tenantAlarm);
tenantAlarm = result.getAlarm();
Alarm customerAlarm = Alarm.builder().tenantId(tenantId)
.originator(device.getId())
.type("Propagated to Customer")
.propagate(false)
.propagateToOwner(true)
.severity(AlarmSeverity.CRITICAL).status(AlarmStatus.ACTIVE_UNACK)
.startTs(ts).build();
result = alarmService.createOrUpdateAlarm(customerAlarm);
customerAlarm = result.getAlarm();
AlarmDataPageLink pageLink = new AlarmDataPageLink();
pageLink.setPage(0);
pageLink.setPageSize(10);
pageLink.setSortOrder(new EntityDataSortOrder(new EntityKey(EntityKeyType.ALARM_FIELD, "createdTime")));
pageLink.setStartTs(0L);
pageLink.setEndTs(System.currentTimeMillis());
pageLink.setSearchPropagatedAlarms(true);
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
//TEST that propagated alarms are visible on the asset level.
PageData<AlarmData> tenantAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(tenantId));
Assert.assertEquals(1, tenantAlarms.getData().size());
Assert.assertEquals(tenantAlarm, tenantAlarms.getData().get(0));
//TEST that propagated alarms are visible on the asset level.
PageData<AlarmData> customerAlarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(customer.getId()));
Assert.assertEquals(1, customerAlarms.getData().size()); Assert.assertEquals(1, customerAlarms.getData().size());
Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0)); Assert.assertEquals(customerAlarm, customerAlarms.getData().get(0));
} }
@ -444,7 +500,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Collections.singletonList(childId)); PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
@ -460,13 +516,13 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Collections.singletonList(childId)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0))); Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
pageLink.setSearchPropagatedAlarms(true); pageLink.setSearchPropagatedAlarms(true);
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Collections.singletonList(childId)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0))); Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
@ -487,7 +543,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Collections.singletonList(childId)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0)); Assert.assertEquals(created, alarms.getData().get(0));
@ -503,7 +559,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Collections.singletonList(parentId)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0)); Assert.assertEquals(created, alarms.getData().get(0));
@ -548,7 +604,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Collections.singletonList(parentId)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(parentId));
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0)); Assert.assertEquals(created, alarms.getData().get(0));
@ -566,7 +622,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING)); pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE)); pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, new CustomerId(CustomerId.NULL_UUID), toQuery(pageLink), Collections.singletonList(childId)); alarms = alarmService.findAlarmDataByQueryForEntities(tenantId, toQuery(pageLink), Collections.singletonList(childId));
Assert.assertNotNull(alarms.getData()); Assert.assertNotNull(alarms.getData());
Assert.assertEquals(1, alarms.getData().size()); Assert.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0)); Assert.assertEquals(created, alarms.getData().get(0));

2
pom.xml

@ -88,7 +88,7 @@
<springfox-swagger.version>3.0.4</springfox-swagger.version> <springfox-swagger.version>3.0.4</springfox-swagger.version>
<swagger-annotations.version>1.6.3</swagger-annotations.version> <swagger-annotations.version>1.6.3</swagger-annotations.version>
<spatial4j.version>0.7</spatial4j.version> <spatial4j.version>0.7</spatial4j.version>
<jts.version>1.15.0</jts.version> <jts.version>1.18.2</jts.version>
<bouncycastle.version>1.67</bouncycastle.version> <bouncycastle.version>1.67</bouncycastle.version>
<winsw.version>2.0.1</winsw.version> <winsw.version>2.0.1</winsw.version>
<postgresql.driver.version>42.2.20</postgresql.driver.version> <postgresql.driver.version>42.2.20</postgresql.driver.version>

2
rule-engine/rule-engine-api/src/main/java/org/thingsboard/rule/engine/api/RuleEngineAlarmService.java

@ -61,5 +61,5 @@ public interface RuleEngineAlarmService {
AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus); AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds); PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
} }

10
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNode.java

@ -145,10 +145,14 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
if (msgAlarm != null) { if (msgAlarm != null) {
existingAlarm.setSeverity(msgAlarm.getSeverity()); existingAlarm.setSeverity(msgAlarm.getSeverity());
existingAlarm.setPropagate(msgAlarm.isPropagate()); existingAlarm.setPropagate(msgAlarm.isPropagate());
existingAlarm.setPropagateToOwner(msgAlarm.isPropagateToOwner());
existingAlarm.setPropagateToTenant(msgAlarm.isPropagateToTenant());
existingAlarm.setPropagateRelationTypes(msgAlarm.getPropagateRelationTypes()); existingAlarm.setPropagateRelationTypes(msgAlarm.getPropagateRelationTypes());
} else { } else {
existingAlarm.setSeverity(processAlarmSeverity(msg)); existingAlarm.setSeverity(processAlarmSeverity(msg));
existingAlarm.setPropagate(config.isPropagate()); existingAlarm.setPropagate(config.isPropagate());
existingAlarm.setPropagateToOwner(config.isPropagateToOwner());
existingAlarm.setPropagateToTenant(config.isPropagateToTenant());
existingAlarm.setPropagateRelationTypes(relationTypes); existingAlarm.setPropagateRelationTypes(relationTypes);
} }
existingAlarm.setDetails(details); existingAlarm.setDetails(details);
@ -160,6 +164,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
} }
private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) { private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) {
long ts = msg.getMetaDataTs();
return Alarm.builder() return Alarm.builder()
.tenantId(tenantId) .tenantId(tenantId)
.originator(msg.getOriginator()) .originator(msg.getOriginator())
@ -168,9 +173,8 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
.propagate(config.isPropagate()) .propagate(config.isPropagate())
.type(TbNodeUtils.processPattern(this.config.getAlarmType(), msg)) .type(TbNodeUtils.processPattern(this.config.getAlarmType(), msg))
.propagateRelationTypes(relationTypes) .propagateRelationTypes(relationTypes)
//todo-vp: alarm date should be taken from Message or current Time should be used? .startTs(ts)
// .startTs(System.currentTimeMillis()) .endTs(ts)
// .endTs(System.currentTimeMillis())
.details(details) .details(details)
.build(); .build();
} }

4
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/action/TbCreateAlarmNodeConfiguration.java

@ -27,6 +27,8 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
private String severity; private String severity;
private boolean propagate; private boolean propagate;
private boolean propagateToOwner;
private boolean propagateToTenant;
private boolean useMessageAlarmData; private boolean useMessageAlarmData;
private boolean dynamicSeverity; private boolean dynamicSeverity;
@ -39,6 +41,8 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
configuration.setAlarmType("General Alarm"); configuration.setAlarmType("General Alarm");
configuration.setSeverity(AlarmSeverity.CRITICAL.name()); configuration.setSeverity(AlarmSeverity.CRITICAL.name());
configuration.setPropagate(false); configuration.setPropagate(false);
configuration.setPropagateToOwner(false);
configuration.setPropagateToTenant(false);
configuration.setUseMessageAlarmData(false); configuration.setUseMessageAlarmData(false);
configuration.setRelationTypes(Collections.emptyList()); configuration.setRelationTypes(Collections.emptyList());
configuration.setDynamicSeverity(false); configuration.setDynamicSeverity(false);

172
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/geo/GeoUtil.java

@ -18,20 +18,35 @@ package org.thingsboard.rule.engine.geo;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import com.google.gson.JsonElement; import com.google.gson.JsonElement;
import com.google.gson.JsonParser; import com.google.gson.JsonParser;
import lombok.NonNull;
import org.locationtech.jts.geom.Coordinate;
import org.locationtech.jts.geom.CoordinateSequence;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.util.GeometryFixer;
import org.locationtech.spatial4j.context.SpatialContext; import org.locationtech.spatial4j.context.SpatialContext;
import org.locationtech.spatial4j.context.jts.JtsSpatialContext; import org.locationtech.spatial4j.context.jts.JtsSpatialContext;
import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory; import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils; import org.locationtech.spatial4j.distance.DistanceUtils;
import org.locationtech.spatial4j.shape.Point; import org.locationtech.spatial4j.shape.Point;
import org.locationtech.spatial4j.shape.Shape;
import org.locationtech.spatial4j.shape.ShapeFactory;
import org.locationtech.spatial4j.shape.SpatialRelation; import org.locationtech.spatial4j.shape.SpatialRelation;
import org.locationtech.spatial4j.shape.jts.JtsGeometry;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.stream.Collectors;
public class GeoUtil { public class GeoUtil {
private static final SpatialContext distCtx = SpatialContext.GEO; private static final SpatialContext distCtx = SpatialContext.GEO;
private static final JtsSpatialContext jtsCtx; private static final JtsSpatialContext jtsCtx;
private static final JsonParser JSON_PARSER = new JsonParser();
static { static {
JtsSpatialContextFactory factory = new JtsSpatialContextFactory(); JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
factory.normWrapLongitude = true; factory.normWrapLongitude = true;
@ -44,43 +59,148 @@ public class GeoUtil {
return unit.fromKm(distCtx.getDistCalc().distance(xLL, yLL) * DistanceUtils.DEG_TO_KM); return unit.fromKm(distCtx.getDistCalc().distance(xLL, yLL) * DistanceUtils.DEG_TO_KM);
} }
public static synchronized boolean contains(String polygon, Coordinates coordinates) { public static synchronized boolean contains(@NonNull String polygonInString, @NonNull Coordinates coordinates) {
JsonArray polygonArray = new JsonParser().parse(polygon).getAsJsonArray(); if (polygonInString.isEmpty() || polygonInString.isBlank()) {
throw new RuntimeException("Polygon string can't be empty or null!");
}
JsonArray polygonsJson = normalizePolygonsJson(JSON_PARSER.parse(polygonInString).getAsJsonArray());
List<Geometry> polygons = buildPolygonsFromJson(polygonsJson);
Set<Geometry> holes = extractHolesFrom(polygons);
polygons.removeIf(holes::contains);
Geometry globalGeometry = unionToGlobalGeometry(polygons, holes);
var point = jtsCtx.getShapeFactory().getGeometryFactory()
.createPoint(new Coordinate(coordinates.getLatitude(), coordinates.getLongitude()));
JsonArray arrayWithCoords = polygonArray; return globalGeometry.contains(point);
JsonArray innerArray = polygonArray.get(0).getAsJsonArray(); }
if (!containsPrimitives(innerArray)) {
arrayWithCoords = innerArray; private static Geometry unionToGlobalGeometry(List<Geometry> polygons, Set<Geometry> holes) {
Geometry globalPolygon = polygons.stream().reduce(Geometry::union).orElseThrow(() ->
new RuntimeException("Error while calculating globalPolygon - the result of all polygons union is null"));
Optional<Geometry> globalHole = holes.stream().reduce(Geometry::union);
if (globalHole.isEmpty()) {
return globalPolygon;
} else {
return globalPolygon.difference(globalHole.get());
} }
}
Shape shape = buildPolygonFromJsonCoords(arrayWithCoords); private static JsonArray normalizePolygonsJson(JsonArray polygonsJsonArray) {
Point point = jtsCtx.getShapeFactory().pointXY(coordinates.getLongitude(), coordinates.getLatitude()); JsonArray result = new JsonArray();
return shape.relate(point).equals(SpatialRelation.CONTAINS); normalizePolygonsJson(polygonsJsonArray, result);
return result;
}
private static void normalizePolygonsJson(JsonArray polygonsJsonArray, JsonArray result) {
if (containsArrayWithPrimitives(polygonsJsonArray)) {
result.add(polygonsJsonArray);
} else {
for (JsonElement element : polygonsJsonArray) {
if (containsArrayWithPrimitives(element.getAsJsonArray())) {
result.add(element);
} else {
normalizePolygonsJson(element.getAsJsonArray(), result);
}
}
}
} }
private static Shape buildPolygonFromJsonCoords(JsonArray coordinates) { private static Set<Geometry> extractHolesFrom(List<Geometry> polygons) {
ShapeFactory.PolygonBuilder polygonBuilder = jtsCtx.getShapeFactory().polygon(); Map<Geometry, List<Geometry>> polygonsHoles = new HashMap<>();
boolean isFirst = true;
double firstLat = 0.0; for (Geometry polygon : polygons) {
double firstLng = 0.0; List<Geometry> holes = polygons.stream()
for (JsonElement element : coordinates) { .filter(another -> !another.equalsExact(polygon))
double lat = element.getAsJsonArray().get(0).getAsDouble(); .filter(another -> {
double lng = element.getAsJsonArray().get(1).getAsDouble(); JtsGeometry currentGeo = jtsCtx.getShapeFactory().makeShape(polygon);
if (isFirst) { JtsGeometry anotherGeo = jtsCtx.getShapeFactory().makeShape(another);
firstLat = lat;
firstLng = lng; boolean currentContainsAnother = currentGeo
isFirst = false; .relate(anotherGeo)
.equals(SpatialRelation.CONTAINS);
boolean anotherWithinCurrent = anotherGeo
.relate(currentGeo)
.equals(SpatialRelation.WITHIN);
return currentContainsAnother && anotherWithinCurrent;
})
.collect(Collectors.toList());
if (!holes.isEmpty()) {
polygonsHoles.put(polygon, holes);
} }
polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(lng), jtsCtx.getShapeFactory().normX(lat));
} }
polygonBuilder.pointXY(jtsCtx.getShapeFactory().normX(firstLng), jtsCtx.getShapeFactory().normX(firstLat));
return polygonBuilder.buildOrRect(); return polygonsHoles.values().stream().flatMap(Collection::stream).collect(Collectors.toSet());
}
private static List<Geometry> buildPolygonsFromJson(JsonArray polygonsJsonArray) {
List<Geometry> polygons = new LinkedList<>();
for (JsonElement polygonJsonArray : polygonsJsonArray) {
polygons.add(
buildPolygonFromCoordinates(parseCoordinates(polygonJsonArray.getAsJsonArray()))
);
}
return polygons;
}
private static Geometry buildPolygonFromCoordinates(List<Coordinate> coordinates) {
if (coordinates.size() == 2) {
Coordinate a = coordinates.get(0);
Coordinate c = coordinates.get(1);
coordinates.clear();
Coordinate b = new Coordinate(a.x, c.y);
Coordinate d = new Coordinate(c.x, a.y);
coordinates.addAll(List.of(a, b, c, d, a));
}
CoordinateSequence coordinateSequence = jtsCtx
.getShapeFactory()
.getGeometryFactory()
.getCoordinateSequenceFactory()
.create(coordinates.toArray(new Coordinate[0]));
return GeometryFixer.fix(jtsCtx.getShapeFactory().getGeometryFactory().createPolygon(coordinateSequence));
}
private static List<Coordinate> parseCoordinates(JsonArray coordinatesJson) {
List<Coordinate> result = new LinkedList<>();
for (JsonElement coords : coordinatesJson) {
double x = coords.getAsJsonArray().get(0).getAsDouble();
double y = coords.getAsJsonArray().get(1).getAsDouble();
result.add(new Coordinate(x, y));
}
if (result.size() >= 3) {
result.add(result.get(0));
}
return result;
} }
private static boolean containsPrimitives(JsonArray array) { private static boolean containsPrimitives(JsonArray array) {
for (JsonElement element : array) { for (JsonElement element : array) {
return element.isJsonPrimitive(); return element.isJsonPrimitive();
} }
return false; return false;
} }
private static boolean containsArrayWithPrimitives(JsonArray array) {
for (JsonElement element : array) {
if (!containsPrimitives(element.getAsJsonArray())) {
return false;
}
}
return true;
}
} }

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/metadata/CalculateDeltaNode.java

@ -28,7 +28,6 @@ import org.thingsboard.rule.engine.api.TbNodeConfiguration;
import org.thingsboard.rule.engine.api.TbNodeException; import org.thingsboard.rule.engine.api.TbNodeException;
import org.thingsboard.rule.engine.api.TbRelationTypes; import org.thingsboard.rule.engine.api.TbRelationTypes;
import org.thingsboard.rule.engine.api.util.TbNodeUtils; import org.thingsboard.rule.engine.api.util.TbNodeUtils;
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
import org.thingsboard.server.common.data.id.EntityId; import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.kv.TsKvEntry; import org.thingsboard.server.common.data.kv.TsKvEntry;
import org.thingsboard.server.common.data.plugin.ComponentType; import org.thingsboard.server.common.data.plugin.ComponentType;
@ -82,7 +81,7 @@ public class CalculateDeltaNode implements TbNode {
DonAsynchron.withCallback(getLastValue(msg.getOriginator()), DonAsynchron.withCallback(getLastValue(msg.getOriginator()),
previousData -> { previousData -> {
double currentValue = json.get(inputKey).asDouble(); double currentValue = json.get(inputKey).asDouble();
long currentTs = TbMsgTimeseriesNode.getTs(msg); long currentTs = msg.getMetaDataTs();
if (useCache) { if (useCache) {
cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue)); cache.put(msg.getOriginator(), new ValueWithTs(currentTs, currentValue));

2
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/AlarmState.java

@ -258,6 +258,8 @@ class AlarmState {
currentAlarm.setOriginator(originator); currentAlarm.setOriginator(originator);
currentAlarm.setTenantId(ctx.getTenantId()); currentAlarm.setTenantId(ctx.getTenantId());
currentAlarm.setPropagate(alarmDefinition.isPropagate()); currentAlarm.setPropagate(alarmDefinition.isPropagate());
currentAlarm.setPropagateToOwner(alarmDefinition.isPropagateToOwner());
currentAlarm.setPropagateToTenant(alarmDefinition.isPropagateToTenant());
if (alarmDefinition.getPropagateRelationTypes() != null) { if (alarmDefinition.getPropagateRelationTypes() != null) {
currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes()); currentAlarm.setPropagateRelationTypes(alarmDefinition.getPropagateRelationTypes());
} }

3
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/profile/DeviceState.java

@ -21,7 +21,6 @@ import org.springframework.util.StringUtils;
import org.thingsboard.rule.engine.api.TbContext; import org.thingsboard.rule.engine.api.TbContext;
import org.thingsboard.rule.engine.profile.state.PersistedAlarmState; import org.thingsboard.rule.engine.profile.state.PersistedAlarmState;
import org.thingsboard.rule.engine.profile.state.PersistedDeviceState; import org.thingsboard.rule.engine.profile.state.PersistedDeviceState;
import org.thingsboard.rule.engine.telemetry.TbMsgTimeseriesNode;
import org.thingsboard.server.common.data.DataConstants; import org.thingsboard.server.common.data.DataConstants;
import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.DeviceProfile; import org.thingsboard.server.common.data.DeviceProfile;
@ -252,7 +251,7 @@ class DeviceState {
protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException { protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
boolean stateChanged = false; boolean stateChanged = false;
Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), TbMsgTimeseriesNode.getTs(msg)); Map<Long, List<KvEntry>> tsKvMap = JsonConverter.convertToSortedTelemetry(new JsonParser().parse(msg.getData()), msg.getMetaDataTs());
// iterate over data by ts (ASC order). // iterate over data by ts (ASC order).
for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) { for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) {
Long ts = entry.getKey(); Long ts = entry.getKey();

16
rule-engine/rule-engine-components/src/main/java/org/thingsboard/rule/engine/telemetry/TbMsgTimeseriesNode.java

@ -111,21 +111,7 @@ public class TbMsgTimeseriesNode implements TbNode {
} }
public static long computeTs(TbMsg msg, boolean ignoreMetadataTs) { public static long computeTs(TbMsg msg, boolean ignoreMetadataTs) {
return ignoreMetadataTs ? System.currentTimeMillis() : getTs(msg); return ignoreMetadataTs ? System.currentTimeMillis() : msg.getMetaDataTs();
}
public static long getTs(TbMsg msg) {
long ts = -1;
String tsStr = msg.getMetaData().getValue("ts");
if (!StringUtils.isEmpty(tsStr)) {
try {
ts = Long.parseLong(tsStr);
} catch (NumberFormatException ignored) {
}
} else {
ts = msg.getTs();
}
return ts;
} }
@Override @Override

2
rule-engine/rule-engine-components/src/main/resources/public/static/rulenode/rulenode-core-config.js

File diff suppressed because one or more lines are too long

163
rule-engine/rule-engine-components/src/test/java/org/thingsboard/rule/engine/geo/TbGeoUtilTest.java

@ -0,0 +1,163 @@
/**
* Copyright © 2016-2022 The Thingsboard Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.thingsboard.rule.engine.geo;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.junit.MockitoJUnitRunner;
@RunWith(MockitoJUnitRunner.class)
public class TbGeoUtilTest {
public static final String SIMPLE_RECT = "[[51.903762928405555,23.642220786948297],[44.669801219635644,41.83345155830211]]";
public static final String SIMPLE_RECT_WITH_HOLE_IN_CENTER = "[[[44.66980121963565,23.642220786948297],[44.66980121963565,41.83345155830211],[51.903762928405555,41.83345155830211],[51.903762928405555,23.642220786948297]],[[46.10464044504632,26.234282119122227],[50.8755868028522,26.25625220459488],[51.04164771375101,38.5595000692786],[45.99790855491869,38.75723083853248]]]";
public static final String SAND_CLOCK = "[[47.45865912532852,25.531200822337155],[49.76760268310416,29.353995694578202],[51.42691963936519,25.355440138555966],[51.413219087617655,39.41629484105169],[49.78179072754938,33.37452133607305],[47.81395478511215,38.867042704235466]]";
public static final String SAND_CLOCK_WITH_HOLE_IN_CENTER = "[[[51.426919639365195,25.355440138555966],[49.76760268310416,29.353995694578202],[47.45865912532852,25.531200822337155],[47.81395478511215,38.867042704235466],[49.78179072754938,33.37452133607305],[51.413219087617655,39.41629484105169]],[[49.8243299406579,30.210829028011513],[50.34591034217041,31.04569227597222],[49.56853374046142,32.53965808811239],[49.02409241675058,31.57297432731579]]]";
public static final String SELF_INTERSECTING = "[[47.42893833699058,27.178954662008522],[51.71367987390804,37.46095466320854],[51.659197648757306,27.947907653551276],[47.41407351681856,37.46095466320854]]";
public static final String SELF_INTERSECTING_WITH_HOLES = "[[[[47.42893833699058,27.17895466200852],[47.41407351681856,37.46095466320853],[49.63741575793274,32.47858934221699]],[[47.84342032696093,29.20045703244998],[49.124803688667576,32.38611942598416],[47.858163521459254,34.714948486085035]]],[[[51.659197648757306,27.947907653551276],[49.63741575793274,32.47858934221699],[51.71367987390804,37.46095466320853]],[[51.20718653775245,30.738363015535448],[51.317169097567344,34.58312797324915],[50.1351109330126,32.51793993882006]]]]";
public static final Coordinates POINT_INSIDE_SIMPLE_RECT_CENTER = new Coordinates(48.37082198780869, 32.673342414527355);
public static final Coordinates POINT_INSIDE_SIMPLE_RECT_NEAR_BORDER = new Coordinates(48.42916753187315,40.956064637716224);
public static final Coordinates POINT_OUTSIDE_SIMPLE_RECT = new Coordinates(52.94806646045028,32.91501335472649);
public static final Coordinates POINT_INSIDE_SAND_CLOCK_CENTER = new Coordinates(49.993588800145105,31.289062500000004);
public static final Coordinates POINT_INSIDE_SAND_CLOCK_NEAR_BORDER = new Coordinates(47.798651123976306,26.895045405470082);
public static final Coordinates POINT_OUTSIDE_SAND_CLOCK_1 = new Coordinates(49.553754212665936,28.03748985004787);
public static final Coordinates POINT_OUTSIDE_SAND_CLOCK_2 = new Coordinates(46.9802466961145,32.321728498980335);
public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_UPPER_CENTER = new Coordinates(50.750366308834884,32.51952867922265);
public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_LOWER_CENTER = new Coordinates(48.1371117277312,32.40967825185941);
public static final Coordinates POINT_INSIDE_SELF_INTERSECTING_NEAR_BORDER = new Coordinates(51.16552151942722,35.66125090181154);
public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_1= new Coordinates(49.66777277299077,33.26651158529272);
public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_2 = new Coordinates(47.10052840114779,32.16800731166027);
public static final Coordinates POINT_OUTSIDE_SELF_INTERSECTING_3 = new Coordinates(78.76578380252519,15.646485040786361);
@Test
public void testPointsInSimplePolygons() {
Assert.assertTrue("Polygon " + SIMPLE_RECT + " must contain the dot " + POINT_INSIDE_SIMPLE_RECT_CENTER,
GeoUtil.contains(SIMPLE_RECT, POINT_INSIDE_SIMPLE_RECT_CENTER)
);
Assert.assertTrue("Polygon " + SIMPLE_RECT + " must contain the dot " + POINT_INSIDE_SIMPLE_RECT_NEAR_BORDER,
GeoUtil.contains(SIMPLE_RECT, POINT_INSIDE_SIMPLE_RECT_NEAR_BORDER)
);
Assert.assertTrue("Polygon " + SIMPLE_RECT_WITH_HOLE_IN_CENTER + " must contain the dot "
+ POINT_INSIDE_SIMPLE_RECT_NEAR_BORDER,
GeoUtil.contains(SIMPLE_RECT_WITH_HOLE_IN_CENTER, POINT_INSIDE_SIMPLE_RECT_NEAR_BORDER)
);
Assert.assertFalse("Polygon " + SIMPLE_RECT + " must not contain the dot "
+ POINT_OUTSIDE_SIMPLE_RECT,
GeoUtil.contains(SIMPLE_RECT, POINT_OUTSIDE_SIMPLE_RECT)
);
Assert.assertFalse("Polygon " + SIMPLE_RECT_WITH_HOLE_IN_CENTER + " must not contain the dot "
+ POINT_OUTSIDE_SIMPLE_RECT,
GeoUtil.contains(SIMPLE_RECT_WITH_HOLE_IN_CENTER, POINT_OUTSIDE_SIMPLE_RECT)
);
Assert.assertFalse("Polygon " + SIMPLE_RECT_WITH_HOLE_IN_CENTER + " must not contain the dot "
+ POINT_INSIDE_SIMPLE_RECT_CENTER,
GeoUtil.contains(SIMPLE_RECT_WITH_HOLE_IN_CENTER, POINT_INSIDE_SIMPLE_RECT_CENTER)
);
}
@Test
public void testPointsInComplexPolygons() {
Assert.assertTrue("Polygon " + SAND_CLOCK + " must contain the dot " + POINT_INSIDE_SAND_CLOCK_CENTER,
GeoUtil.contains(SAND_CLOCK, POINT_INSIDE_SAND_CLOCK_CENTER)
);
Assert.assertTrue("Polygon " + SAND_CLOCK + " must contain the dot " + POINT_INSIDE_SAND_CLOCK_NEAR_BORDER,
GeoUtil.contains(SAND_CLOCK, POINT_INSIDE_SAND_CLOCK_NEAR_BORDER)
);
Assert.assertTrue("Polygon " + SAND_CLOCK_WITH_HOLE_IN_CENTER + " must contain the dot "
+ POINT_INSIDE_SAND_CLOCK_NEAR_BORDER,
GeoUtil.contains(SAND_CLOCK_WITH_HOLE_IN_CENTER, POINT_INSIDE_SAND_CLOCK_NEAR_BORDER)
);
Assert.assertFalse("Polygon " + SAND_CLOCK + " must not contain the dot "
+ POINT_OUTSIDE_SAND_CLOCK_1,
GeoUtil.contains(SAND_CLOCK, POINT_OUTSIDE_SAND_CLOCK_1)
);
Assert.assertFalse("Polygon " + SAND_CLOCK + " must not contain the dot "
+ POINT_OUTSIDE_SAND_CLOCK_2,
GeoUtil.contains(SAND_CLOCK, POINT_OUTSIDE_SAND_CLOCK_2)
);
Assert.assertFalse("Polygon " + SAND_CLOCK_WITH_HOLE_IN_CENTER + " must not contain the dot "
+ POINT_INSIDE_SAND_CLOCK_CENTER,
GeoUtil.contains(SAND_CLOCK_WITH_HOLE_IN_CENTER, POINT_INSIDE_SAND_CLOCK_CENTER)
);
Assert.assertFalse("Polygon " + SAND_CLOCK_WITH_HOLE_IN_CENTER + " must not contain the dot "
+ POINT_OUTSIDE_SAND_CLOCK_1,
GeoUtil.contains(SAND_CLOCK_WITH_HOLE_IN_CENTER, POINT_OUTSIDE_SAND_CLOCK_1)
);
Assert.assertFalse("Polygon " + SAND_CLOCK_WITH_HOLE_IN_CENTER + " must not contain the dot "
+ POINT_OUTSIDE_SAND_CLOCK_2,
GeoUtil.contains(SAND_CLOCK_WITH_HOLE_IN_CENTER, POINT_OUTSIDE_SAND_CLOCK_2)
);
}
@Test
public void testPointsInSelfIntersectingPolygons() {
Assert.assertTrue("Polygon " + SELF_INTERSECTING + " must contain the dot "
+ POINT_INSIDE_SELF_INTERSECTING_UPPER_CENTER,
GeoUtil.contains(SELF_INTERSECTING, POINT_INSIDE_SELF_INTERSECTING_UPPER_CENTER)
);
Assert.assertTrue("Polygon " + SELF_INTERSECTING + " must contain the dot "
+ POINT_INSIDE_SELF_INTERSECTING_LOWER_CENTER,
GeoUtil.contains(SELF_INTERSECTING, POINT_INSIDE_SELF_INTERSECTING_LOWER_CENTER)
);
Assert.assertTrue("Polygon " + SELF_INTERSECTING + " must contain the dot "
+ POINT_INSIDE_SELF_INTERSECTING_NEAR_BORDER,
GeoUtil.contains(SELF_INTERSECTING, POINT_INSIDE_SELF_INTERSECTING_NEAR_BORDER)
);
Assert.assertTrue("Polygon " + SELF_INTERSECTING_WITH_HOLES + " must contain the dot "
+ POINT_INSIDE_SAND_CLOCK_NEAR_BORDER,
GeoUtil.contains(SELF_INTERSECTING_WITH_HOLES, POINT_INSIDE_SELF_INTERSECTING_NEAR_BORDER)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING + " must not contain the dot "
+ POINT_OUTSIDE_SELF_INTERSECTING_1,
GeoUtil.contains(SELF_INTERSECTING, POINT_OUTSIDE_SELF_INTERSECTING_1)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING + " must not contain the dot "
+ POINT_OUTSIDE_SELF_INTERSECTING_2,
GeoUtil.contains(SELF_INTERSECTING, POINT_OUTSIDE_SELF_INTERSECTING_2)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING + " must not contain the dot "
+ POINT_OUTSIDE_SELF_INTERSECTING_3,
GeoUtil.contains(SELF_INTERSECTING, POINT_OUTSIDE_SELF_INTERSECTING_3)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING_WITH_HOLES + " must not contain the dot "
+ POINT_OUTSIDE_SELF_INTERSECTING_1,
GeoUtil.contains(SELF_INTERSECTING_WITH_HOLES, POINT_OUTSIDE_SELF_INTERSECTING_1)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING_WITH_HOLES + " must not contain the dot "
+ POINT_OUTSIDE_SELF_INTERSECTING_2,
GeoUtil.contains(SELF_INTERSECTING_WITH_HOLES, POINT_OUTSIDE_SELF_INTERSECTING_2)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING_WITH_HOLES + " must not contain the dot "
+ POINT_OUTSIDE_SELF_INTERSECTING_3,
GeoUtil.contains(SELF_INTERSECTING_WITH_HOLES, POINT_OUTSIDE_SELF_INTERSECTING_3)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING_WITH_HOLES + " must not contain the dot "
+ POINT_INSIDE_SELF_INTERSECTING_UPPER_CENTER,
GeoUtil.contains(SELF_INTERSECTING_WITH_HOLES, POINT_INSIDE_SELF_INTERSECTING_UPPER_CENTER)
);
Assert.assertFalse("Polygon " + SELF_INTERSECTING_WITH_HOLES + " must not contain the dot "
+ POINT_INSIDE_SELF_INTERSECTING_LOWER_CENTER,
GeoUtil.contains(SELF_INTERSECTING_WITH_HOLES, POINT_INSIDE_SELF_INTERSECTING_LOWER_CENTER)
);
}
}

3
ui-ngx/angular.json

@ -89,8 +89,7 @@
"scripts": [ "scripts": [
"node_modules/tinycolor2/dist/tinycolor-min.js", "node_modules/tinycolor2/dist/tinycolor-min.js",
"node_modules/split.js/dist/split.min.js", "node_modules/split.js/dist/split.min.js",
"node_modules/systemjs/dist/system.js", "node_modules/systemjs/dist/system.min.js",
"node_modules/systemjs-babel/dist/systemjs-babel.js",
"node_modules/marked/lib/marked.js", "node_modules/marked/lib/marked.js",
"node_modules/prismjs/prism.js", "node_modules/prismjs/prism.js",
"node_modules/prismjs/components/prism-css.min.js", "node_modules/prismjs/components/prism-css.min.js",

1
ui-ngx/package.json

@ -89,7 +89,6 @@
"screenfull": "^6.0.0", "screenfull": "^6.0.0",
"split.js": "^1.6.4", "split.js": "^1.6.4",
"systemjs": "6.11.0", "systemjs": "6.11.0",
"systemjs-babel": "^0.3.1",
"tinycolor2": "^1.4.2", "tinycolor2": "^1.4.2",
"tooltipster": "^4.2.8", "tooltipster": "^4.2.8",
"tslib": "^2.3.1", "tslib": "^2.3.1",

2
ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts

@ -128,7 +128,7 @@ export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink>
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {
if (res) { if (res) {
this.table.updateData(); this.updateData();
} }
} }
); );

2
ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts

@ -43,6 +43,6 @@ export class AlarmTableHeaderComponent extends EntityTableHeaderComponent<AlarmI
searchStatusChanged(searchStatus: AlarmSearchStatus) { searchStatusChanged(searchStatus: AlarmSearchStatus) {
this.alarmTableConfig.searchStatus = searchStatus; this.alarmTableConfig.searchStatus = searchStatus;
this.alarmTableConfig.table.resetSortAndFilter(true, true); this.alarmTableConfig.getTable().resetSortAndFilter(true, true);
} }
} }

15
ui-ngx/src/app/modules/home/components/edge/edge-downlink-table-config.ts

@ -48,7 +48,7 @@ import { AttributeScope } from '@shared/models/telemetry/telemetry.models';
import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component'; import { EdgeDownlinkTableHeaderComponent } from '@home/components/edge/edge-downlink-table-header.component';
import { EdgeService } from '@core/http/edge.service'; import { EdgeService } from '@core/http/edge.service';
import { concatMap, map } from 'rxjs/operators'; import { concatMap, map } from 'rxjs/operators';
import { EntityService } from "@core/http/entity.service"; import { EntityService } from '@core/http/entity.service';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { ActionNotificationShow } from '@core/notification/notification.actions'; import { ActionNotificationShow } from '@core/notification/notification.actions';
@ -94,9 +94,9 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
private onUpdate(attributes: any): void { private onUpdate(attributes: any): void {
this.queueStartTs = 0; this.queueStartTs = 0;
let edge = attributes.reduce(function (map, attribute) { const edge = attributes.reduce((attrMap, attribute) => {
map[attribute.key] = attribute; attrMap[attribute.key] = attribute;
return map; return attrMap;
}, {}); }, {});
this.queueStartTs = edge.queueStartTs && edge.queueStartTs.value ? edge.queueStartTs.value : 0; this.queueStartTs = edge.queueStartTs && edge.queueStartTs.value ? edge.queueStartTs.value : 0;
} }
@ -114,7 +114,8 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
new EntityTableColumn<EdgeEvent>('status', 'event.status', '10%', new EntityTableColumn<EdgeEvent>('status', 'event.status', '10%',
(entity) => this.updateEdgeEventStatus(entity.createdTime), (entity) => this.updateEdgeEventStatus(entity.createdTime),
entity => ({ entity => ({
color: this.isPending(entity.createdTime) ? edgeEventStatusColor.get(EdgeEventStatus.PENDING) : edgeEventStatusColor.get(EdgeEventStatus.DEPLOYED) color: this.isPending(entity.createdTime) ? edgeEventStatusColor.get(EdgeEventStatus.PENDING) :
edgeEventStatusColor.get(EdgeEventStatus.DEPLOYED)
}), false), }), false),
new EntityActionTableColumn<EdgeEvent>('data', 'event.data', new EntityActionTableColumn<EdgeEvent>('data', 'event.data',
{ {
@ -124,7 +125,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
onAction: ($event, entity) => onAction: ($event, entity) =>
{ {
this.prepareEdgeEventContent(entity).subscribe( this.prepareEdgeEventContent(entity).subscribe(
(content) => this.showEdgeEventContent($event, content,'event.data'), (content) => this.showEdgeEventContent($event, content, 'event.data'),
() => this.showEntityNotFoundError() () => this.showEntityNotFoundError()
); );
} }
@ -132,7 +133,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
'40px'), '40px'),
); );
if (updateTableColumns) { if (updateTableColumns) {
this.table.columnsUpdated(true); this.getTable().columnsUpdated(true);
} }
} }

3
ui-ngx/src/app/modules/home/components/entity/entities-table.component.html

@ -23,8 +23,9 @@
position="end" position="end"
[opened]="isDetailsOpen"> [opened]="isDetailsOpen">
<tb-entity-details-panel <tb-entity-details-panel
#entityDetailsPanel
[entitiesTableConfig]="entitiesTableConfig" [entitiesTableConfig]="entitiesTableConfig"
[entityId]="dataSource.currentEntity?.id" [entityId]="isDetailsOpen ? dataSource.currentEntity?.id : null"
(closeEntityDetails)="isDetailsOpen = false; detailsPanelOpened.emit(isDetailsOpen);" (closeEntityDetails)="isDetailsOpen = false; detailsPanelOpened.emit(isDetailsOpen);"
(entityUpdated)="onEntityUpdated($event)" (entityUpdated)="onEntityUpdated($event)"
(entityAction)="onEntityAction($event)" (entityAction)="onEntityAction($event)"

8
ui-ngx/src/app/modules/home/components/entity/entities-table.component.ts

@ -64,6 +64,7 @@ import { HasUUID } from '@shared/models/id/has-uuid';
import { ResizeObserver } from '@juggle/resize-observer'; import { ResizeObserver } from '@juggle/resize-observer';
import { hidePageSizePixelValue } from '@shared/models/constants'; import { hidePageSizePixelValue } from '@shared/models/constants';
import { IEntitiesTableComponent } from '@home/models/entity/entity-table-component.models'; import { IEntitiesTableComponent } from '@home/models/entity/entity-table-component.models';
import { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component';
@Component({ @Component({
selector: 'tb-entities-table', selector: 'tb-entities-table',
@ -118,6 +119,8 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort; @ViewChild(MatSort) sort: MatSort;
@ViewChild('entityDetailsPanel') entityDetailsPanel: EntityDetailsPanelComponent;
private updateDataSubscription: Subscription; private updateDataSubscription: Subscription;
private viewInited = false; private viewInited = false;
@ -182,7 +185,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
headerComponent.entitiesTableConfig = this.entitiesTableConfig; headerComponent.entitiesTableConfig = this.entitiesTableConfig;
} }
this.entitiesTableConfig.table = this; this.entitiesTableConfig.setTable(this);
this.translations = this.entitiesTableConfig.entityTranslations; this.translations = this.entitiesTableConfig.entityTranslations;
this.headerActionDescriptors = [...this.entitiesTableConfig.headerActionDescriptors]; this.headerActionDescriptors = [...this.entitiesTableConfig.headerActionDescriptors];
@ -392,6 +395,9 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
} }
} }
this.dataSource.loadEntities(this.pageLink); this.dataSource.loadEntities(this.pageLink);
if (this.isDetailsOpen && this.entityDetailsPanel) {
this.entityDetailsPanel.reloadEntity();
}
} }
private dataLoaded(col?: number, row?: number) { private dataLoaded(col?: number, row?: number) {

61
ui-ngx/src/app/modules/home/components/entity/entity-details-page.component.ts

@ -32,10 +32,11 @@ import { ActivatedRoute, Router } from '@angular/router';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { AssetId } from '@shared/models/id/asset-id'; import { AssetId } from '@shared/models/id/asset-id';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { deepClone, mergeDeep } from '@core/utils'; import { deepClone } from '@core/utils';
import { BroadcastService } from '@core/services/broadcast.service'; import { BroadcastService } from '@core/services/broadcast.service';
import { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component'; import { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { IEntityDetailsPageComponent } from '@home/models/entity/entity-details-page-component.models';
@Component({ @Component({
selector: 'tb-entity-details-page', selector: 'tb-entity-details-page',
@ -43,7 +44,7 @@ import { DialogService } from '@core/services/dialog.service';
styleUrls: ['./entity-details-page.component.scss'], styleUrls: ['./entity-details-page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush changeDetection: ChangeDetectionStrategy.OnPush
}) })
export class EntityDetailsPageComponent extends EntityDetailsPanelComponent implements OnInit, OnDestroy { export class EntityDetailsPageComponent extends EntityDetailsPanelComponent implements IEntityDetailsPageComponent, OnInit, OnDestroy {
headerTitle: string; headerTitle: string;
headerSubtitle: string; headerSubtitle: string;
@ -54,6 +55,7 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
if (this.entitiesTableConfigValue !== entitiesTableConfig) { if (this.entitiesTableConfigValue !== entitiesTableConfig) {
this.entitiesTableConfigValue = entitiesTableConfig; this.entitiesTableConfigValue = entitiesTableConfig;
if (this.entitiesTableConfigValue) { if (this.entitiesTableConfigValue) {
this.entitiesTableConfigValue.setEntityDetailsPage(this);
this.isEdit = false; this.isEdit = false;
this.entity = null; this.entity = null;
} }
@ -81,9 +83,6 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
ngOnInit() { ngOnInit() {
this.headerSubtitle = ''; this.headerSubtitle = '';
this.route.paramMap.subscribe( paramMap => {
this.entityId = new AssetId(paramMap.get('entityId'));
});
this.headerSubtitle = this.translate.instant(this.entitiesTableConfig.entityTranslations.details); this.headerSubtitle = this.translate.instant(this.entitiesTableConfig.entityTranslations.details);
super.init(); super.init();
this.entityComponent.isDetailsPage = true; this.entityComponent.isDetailsPage = true;
@ -92,6 +91,15 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
this.deleteEntity(action.event, action.entity); this.deleteEntity(action.event, action.entity);
} }
})); }));
this.subscriptions.push(this.route.paramMap.subscribe( paramMap => {
if (this.entitiesTableConfig) {
const entityType = this.entitiesTableConfig.entityType;
const id = paramMap.get('entityId');
this.currentEntityId = { id, entityType };
this.reload();
this.selectedTab = 0;
}
}));
} }
ngOnDestroy() { ngOnDestroy() {
@ -99,20 +107,9 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
} }
reload(): void { reload(): void {
this.isEdit = false; this.reloadEntity().subscribe(() => {
this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe( this.onUpdateEntity();
(entity) => { });
this.entity = entity;
this.broadcast.broadcast('updateBreadcrumb');
this.isReadOnly = this.entitiesTableConfig.detailsReadonly(entity);
this.headerTitle = this.entitiesTableConfig.entityTitle(entity);
this.entityComponent.entity = entity;
this.entityComponent.isEdit = false;
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = entity;
}
}
);
} }
onToggleDetailsEditMode() { onToggleDetailsEditMode() {
@ -136,29 +133,23 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
} }
onApplyDetails() { onApplyDetails() {
if (this.detailsForm && this.detailsForm.valid) { this.saveEntity(false).subscribe((entity) => {
const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()}; if (entity) {
if (this.editingEntity.hasOwnProperty('additionalInfo')) { this.onUpdateEntity();
editingEntity.additionalInfo =
mergeDeep((this.editingEntity as any).additionalInfo, this.entityComponent.entityFormValue()?.additionalInfo);
} }
this.entitiesTableConfig.saveEntity(editingEntity, this.editingEntity).subscribe( });
(entity) => {
this.entity = entity;
this.entityComponent.entity = entity;
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = entity;
}
this.isEdit = false;
}
);
}
} }
confirmForm(): FormGroup { confirmForm(): FormGroup {
return this.detailsForm; return this.detailsForm;
} }
private onUpdateEntity() {
this.broadcast.broadcast('updateBreadcrumb');
this.isReadOnly = this.entitiesTableConfig.detailsReadonly(this.entity);
this.headerTitle = this.entitiesTableConfig.entityTitle(this.entity);
}
private deleteEntity($event: Event, entity: BaseData<HasId>) { private deleteEntity($event: Event, entity: BaseData<HasId>) {
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();

30
ui-ngx/src/app/modules/home/components/entity/entity-details-panel.component.ts

@ -34,16 +34,17 @@ import { PageComponent } from '@shared/components/page.component';
import { Store } from '@ngrx/store'; import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state'; import { AppState } from '@core/core.state';
import { EntityTableConfig } from '@home/models/entity/entities-table-config.models'; import { EntityTableConfig } from '@home/models/entity/entities-table-config.models';
import { BaseData, HasId } from '@shared/models/base-data'; import { BaseData, HasId, hasIdEquals } from '@shared/models/base-data';
import { EntityType, EntityTypeResource, EntityTypeTranslation } from '@shared/models/entity-type.models'; import { EntityType, EntityTypeResource, EntityTypeTranslation } from '@shared/models/entity-type.models';
import { FormGroup } from '@angular/forms'; import { FormGroup } from '@angular/forms';
import { EntityComponent } from './entity.component'; import { EntityComponent } from './entity.component';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component'; import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
import { EntityAction } from '@home/models/entity/entity-component.models'; import { EntityAction } from '@home/models/entity/entity-component.models';
import { Subscription } from 'rxjs'; import { Observable, ReplaySubject, Subscription } from 'rxjs';
import { MatTab, MatTabGroup } from '@angular/material/tabs'; import { MatTab, MatTabGroup } from '@angular/material/tabs';
import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component'; import { EntityTabsComponent } from '@home/components/entity/entity-tabs.component';
import { deepClone, mergeDeep } from '@core/utils'; import { deepClone, mergeDeep } from '@core/utils';
import { entityIdEquals } from '@shared/models/id/entity-id';
@Component({ @Component({
selector: 'tb-entity-details-panel', selector: 'tb-entity-details-panel',
@ -102,9 +103,11 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
@Input() @Input()
set entityId(entityId: HasId) { set entityId(entityId: HasId) {
if (entityId && entityId !== this.currentEntityId) { if (!hasIdEquals(entityId, this.currentEntityId)) {
this.currentEntityId = entityId; this.currentEntityId = entityId;
this.reload(); if (this.currentEntityId) {
this.reloadEntity();
}
} }
} }
@ -228,7 +231,8 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
return this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit; return this.isEditValue && this.entitiesTableConfig.hideDetailsTabsOnEdit;
} }
reload(): void { reloadEntity(): Observable<BaseData<HasId>> {
const loadEntitySubject = new ReplaySubject<BaseData<HasId>>();
this.isEdit = false; this.isEdit = false;
this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe( this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe(
(entity) => { (entity) => {
@ -237,8 +241,11 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
if (this.entityTabsComponent) { if (this.entityTabsComponent) {
this.entityTabsComponent.entity = entity; this.entityTabsComponent.entity = entity;
} }
loadEntitySubject.next(entity);
loadEntitySubject.complete();
} }
); );
return loadEntitySubject;
} }
onCloseEntityDetails() { onCloseEntityDetails() {
@ -273,7 +280,8 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
} }
} }
saveEntity() { saveEntity(emitEntityUpdated = true): Observable<BaseData<HasId>> {
const saveEntitySubject = new ReplaySubject<BaseData<HasId>>();
if (this.detailsForm.valid) { if (this.detailsForm.valid) {
const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()}; const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()};
if (this.editingEntity.hasOwnProperty('additionalInfo')) { if (this.editingEntity.hasOwnProperty('additionalInfo')) {
@ -288,10 +296,18 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
this.entityTabsComponent.entity = entity; this.entityTabsComponent.entity = entity;
} }
this.isEdit = false; this.isEdit = false;
this.entityUpdated.emit(this.entity); if (emitEntityUpdated) {
this.entityUpdated.emit(this.entity);
}
saveEntitySubject.next(entity);
saveEntitySubject.complete();
} }
); );
} else {
saveEntitySubject.next(null);
saveEntitySubject.complete();
} }
return saveEntitySubject;
} }
ngAfterViewInit(): void { ngAfterViewInit(): void {

38
ui-ngx/src/app/modules/home/components/event/event-table-config.ts

@ -52,6 +52,7 @@ import {
export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> { export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
eventTypeValue: EventType | DebugEventType; eventTypeValue: EventType | DebugEventType;
hideClearEventAction = false;
private filterParams: FilterEventBody = {}; private filterParams: FilterEventBody = {};
private filterColumns: FilterEntityColumn[] = []; private filterColumns: FilterEntityColumn[] = [];
@ -140,15 +141,30 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
{ {
name: this.translate.instant('event.clean-events'), name: this.translate.instant('event.clean-events'),
icon: 'delete', icon: 'delete',
isEnabled: () => true, isEnabled: () => !this.hideClearEventAction,
onAction: ($event) => { onAction: $event => this.clearEvents($event)
this.eventService.clearEvents(this.entityId, this.eventType, this.filterParams, this.tenantId, this.table.pageLink as TimePageLink).subscribe( });
}
clearEvents($event) {
if ($event) {
$event.stopPropagation();
}
this.dialogService.confirm(
this.translate.instant('event.clear-request-title'),
this.translate.instant('event.clear-request-text'),
this.translate.instant('action.no'),
this.translate.instant('action.yes')
).subscribe((res) => {
if (res) {
this.eventService.clearEvents(this.entityId, this.eventType, this.filterParams,
this.tenantId, this.getTable().pageLink as TimePageLink).subscribe(
() => { () => {
this.table.paginator.pageIndex = 0; this.getTable().paginator.pageIndex = 0;
this.table.updateData(); this.updateData();
} }
); );
} }
}); });
} }
@ -275,7 +291,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
break; break;
} }
if (updateTableColumns) { if (updateTableColumns) {
this.table.columnsUpdated(true); this.getTable().columnsUpdated(true);
} }
} }
@ -345,8 +361,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
} }
this.filterParams = {}; this.filterParams = {};
this.table.paginator.pageIndex = 0; this.getTable().paginator.pageIndex = 0;
this.table.updateData(); this.updateData();
} }
private editEventFilter($event: MouseEvent) { private editEventFilter($event: MouseEvent) {
@ -389,8 +405,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
componentRef.onDestroy(() => { componentRef.onDestroy(() => {
if (componentRef.instance.result && !isEqual(this.filterParams, componentRef.instance.result.filterParams)) { if (componentRef.instance.result && !isEqual(this.filterParams, componentRef.instance.result.filterParams)) {
this.filterParams = componentRef.instance.result.filterParams; this.filterParams = componentRef.instance.result.filterParams;
this.table.paginator.pageIndex = 0; this.getTable().paginator.pageIndex = 0;
this.table.updateData(); this.updateData();
} }
}); });
this.cd.detectChanges(); this.cd.detectChanges();

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

@ -40,6 +40,6 @@ export class EventTableHeaderComponent extends EntityTableHeaderComponent<Event>
eventTypeChanged(eventType: EventType | DebugEventType) { eventTypeChanged(eventType: EventType | DebugEventType) {
this.eventTableConfig.eventType = eventType; this.eventTableConfig.eventType = eventType;
this.eventTableConfig.table.resetSortAndFilter(true, true); this.eventTableConfig.getTable().resetSortAndFilter(true, true);
} }
} }

17
ui-ngx/src/app/modules/home/components/event/event-table.component.ts

@ -14,7 +14,7 @@
/// limitations under the License. /// limitations under the License.
/// ///
import { ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core'; import { AfterViewInit, ChangeDetectorRef, Component, Input, OnInit, ViewChild, ViewContainerRef } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common'; import { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material/dialog'; import { MatDialog } from '@angular/material/dialog';
@ -25,13 +25,14 @@ import { EventService } from '@core/http/event.service';
import { DialogService } from '@core/services/dialog.service'; import { DialogService } from '@core/services/dialog.service';
import { DebugEventType, EventType } from '@shared/models/event.models'; import { DebugEventType, EventType } from '@shared/models/event.models';
import { Overlay } from '@angular/cdk/overlay'; import { Overlay } from '@angular/cdk/overlay';
import { Subscription } from 'rxjs';
@Component({ @Component({
selector: 'tb-event-table', selector: 'tb-event-table',
templateUrl: './event-table.component.html', templateUrl: './event-table.component.html',
styleUrls: ['./event-table.component.scss'] styleUrls: ['./event-table.component.scss']
}) })
export class EventTableComponent implements OnInit { export class EventTableComponent implements OnInit, AfterViewInit {
@Input() @Input()
tenantId: string; tenantId: string;
@ -77,6 +78,8 @@ export class EventTableComponent implements OnInit {
eventTableConfig: EventTableConfig; eventTableConfig: EventTableConfig;
private isEmptyData$: Subscription;
constructor(private eventService: EventService, constructor(private eventService: EventService,
private dialogService: DialogService, private dialogService: DialogService,
private translate: TranslateService, private translate: TranslateService,
@ -106,4 +109,14 @@ export class EventTableComponent implements OnInit {
); );
} }
ngAfterViewInit() {
this.isEmptyData$ = this.entitiesTable.dataSource.isEmpty().subscribe(value => this.eventTableConfig.hideClearEventAction = value);
}
ngOnDestroy() {
if (this.isEmptyData$) {
this.isEmptyData$.unsubscribe();
}
}
} }

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

@ -15,8 +15,9 @@
limitations under the License. limitations under the License.
--> -->
<div fxLayout="row" fxLayoutAlign="start center" [formGroup]="alarmRuleConditionFormGroup" style="min-width: 0;"> <div fxLayout="row" fxLayoutAlign="start center" [formGroup]="alarmRuleConditionFormGroup" (click)="openFilterDialog($event)" style="min-width: 0;">
<div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)"> <span class="tb-alarm-rule-condition title">{{('device-profile.condition' | translate) + ': '}}</span>
<div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center">
<tb-filter-text formControlName="condition" <tb-filter-text formControlName="condition"
[nowrap]="true" [nowrap]="true"
required required

5
ui-ngx/src/app/modules/home/components/profile/alarm/alarm-rule-condition.component.scss

@ -27,6 +27,11 @@
opacity: 0.7; opacity: 0.7;
padding: 4px; padding: 4px;
} }
&.title {
padding: 4px;
opacity: 0.7;
overflow: visible;
}
} }
} }

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

@ -82,6 +82,12 @@
<mat-hint innerHTML="{{ 'device-profile.alarm-rule-relation-types-list-hint' | translate }}"></mat-hint> <mat-hint innerHTML="{{ 'device-profile.alarm-rule-relation-types-list-hint' | translate }}"></mat-hint>
</mat-form-field> </mat-form-field>
</section> </section>
<mat-checkbox formControlName="propagateToOwner" style="display: block; padding-bottom: 16px;">
{{ 'device-profile.propagate-alarm-to-owner' | translate }}
</mat-checkbox>
<mat-checkbox formControlName="propagateToTenant" style="display: block; padding-bottom: 16px;">
{{ 'device-profile.propagate-alarm-to-tenant' | translate }}
</mat-checkbox>
</ng-template> </ng-template>
</mat-expansion-panel> </mat-expansion-panel>
<div fxFlex fxLayout="column"> <div fxFlex fxLayout="column">

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

@ -97,7 +97,9 @@ export class DeviceProfileAlarmComponent implements ControlValueAccessor, OnInit
createRules: [null], createRules: [null],
clearRule: [null], clearRule: [null],
propagate: [null], propagate: [null],
propagateRelationTypes: [null] propagateRelationTypes: [null],
propagateToOwner: [null],
propagateToTenant: [null]
}, { validators: deviceProfileAlarmValidator }); }, { validators: deviceProfileAlarmValidator });
this.alarmFormGroup.valueChanges.subscribe(() => { this.alarmFormGroup.valueChanges.subscribe(() => {
this.updateModel(); this.updateModel();

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

@ -33,6 +33,7 @@
<mat-form-field class="mat-block"> <mat-form-field class="mat-block">
<mat-label translate>device-profile.alarm-details</mat-label> <mat-label translate>device-profile.alarm-details</mat-label>
<textarea matInput formControlName="alarmDetails" rows="5"></textarea> <textarea matInput formControlName="alarmDetails" rows="5"></textarea>
<mat-hint [innerHTML]="'device-profile.alarm-rule-details-hint' | translate | safe: 'html'"></mat-hint>
</mat-form-field> </mat-form-field>
</div> </div>
</fieldset> </fieldset>

2
ui-ngx/src/app/modules/home/components/widget/lib/date-range-navigator/date-range-navigator-panel.component.scss

@ -29,5 +29,7 @@
.mat-padding { .mat-padding {
padding: 16px; padding: 16px;
min-width: 560px;
} }
} }

5
ui-ngx/src/app/modules/home/components/widget/widget-config.component.ts

@ -396,14 +396,15 @@ export class WidgetConfigComponent extends PageComponent implements OnInit, Cont
const layout = this.modelValue.layout; const layout = this.modelValue.layout;
if (config) { if (config) {
this.selectedTab = 0; this.selectedTab = 0;
const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false;
this.widgetSettings.patchValue({ this.widgetSettings.patchValue({
title: config.title, title: config.title,
showTitleIcon: isDefined(config.showTitleIcon) ? config.showTitleIcon : false, showTitleIcon: isDefined(config.showTitleIcon) && displayWidgetTitle ? config.showTitleIcon : false,
titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '', titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '',
iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)', iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
iconSize: isDefined(config.iconSize) ? config.iconSize : '24px', iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '', titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
showTitle: isDefined(config.showTitle) ? config.showTitle : false, showTitle: displayWidgetTitle,
dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true, dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true, enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
backgroundColor: config.backgroundColor, backgroundColor: config.backgroundColor,

40
ui-ngx/src/app/modules/home/models/entity/entities-table-config.models.ts

@ -31,6 +31,8 @@ import { ActivatedRoute } from '@angular/router';
import { EntityTabsComponent } from '../../components/entity/entity-tabs.component'; import { EntityTabsComponent } from '../../components/entity/entity-tabs.component';
import { DAY, historyInterval } from '@shared/models/time/time.models'; import { DAY, historyInterval } from '@shared/models/time/time.models';
import { IEntitiesTableComponent } from '@home/models/entity/entity-table-component.models'; import { IEntitiesTableComponent } from '@home/models/entity/entity-table-component.models';
import { IEntityDetailsPageComponent } from '@home/models/entity/entity-details-page-component.models';
import { templateJitUrl } from '@angular/compiler';
export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean; export type EntityBooleanFunction<T extends BaseData<HasId>> = (entity: T) => boolean;
export type EntityStringFunction<T extends BaseData<HasId>> = (entity: T) => string; export type EntityStringFunction<T extends BaseData<HasId>> = (entity: T) => string;
@ -136,11 +138,13 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
constructor() {} constructor() {}
private table: IEntitiesTableComponent = null;
private entityDetailsPage: IEntityDetailsPageComponent = null;
componentsData: any = null; componentsData: any = null;
loadDataOnInit = true; loadDataOnInit = true;
onLoadAction: (route: ActivatedRoute) => void = null; onLoadAction: (route: ActivatedRoute) => void = null;
table: IEntitiesTableComponent = null;
useTimePageLink = false; useTimePageLink = false;
defaultTimewindowInterval = historyInterval(DAY); defaultTimewindowInterval = historyInterval(DAY);
entityType: EntityType = null; entityType: EntityType = null;
@ -189,6 +193,40 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
entityAdded: EntityVoidFunction<T> = () => {}; entityAdded: EntityVoidFunction<T> = () => {};
entityUpdated: EntityVoidFunction<T> = () => {}; entityUpdated: EntityVoidFunction<T> = () => {};
entitiesDeleted: EntityIdsVoidFunction<T> = () => {}; entitiesDeleted: EntityIdsVoidFunction<T> = () => {};
getTable(): IEntitiesTableComponent {
return this.table;
}
setTable(table: IEntitiesTableComponent) {
this.table = table;
this.entityDetailsPage = null;
}
getEntityDetailsPage(): IEntityDetailsPageComponent {
return this.entityDetailsPage;
}
setEntityDetailsPage(entityDetailsPage: IEntityDetailsPageComponent) {
this.entityDetailsPage = entityDetailsPage;
this.table = null;
}
updateData(closeDetails = false) {
if (this.table) {
this.table.updateData(closeDetails);
} else if (this.entityDetailsPage) {
this.entityDetailsPage.reload();
}
}
getActivatedRoute(): ActivatedRoute {
if (this.table) {
return this.table.route;
} else {
return null;
}
}
} }
export function checkBoxCell(value: boolean): string { export function checkBoxCell(value: boolean): string {

19
ui-ngx/src/app/modules/home/models/entity/entity-details-page-component.models.ts

@ -0,0 +1,19 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
export interface IEntityDetailsPageComponent {
reload(): void;
}

2
ui-ngx/src/app/modules/home/pages/asset/asset-table-header.component.ts

@ -36,7 +36,7 @@ export class AssetTableHeaderComponent extends EntityTableHeaderComponent<AssetI
assetTypeChanged(assetType: string) { assetTypeChanged(assetType: string) {
this.entitiesTableConfig.componentsData.assetType = assetType; this.entitiesTableConfig.componentsData.assetType = assetType;
this.entitiesTableConfig.table.resetSortAndFilter(true); this.entitiesTableConfig.getTable().resetSortAndFilter(true);
} }
} }

25
ui-ngx/src/app/modules/home/pages/asset/assets-table-config.resolver.ts

@ -104,7 +104,8 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
)); ));
}; };
this.config.onEntityAction = action => this.onAssetAction(action, this.config); this.config.onEntityAction = action => this.onAssetAction(action, this.config);
this.config.detailsReadonly = () => (this.config.componentsData.assetScope === 'customer_user' || this.config.componentsData.assetScope === 'edge_customer_user'); this.config.detailsReadonly = () => (this.config.componentsData.assetScope === 'customer_user' ||
this.config.componentsData.assetScope === 'edge_customer_user');
this.config.headerComponent = AssetTableHeaderComponent; this.config.headerComponent = AssetTableHeaderComponent;
@ -295,7 +296,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
name: this.translate.instant('asset.add-asset-text'), name: this.translate.instant('asset.add-asset-text'),
icon: 'insert_drive_file', icon: 'insert_drive_file',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event) onAction: ($event) => this.config.getTable().addEntity($event)
}, },
{ {
name: this.translate.instant('asset.import'), name: this.translate.instant('asset.import'),
@ -332,7 +333,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
this.homeDialogs.importEntities(EntityType.ASSET).subscribe((res) => { this.homeDialogs.importEntities(EntityType.ASSET).subscribe((res) => {
if (res) { if (res) {
this.broadcast.broadcast('assetSaved'); this.broadcast.broadcast('assetSaved');
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -341,7 +342,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const url = this.router.createUrlTree([asset.id.id], {relativeTo: config.table.route}); const url = this.router.createUrlTree([asset.id.id], {relativeTo: config.getActivatedRoute()});
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
} }
@ -360,7 +361,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -379,7 +380,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if (res) { if (res) {
this.assetService.makeAssetPublic(asset.id.id).subscribe( this.assetService.makeAssetPublic(asset.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -402,7 +403,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -431,7 +432,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if (res) { if (res) {
this.assetService.unassignAssetFromCustomer(asset.id.id).subscribe( this.assetService.unassignAssetFromCustomer(asset.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.assetScope !== 'tenant');
} }
); );
} }
@ -459,7 +460,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -503,7 +504,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -522,7 +523,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if (res) { if (res) {
this.assetService.unassignAssetFromEdge(this.config.componentsData.edgeId, asset.id.id).subscribe( this.assetService.unassignAssetFromEdge(this.config.componentsData.edgeId, asset.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.assetScope !== 'tenant');
} }
); );
} }
@ -550,7 +551,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }

2
ui-ngx/src/app/modules/home/pages/customer/customers-table-config.resolver.ts

@ -141,7 +141,7 @@ export class CustomersTableConfigResolver implements Resolve<EntityTableConfig<C
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const url = this.router.createUrlTree([customer.id.id], {relativeTo: config.table.route}); const url = this.router.createUrlTree([customer.id.id], {relativeTo: config.getActivatedRoute()});
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
} }

28
ui-ngx/src/app/modules/home/pages/dashboard/dashboards-table-config.resolver.ts

@ -103,7 +103,8 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
return this.dashboardService.saveDashboard(dashboard as Dashboard); return this.dashboardService.saveDashboard(dashboard as Dashboard);
}; };
this.config.onEntityAction = action => this.onDashboardAction(action); this.config.onEntityAction = action => this.onDashboardAction(action);
this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' || this.config.componentsData.dashboardScope === 'edge_customer_user'); this.config.detailsReadonly = () => (this.config.componentsData.dashboardScope === 'customer_user' ||
this.config.componentsData.dashboardScope === 'edge_customer_user');
} }
resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> { resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DashboardInfo | Dashboard>> {
@ -147,7 +148,8 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.dashboardScope); this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.dashboardScope);
this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.dashboardScope); this.config.groupActionDescriptors = this.configureGroupActions(this.config.componentsData.dashboardScope);
this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.dashboardScope); this.config.addActionDescriptors = this.configureAddActions(this.config.componentsData.dashboardScope);
this.config.addEnabled = !(this.config.componentsData.dashboardScope === 'customer_user' || this.config.componentsData.dashboardScope === 'edge_customer_user'); this.config.addEnabled = !(this.config.componentsData.dashboardScope === 'customer_user' ||
this.config.componentsData.dashboardScope === 'edge_customer_user');
this.config.entitiesDeleteEnabled = this.config.componentsData.dashboardScope === 'tenant'; this.config.entitiesDeleteEnabled = this.config.componentsData.dashboardScope === 'tenant';
this.config.deleteEnabled = () => this.config.componentsData.dashboardScope === 'tenant'; this.config.deleteEnabled = () => this.config.componentsData.dashboardScope === 'tenant';
return this.config; return this.config;
@ -322,7 +324,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
name: this.translate.instant('dashboard.create-new-dashboard'), name: this.translate.instant('dashboard.create-new-dashboard'),
icon: 'insert_drive_file', icon: 'insert_drive_file',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event) onAction: ($event) => this.config.getTable().addEntity($event)
}, },
{ {
name: this.translate.instant('dashboard.import'), name: this.translate.instant('dashboard.import'),
@ -372,7 +374,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.importExport.importDashboard().subscribe( this.importExport.importDashboard().subscribe(
(dashboard) => { (dashboard) => {
if (dashboard) { if (dashboard) {
this.config.table.updateData(); this.config.updateData();
} }
} }
); );
@ -400,7 +402,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -420,7 +422,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
} }
}).afterClosed() }).afterClosed()
.subscribe(() => { .subscribe(() => {
this.config.table.updateData(); this.config.updateData();
}); });
} }
); );
@ -440,7 +442,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) { if (res) {
this.dashboardService.makeDashboardPrivate(dashboard.id.id).subscribe( this.dashboardService.makeDashboardPrivate(dashboard.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -480,7 +482,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -499,7 +501,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) { if (res) {
this.dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).subscribe( this.dashboardService.unassignDashboardFromCustomer(customerId, dashboard.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.dashboardScope !== 'tenant');
} }
); );
} }
@ -527,7 +529,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -577,7 +579,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -596,7 +598,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) { if (res) {
this.dashboardService.unassignDashboardFromEdge(this.config.componentsData.edgeId, dashboard.id.id).subscribe( this.dashboardService.unassignDashboardFromEdge(this.config.componentsData.edgeId, dashboard.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.dashboardScope !== 'tenant');
} }
); );
} }
@ -624,7 +626,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }

6
ui-ngx/src/app/modules/home/pages/device-profile/device-profiles-table-config.resolver.ts

@ -151,7 +151,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
} }
); );
@ -171,7 +171,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
if (res) { if (res) {
this.deviceProfileService.setDefaultDeviceProfile(deviceProfile.id.id).subscribe( this.deviceProfileService.setDefaultDeviceProfile(deviceProfile.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -191,7 +191,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
this.importExport.importDeviceProfile().subscribe( this.importExport.importDeviceProfile().subscribe(
(deviceProfile) => { (deviceProfile) => {
if (deviceProfile) { if (deviceProfile) {
this.config.table.updateData(); this.config.updateData();
} }
} }
); );

2
ui-ngx/src/app/modules/home/pages/device/device-table-header.component.ts

@ -37,7 +37,7 @@ export class DeviceTableHeaderComponent extends EntityTableHeaderComponent<Devic
deviceProfileChanged(deviceProfileId: DeviceProfileId) { deviceProfileChanged(deviceProfileId: DeviceProfileId) {
this.entitiesTableConfig.componentsData.deviceProfileId = deviceProfileId; this.entitiesTableConfig.componentsData.deviceProfileId = deviceProfileId;
this.entitiesTableConfig.table.resetSortAndFilter(true); this.entitiesTableConfig.getTable().resetSortAndFilter(true);
} }
} }

2
ui-ngx/src/app/modules/home/pages/device/device.component.ts

@ -140,7 +140,7 @@ export class DeviceComponent extends EntityComponent<DeviceInfo> {
} }
onDeviceProfileUpdated() { onDeviceProfileUpdated() {
this.entitiesTableConfig.table.updateData(false); this.entitiesTableConfig.updateData(false);
} }
onDeviceProfileChanged(deviceProfile: DeviceProfileInfo) { onDeviceProfileChanged(deviceProfile: DeviceProfileInfo) {

24
ui-ngx/src/app/modules/home/pages/device/devices-table-config.resolver.ts

@ -375,7 +375,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const url = this.router.createUrlTree([device.id.id], {relativeTo: config.table.route}); const url = this.router.createUrlTree([device.id.id], {relativeTo: config.getActivatedRoute()});
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
} }
@ -383,7 +383,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
this.homeDialogs.importEntities(EntityType.DEVICE).subscribe((res) => { this.homeDialogs.importEntities(EntityType.DEVICE).subscribe((res) => {
if (res) { if (res) {
this.broadcast.broadcast('deviceSaved'); this.broadcast.broadcast('deviceSaved');
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -394,12 +394,12 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
disableClose: true, disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'], panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: { data: {
entitiesTableConfig: this.config.table.entitiesTableConfig entitiesTableConfig: this.config
} }
}).afterClosed().subscribe( }).afterClosed().subscribe(
(res) => { (res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
} }
); );
@ -420,7 +420,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -439,7 +439,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
if (res) { if (res) {
this.deviceService.makeDevicePublic(device.id.id).subscribe( this.deviceService.makeDevicePublic(device.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -462,7 +462,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -491,7 +491,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
if (res) { if (res) {
this.deviceService.unassignDeviceFromCustomer(device.id.id).subscribe( this.deviceService.unassignDeviceFromCustomer(device.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.deviceScope !== 'tenant');
} }
); );
} }
@ -519,7 +519,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -586,7 +586,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -605,7 +605,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
if (res) { if (res) {
this.deviceService.unassignDeviceFromEdge(this.config.componentsData.edgeId, device.id.id).subscribe( this.deviceService.unassignDeviceFromEdge(this.config.componentsData.edgeId, device.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.deviceScope !== 'tenant');
} }
); );
} }
@ -633,7 +633,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }

2
ui-ngx/src/app/modules/home/pages/edge/edge-table-header.component.ts

@ -36,7 +36,7 @@ export class EdgeTableHeaderComponent extends EntityTableHeaderComponent<EdgeInf
edgeTypeChanged(edgeType: string) { edgeTypeChanged(edgeType: string) {
this.entitiesTableConfig.componentsData.edgeType = edgeType; this.entitiesTableConfig.componentsData.edgeType = edgeType;
this.entitiesTableConfig.table.resetSortAndFilter(true); this.entitiesTableConfig.getTable().resetSortAndFilter(true);
} }
} }

16
ui-ngx/src/app/modules/home/pages/edge/edges-table-config.resolver.ts

@ -318,7 +318,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
name: this.translate.instant('edge.add-edge-text'), name: this.translate.instant('edge.add-edge-text'),
icon: 'insert_drive_file', icon: 'insert_drive_file',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event) onAction: ($event) => this.config.getTable().addEntity($event)
}, },
{ {
name: this.translate.instant('edge.import'), name: this.translate.instant('edge.import'),
@ -345,7 +345,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
this.homeDialogs.importEntities(EntityType.EDGE).subscribe((res) => { this.homeDialogs.importEntities(EntityType.EDGE).subscribe((res) => {
if (res) { if (res) {
this.broadcast.broadcast('edgeSaved'); this.broadcast.broadcast('edgeSaved');
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -365,7 +365,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -374,7 +374,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const url = this.router.createUrlTree([edge.id.id], {relativeTo: config.table.route}); const url = this.router.createUrlTree([edge.id.id], {relativeTo: config.getActivatedRoute()});
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
} }
@ -392,7 +392,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
if (res) { if (res) {
this.edgeService.makeEdgePublic(edge.id.id).subscribe( this.edgeService.makeEdgePublic(edge.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -443,7 +443,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -472,7 +472,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
if (res) { if (res) {
this.edgeService.unassignEdgeFromCustomer(edge.id.id).subscribe( this.edgeService.unassignEdgeFromCustomer(edge.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.edgeScope !== 'tenant');
} }
); );
} }
@ -500,7 +500,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }

2
ui-ngx/src/app/modules/home/pages/entity-view/entity-view-table-header.component.ts

@ -36,7 +36,7 @@ export class EntityViewTableHeaderComponent extends EntityTableHeaderComponent<E
entityViewTypeChanged(entityViewType: string) { entityViewTypeChanged(entityViewType: string) {
this.entitiesTableConfig.componentsData.entityViewType = entityViewType; this.entitiesTableConfig.componentsData.entityViewType = entityViewType;
this.entitiesTableConfig.table.resetSortAndFilter(true); this.entitiesTableConfig.getTable().resetSortAndFilter(true);
} }
} }

18
ui-ngx/src/app/modules/home/pages/entity-view/entity-views-table-config.resolver.ts

@ -328,7 +328,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -337,7 +337,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const url = this.router.createUrlTree([entityView.id.id], {relativeTo: config.table.route}); const url = this.router.createUrlTree([entityView.id.id], {relativeTo: config.getActivatedRoute()});
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
} }
@ -355,7 +355,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if (res) { if (res) {
this.entityViewService.makeEntityViewPublic(entityView.id.id).subscribe( this.entityViewService.makeEntityViewPublic(entityView.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -378,7 +378,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -407,7 +407,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if (res) { if (res) {
this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id).subscribe( this.entityViewService.unassignEntityViewFromCustomer(entityView.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.entityViewScope !== 'tenant');
} }
); );
} }
@ -435,7 +435,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -479,7 +479,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
}).afterClosed() }).afterClosed()
.subscribe((res) => { .subscribe((res) => {
if (res) { if (res) {
this.config.table.updateData(); this.config.updateData();
} }
}); });
} }
@ -498,7 +498,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if (res) { if (res) {
this.entityViewService.unassignEntityViewFromEdge(this.config.componentsData.edgeId, entityView.id.id).subscribe( this.entityViewService.unassignEntityViewFromEdge(this.config.componentsData.edgeId, entityView.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.entityViewScope !== 'tenant');
} }
); );
} }
@ -526,7 +526,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }

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

@ -151,7 +151,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
name: this.translate.instant('rulechain.create-new-rulechain'), name: this.translate.instant('rulechain.create-new-rulechain'),
icon: 'insert_drive_file', icon: 'insert_drive_file',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event) onAction: ($event) => this.config.getTable().addEntity($event)
}, },
{ {
name: this.translate.instant('rulechain.import'), name: this.translate.instant('rulechain.import'),
@ -343,13 +343,13 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
this.ruleChainService.setEdgeRootRuleChain(this.config.componentsData.edgeId, ruleChain.id.id).subscribe( this.ruleChainService.setEdgeRootRuleChain(this.config.componentsData.edgeId, ruleChain.id.id).subscribe(
(edge) => { (edge) => {
this.config.componentsData.edge = edge; this.config.componentsData.edge = edge;
this.config.table.updateData(); this.config.updateData();
} }
); );
} else { } else {
this.ruleChainService.setRootRuleChain(ruleChain.id.id).subscribe( this.ruleChainService.setRootRuleChain(ruleChain.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -399,7 +399,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) { if (res) {
this.ruleChainService.setEdgeTemplateRootRuleChain(ruleChain.id.id).subscribe( this.ruleChainService.setEdgeTemplateRootRuleChain(ruleChain.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -436,11 +436,11 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'), this.dialogService.alert(this.translate.instant('edge.missing-related-rule-chains-title'),
message, this.translate.instant('action.close'), true).subscribe( message, this.translate.instant('action.close'), true).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} else { } else {
this.config.table.updateData(); this.config.updateData();
} }
} }
); );
@ -463,7 +463,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) { if (res) {
this.ruleChainService.unassignRuleChainFromEdge(this.config.componentsData.edgeId, ruleChain.id.id).subscribe( this.ruleChainService.unassignRuleChainFromEdge(this.config.componentsData.edgeId, ruleChain.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData(this.config.componentsData.ruleChainScope !== 'tenant');
} }
); );
} }
@ -491,7 +491,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
); );
forkJoin(tasks).subscribe( forkJoin(tasks).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -513,7 +513,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) { if (res) {
this.ruleChainService.setAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe( this.ruleChainService.setAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -535,7 +535,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) { if (res) {
this.ruleChainService.unsetAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe( this.ruleChainService.unsetAutoAssignToEdgeRuleChain(ruleChain.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }

6
ui-ngx/src/app/modules/home/pages/tenant-profile/tenant-profiles-table-config.resolver.ts

@ -106,7 +106,7 @@ export class TenantProfilesTableConfigResolver implements Resolve<EntityTableCon
name: this.translate.instant('tenant-profile.create-tenant-profile'), name: this.translate.instant('tenant-profile.create-tenant-profile'),
icon: 'insert_drive_file', icon: 'insert_drive_file',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event) onAction: ($event) => this.config.getTable().addEntity($event)
}, },
{ {
name: this.translate.instant('tenant-profile.import'), name: this.translate.instant('tenant-profile.import'),
@ -140,7 +140,7 @@ export class TenantProfilesTableConfigResolver implements Resolve<EntityTableCon
if (res) { if (res) {
this.tenantProfileService.setDefaultTenantProfile(tenantProfile.id.id).subscribe( this.tenantProfileService.setDefaultTenantProfile(tenantProfile.id.id).subscribe(
() => { () => {
this.config.table.updateData(); this.config.updateData();
} }
); );
} }
@ -152,7 +152,7 @@ export class TenantProfilesTableConfigResolver implements Resolve<EntityTableCon
this.importExport.importTenantProfile().subscribe( this.importExport.importTenantProfile().subscribe(
(deviceProfile) => { (deviceProfile) => {
if (deviceProfile) { if (deviceProfile) {
this.config.table.updateData(); this.config.updateData();
} }
} }
); );

2
ui-ngx/src/app/modules/home/pages/tenant/tenant.component.ts

@ -99,6 +99,6 @@ export class TenantComponent extends ContactBasedComponent<TenantInfo> {
} }
onTenantProfileUpdated() { onTenantProfileUpdated() {
this.entitiesTableConfig.table.updateData(false); this.entitiesTableConfig.updateData(false);
} }
} }

2
ui-ngx/src/app/modules/home/pages/tenant/tenants-table-config.resolver.ts

@ -91,7 +91,7 @@ export class TenantsTableConfigResolver implements Resolve<EntityTableConfig<Ten
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const url = this.router.createUrlTree([tenant.id.id], {relativeTo: config.table.route}); const url = this.router.createUrlTree([tenant.id.id], {relativeTo: config.getActivatedRoute()});
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
} }

2
ui-ngx/src/app/modules/home/pages/user/add-user-dialog.component.html

@ -19,7 +19,7 @@
<mat-toolbar color="primary"> <mat-toolbar color="primary">
<h2 translate>user.add</h2> <h2 translate>user.add</h2>
<span fxFlex></span> <span fxFlex></span>
<div [tb-help]="'user'"></div> <div [tb-help]="'users'"></div>
<button mat-icon-button <button mat-icon-button
(click)="cancel()" (click)="cancel()"
type="button"> type="button">

2
ui-ngx/src/app/modules/home/pages/user/users-table-config.resolver.ts

@ -178,7 +178,7 @@ export class UsersTableConfigResolver implements Resolve<EntityTableConfig<User>
if ($event) { if ($event) {
$event.stopPropagation(); $event.stopPropagation();
} }
const url = this.router.createUrlTree([user.id.id], {relativeTo: config.table.route}); const url = this.router.createUrlTree([user.id.id], {relativeTo: config.getActivatedRoute()});
this.router.navigateByUrl(url); this.router.navigateByUrl(url);
} }

4
ui-ngx/src/app/modules/home/pages/widget/widgets-bundles-table-config.resolver.ts

@ -76,7 +76,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon
name: this.translate.instant('widgets-bundle.create-new-widgets-bundle'), name: this.translate.instant('widgets-bundle.create-new-widgets-bundle'),
icon: 'insert_drive_file', icon: 'insert_drive_file',
isEnabled: () => true, isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event) onAction: ($event) => this.config.getTable().addEntity($event)
}, },
{ {
name: this.translate.instant('widgets-bundle.import'), name: this.translate.instant('widgets-bundle.import'),
@ -137,7 +137,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon
this.importExport.importWidgetsBundle().subscribe( this.importExport.importWidgetsBundle().subscribe(
(widgetsBundle) => { (widgetsBundle) => {
if (widgetsBundle) { if (widgetsBundle) {
this.config.table.updateData(); this.config.updateData();
} }
} }
); );

9
ui-ngx/src/app/shared/models/base-data.ts

@ -16,6 +16,7 @@
import { EntityId } from '@shared/models/id/entity-id'; import { EntityId } from '@shared/models/id/entity-id';
import { HasUUID } from '@shared/models/id/has-uuid'; import { HasUUID } from '@shared/models/id/has-uuid';
import { isDefinedAndNotNull } from '@core/utils';
export declare type HasId = EntityId | HasUUID; export declare type HasId = EntityId | HasUUID;
@ -25,3 +26,11 @@ export interface BaseData<T extends HasId> {
name?: string; name?: string;
label?: string; label?: string;
} }
export function hasIdEquals(id1: HasId, id2: HasId): boolean {
if (isDefinedAndNotNull(id1) && isDefinedAndNotNull(id2)) {
return id1.id === id2.id;
} else {
return id1 === id2;
}
}

2
ui-ngx/src/app/shared/models/device.models.ts

@ -517,6 +517,8 @@ export interface DeviceProfileAlarm {
createRules: {[severity: string]: AlarmRule}; createRules: {[severity: string]: AlarmRule};
clearRule?: AlarmRule; clearRule?: AlarmRule;
propagate?: boolean; propagate?: boolean;
propagateToOwner?: boolean;
propagateToTenant?: boolean;
propagateRelationTypes?: Array<string>; propagateRelationTypes?: Array<string>;
} }

1
ui-ngx/src/app/shared/models/public-api.ts

@ -20,6 +20,7 @@ export * from './telemetry/telemetry.models';
export * from './time/time.models'; export * from './time/time.models';
export * from './alarm.models'; export * from './alarm.models';
export * from './alias.models'; export * from './alias.models';
export * from './asset.models';
export * from './audit-log.models'; export * from './audit-log.models';
export * from './authority.enum'; export * from './authority.enum';
export * from './base-data'; export * from './base-data';

37
ui-ngx/src/app/shared/pipe/safe.pipe.ts

@ -0,0 +1,37 @@
///
/// Copyright © 2016-2022 The Thingsboard Authors
///
/// Licensed under the Apache License, Version 2.0 (the "License");
/// you may not use this file except in compliance with the License.
/// You may obtain a copy of the License at
///
/// http://www.apache.org/licenses/LICENSE-2.0
///
/// Unless required by applicable law or agreed to in writing, software
/// distributed under the License is distributed on an "AS IS" BASIS,
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
/// See the License for the specific language governing permissions and
/// limitations under the License.
///
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer, SafeHtml, SafeStyle, SafeScript, SafeUrl, SafeResourceUrl } from '@angular/platform-browser';
@Pipe({
name: 'safe'
})
export class SafePipe implements PipeTransform {
constructor(protected sanitizer: DomSanitizer) {}
public transform(value: any, type: string): SafeHtml | SafeStyle | SafeScript | SafeUrl | SafeResourceUrl {
switch (type) {
case 'html': return this.sanitizer.bypassSecurityTrustHtml(value);
case 'style': return this.sanitizer.bypassSecurityTrustStyle(value);
case 'script': return this.sanitizer.bypassSecurityTrustScript(value);
case 'url': return this.sanitizer.bypassSecurityTrustUrl(value);
case 'resourceUrl': return this.sanitizer.bypassSecurityTrustResourceUrl(value);
default: throw new Error(`Invalid safe type specified: ${type}`);
}
}
}

4
ui-ngx/src/app/shared/shared.module.ts

@ -158,6 +158,7 @@ import { HELP_MARKDOWN_COMPONENT_TOKEN, SHARED_MODULE_TOKEN } from '@shared/comp
import { TbMarkdownComponent } from '@shared/components/markdown.component'; import { TbMarkdownComponent } from '@shared/components/markdown.component';
import { ProtobufContentComponent } from '@shared/components/protobuf-content.component'; import { ProtobufContentComponent } from '@shared/components/protobuf-content.component';
import { CssComponent } from '@shared/components/css.component'; import { CssComponent } from '@shared/components/css.component';
import { SafePipe } from '@shared/pipe/safe.pipe';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) { export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService; return markedOptionsService;
@ -172,6 +173,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TruncatePipe, TruncatePipe,
TbJsonPipe, TbJsonPipe,
FileSizePipe, FileSizePipe,
SafePipe,
{ {
provide: FlowInjectionToken, provide: FlowInjectionToken,
useValue: Flow useValue: Flow
@ -263,6 +265,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TruncatePipe, TruncatePipe,
TbJsonPipe, TbJsonPipe,
FileSizePipe, FileSizePipe,
SafePipe,
SelectableColumnsPipe, SelectableColumnsPipe,
KeyboardShortcutPipe, KeyboardShortcutPipe,
TbJsonToStringDirective, TbJsonToStringDirective,
@ -457,6 +460,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TbJsonPipe, TbJsonPipe,
KeyboardShortcutPipe, KeyboardShortcutPipe,
FileSizePipe, FileSizePipe,
SafePipe,
SelectableColumnsPipe, SelectableColumnsPipe,
RouterModule, RouterModule,
TranslateModule, TranslateModule,

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

@ -1199,13 +1199,16 @@
"condition-duration-time-unit-required": "Time unit is required.", "condition-duration-time-unit-required": "Time unit is required.",
"advanced-settings": "Advanced settings", "advanced-settings": "Advanced settings",
"alarm-rule-details": "Details", "alarm-rule-details": "Details",
"alarm-rule-details-hint": "Hint: use <code><span style=\"color: #000;\">$&#123;</span>keyName<span style=\"color: #000;\">&#125;</span></code> to substitute values of the attribute or telemetry keys that are used in alarm rule condition.",
"add-alarm-rule-details": "Add details", "add-alarm-rule-details": "Add details",
"alarm-rule-mobile-dashboard": "Mobile dashboard", "alarm-rule-mobile-dashboard": "Mobile dashboard",
"alarm-rule-mobile-dashboard-hint": "Used by mobile application as an alarm details dashboard", "alarm-rule-mobile-dashboard-hint": "Used by mobile application as an alarm details dashboard",
"alarm-rule-no-mobile-dashboard": "No dashboard selected", "alarm-rule-no-mobile-dashboard": "No dashboard selected",
"propagate-alarm": "Propagate alarm", "propagate-alarm": "Propagate alarm to related entities",
"alarm-rule-relation-types-list": "Relation types to propagate", "alarm-rule-relation-types-list": "Relation types to propagate",
"alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.", "alarm-rule-relation-types-list-hint": "If Propagate relation types are not selected, alarms will be propagated without filtering by relation type.",
"propagate-alarm-to-owner": "Propagate alarm to entity owner (Customer or Tenant)",
"propagate-alarm-to-tenant": "Propagate alarm to Tenant",
"alarm-details": "Alarm details", "alarm-details": "Alarm details",
"alarm-rule-condition": "Alarm rule condition", "alarm-rule-condition": "Alarm rule condition",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition", "enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
@ -1888,7 +1891,9 @@
"has-error": "Has error", "has-error": "Has error",
"entity-id": "Entity Id", "entity-id": "Entity Id",
"entity-type": "Entity type", "entity-type": "Entity type",
"clear-filter": "Clear Filter" "clear-filter": "Clear Filter",
"clear-request-title": "Clear all events",
"clear-request-text": "Are you sure you want to clear all events?"
}, },
"extension": { "extension": {
"extensions": "Extensions", "extensions": "Extensions",

4
ui-ngx/src/assets/locale/locale.constant-ru_RU.json

@ -959,7 +959,9 @@
"messages-processed": "Сообщения обработаны", "messages-processed": "Сообщения обработаны",
"errors-occurred": "Возникли ошибки", "errors-occurred": "Возникли ошибки",
"all-events": "Все", "all-events": "Все",
"entity-type": "Тип объекта" "entity-type": "Тип объекта",
"clear-request-title": "Удалить все события",
"clear-request-text": "Вы точно хотите удалить все события?"
}, },
"extension": { "extension": {
"extensions": "Расширение", "extensions": "Расширение",

4
ui-ngx/src/assets/locale/locale.constant-uk_UA.json

@ -1211,7 +1211,9 @@
"messages-processed": "Повідомлення опрацьовані", "messages-processed": "Повідомлення опрацьовані",
"errors-occurred": "Виникли помилки", "errors-occurred": "Виникли помилки",
"all-events": "Всі", "all-events": "Всі",
"entity-type": "Тип сутності" "entity-type": "Тип сутності",
"clear-request-title": "Видалити всі події",
"clear-request-text": "Ви впевнені, що хочете видалити всі події?"
}, },
"extension": { "extension": {
"extensions": "Розширення", "extensions": "Розширення",

5
ui-ngx/yarn.lock

@ -9280,11 +9280,6 @@ symbol-observable@4.0.0:
resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205" resolved "https://registry.yarnpkg.com/symbol-observable/-/symbol-observable-4.0.0.tgz#5b425f192279e87f2f9b937ac8540d1984b39205"
integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ== integrity sha512-b19dMThMV4HVFynSAM1++gBHAbk2Tc/osgLIBZMKsyqh34jb2e8Os7T6ZW/Bt3pJFdBTd2JwAnAAEQV7rSNvcQ==
systemjs-babel@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/systemjs-babel/-/systemjs-babel-0.3.1.tgz#4b864be909b542fb7a84303f24d0dbdbc35da2e1"
integrity sha512-ggZ+v7RSKmN1OlAd46REpfZJmBTnrBUH1zgHnSalIB0KPepfPXEm1p2auE6v7wCmJl231EyApRxnw6X3SgpQtA==
systemjs@6.11.0: systemjs@6.11.0:
version "6.11.0" version "6.11.0"
resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.11.0.tgz#8df8e74fc05822e6c40170aa409b9ca64833315f" resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.11.0.tgz#8df8e74fc05822e6c40170aa409b9ca64833315f"

Loading…
Cancel
Save