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())
.setClearTs(alarm.getClearTs())
.setDetails(JacksonUtil.toString(alarm.getDetails()))
.setPropagate(alarm.isPropagate());
.setPropagate(alarm.isPropagate())
.setPropagateToOwner(alarm.isPropagateToOwner())
.setPropagateToTenant(alarm.isPropagateToTenant());
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 ...");
schemaUpdateFile = Paths.get(installScripts.getDataDir(), "upgrade", "3.3.2", SCHEMA_UPDATE_SQL);
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 {
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;");

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()) {
entitiesMap.put(entityData.getEntityId(), entityData);
}
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(securityUser.getTenantId(),
securityUser.getCustomerId(), query, entitiesMap.keySet());
PageData<AlarmData> alarms = alarmService.findAlarmDataByQueryForEntities(securityUser.getTenantId(), query, entitiesMap.keySet());
for (AlarmData alarmData : alarms.getData()) {
EntityId entityId = alarmData.getEntityId();
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;
if (!entitiesMap.isEmpty()) {
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();
stats.getAlarmQueryInvocationCnt().incrementAndGet();
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
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return alarmService.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds);
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return alarmService.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
}
@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);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
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;
@ApiModelProperty(position = 15, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
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. " +
"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;
@ -98,6 +102,8 @@ public class Alarm extends BaseData<AlarmId> implements HasName, HasTenantId, Ha
this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails();
this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant();
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;
@ApiModelProperty(position = 17, value = "Alarm originator name", example = "Thermostat")
@ApiModelProperty(position = 19, value = "Alarm originator name", example = "Thermostat")
private String originatorName;
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
@ApiModelProperty(position = 5, value = "Propagation flag to specify if alarm should be propagated to parent entities of alarm originator", example = "true")
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. " +
"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;
}

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

@ -269,6 +269,8 @@ message AlarmUpdateMsg {
int64 clearTs = 13;
string details = 14;
bool propagate = 15;
bool propagateToOwner = 16;
bool propagateToTenant = 17;
}
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.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@ -296,4 +297,15 @@ public final class TbMsg implements Serializable {
public boolean isValid() {
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.queue.util.TbLwM2mBootstrapTransportComponent;
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.LwM2MInMemoryBootstrapConfigStore;
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_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;
@Slf4j
@ -53,6 +55,7 @@ public class LwM2MTransportBootstrapService {
private final LwM2MBootstrapSecurityStore lwM2MBootstrapSecurityStore;
private final LwM2MInMemoryBootstrapConfigStore lwM2MInMemoryBootstrapConfigStore;
private final TransportService transportService;
private final TbLwM2MDtlsBootstrapCertificateVerifier certificateVerifier;
private LeshanBootstrapServer server;
@PostConstruct
@ -78,25 +81,24 @@ public class LwM2MTransportBootstrapService {
/* Create CoAP Config */
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 */
DtlsConnectorConfig.Builder dtlsConfig = new DtlsConnectorConfig.Builder(getCoapConfig(bootstrapConfig.getPort(), bootstrapConfig.getSecurePort(), serverConfig));
dtlsConfig.set(DTLS_RECOMMENDED_CURVES_ONLY, serverConfig.isRecommendedSupportedGroups());
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);
setServerWithCredentials(builder, dtlsConfig);
/* Set DTLS Config */
builder.setDtlsConfig(dtlsConfig);
/* Set securityStore with new ConfigStore */
builder.setConfigStore(lwM2MInMemoryBootstrapConfigStore);
/* SecurityStore */
builder.setSecurityStore(lwM2MBootstrapSecurityStore);
BootstrapSessionManager sessionManager = new LwM2mDefaultBootstrapSessionManager(lwM2MBootstrapSecurityStore, lwM2MInMemoryBootstrapConfigStore, transportService);
builder.setSessionManager(sessionManager);
@ -104,18 +106,14 @@ public class LwM2MTransportBootstrapService {
return builder.build();
}
private void setServerWithCredentials(LeshanBootstrapServerBuilder builder) {
private void setServerWithCredentials(LeshanBootstrapServerBuilder builder, DtlsConnectorConfig.Builder dtlsConfig) {
if (this.bootstrapConfig.getSslCredentials() != null) {
SslCredentials sslCredentials = this.bootstrapConfig.getSslCredentials();
builder.setPublicKey(sslCredentials.getPublicKey());
builder.setPrivateKey(sslCredentials.getPrivateKey());
builder.setCertificateChain(sslCredentials.getCertificateChain());
if (this.serverConfig.getTrustSslCredentials() != null) {
builder.setTrustedCertificates(this.serverConfig.getTrustSslCredentials().getTrustedCertificates());
} else {
/* by default trust all */
builder.setTrustedCertificates(new X509Certificate[0]);
}
dtlsConfig.setAsList(DtlsConfig.DTLS_CIPHER_SUITES, this.pskMode ? DefaultLwM2mTransportService.PSK_CIPHER_SUITES : DefaultLwM2mTransportService.RPK_OR_X509_CIPHER_SUITES);
dtlsConfig.setAdvancedCertificateVerifier(certificateVerifier);
} else {
/* by default trust all */
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 java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
@Slf4j
@Data
@ -97,7 +95,6 @@ public class LwM2MBootstrapConfig implements Serializable {
byte[] secretKey = new byte[]{};
byte[] serverPublicKey = new byte[]{};
serverSecurity.serverId = serverCredential.getShortServerId();
log.info("serverId = [{}]", serverSecurity.serverId);
serverSecurity.securityMode = SecurityMode.valueOf(securityMode.name());
serverSecurity.bootstrapServer = serverCredential.isBootstrapServerIs();
if (!LwM2MSecurityMode.NO_SEC.equals(securityMode)) {
@ -112,22 +109,17 @@ public class LwM2MBootstrapConfig implements Serializable {
if (LwM2MSecurityMode.PSK.equals(securityMode)) {
publicKeyOrId = server.getClientPublicKeyOrId().getBytes();
secretKey = Hex.decodeHex(server.getClientSecretKey().toCharArray());
log.info("publicKeyOrId [{}]", new String(publicKeyOrId, StandardCharsets.UTF_8));
} else {
publicKeyOrId = server.getDecodedClientPublicKeyOrId();
secretKey = server.getDecodedClientSecretKey();
log.info("publicKeyOrId [{}]", Hex.encodeHexString(publicKeyOrId));
}
serverPublicKey = serverCredential.getDecodedCServerPublicKey();
}
log.info("secretKey [{}]", Hex.encodeHexString(secretKey));
serverUri += (((serverCredential.getHost().equals("0.0.0.0") ? "localhost" : serverCredential.getHost()) + ":" + serverCredential.getPort()));
log.info("serverSecurity.uri = [{}]", serverUri);
serverSecurity.uri = serverUri;
serverSecurity.publicKeyOrId = publicKeyOrId;
serverSecurity.secretKey = secretKey;
serverSecurity.serverPublicKey = serverPublicKey;
log.info("server [{}]", Hex.encodeHexString(serverSecurity.serverPublicKey));
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 java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
@ -83,15 +84,16 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
@Override
public BootstrapSession begin(BootstrapRequest request, Identity clientIdentity) {
boolean authorized = true;
Iterator<SecurityInfo> securityInfos;
Iterator<SecurityInfo> securityInfos = null;
try {
if (bsSecurityStore != null && securityChecker != null) {
if (clientIdentity.isSecure() && clientIdentity.isPSK()) {
securityInfos = bsSecurityStore.getAllByEndpoint(clientIdentity.getPskIdentity());
} else {
if (clientIdentity.isPSK()) {
SecurityInfo securityInfo = bsSecurityStore.getByIdentity(clientIdentity.getPskIdentity());
securityInfos = Collections.singletonList(securityInfo).iterator();
} else if (!clientIdentity.isX509()) {
securityInfos = bsSecurityStore.getAllByEndpoint(request.getEndpointName());
}
authorized = securityChecker.checkSecurityInfos(request.getEndpointName(), clientIdentity, securityInfos);
authorized = this.checkSecurityInfo(request.getEndpointName(), clientIdentity, securityInfos);
}
} catch (LwM2MAuthException e) {
authorized = false;
@ -187,7 +189,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
// store response
DefaultBootstrapSession session = (DefaultBootstrapSession) bsSession;
session.getResponses().add(response);
this.sendLogs (bsSession.getEndpoint(),
this.sendLogs(bsSession.getEndpoint(),
String.format("%s: %s %s receives error response %s ", LOG_LWM2M_INFO,
request.getClass().getSimpleName(),
request.getPath().toString(), response.toString()));
@ -195,7 +197,7 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
return BootstrapPolicy.continueWith(nextRequest(bsSession));
} else {
// 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()));
return BootstrapPolicy.failed();
}
@ -222,7 +224,16 @@ public class LwM2mDefaultBootstrapSessionManager extends DefaultBootstrapSession
}
private void sendLogs(String endpointName, String logMsg) {
log.info(logMsg);
log.info("Endpoint: [{}] [{}]", 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.LwM2mTransportContext;
import org.thingsboard.server.transport.lwm2m.server.LwM2mTransportServerHelper;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.Collections;
import java.util.Iterator;
@ -67,67 +68,44 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
}
@Override
public Iterator<SecurityInfo> getAllByEndpoint(String endPoint) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endPoint, BOOTSTRAP);
if (store.getBootstrapCredentialConfig() != null) {
/* add value to store from BootstrapJson */
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;
public Iterator<SecurityInfo> getAllByEndpoint(String endpoint) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(endpoint, BOOTSTRAP);
SecurityInfo securityInfo = this.addValueToStore(store, endpoint);
return securityInfo == null ? null : Collections.singletonList(store.getSecurityInfo()).iterator();
}
@Override
public SecurityInfo getByIdentity(String identity) {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, BOOTSTRAP);
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
/* add value to store from BootstrapJson */
this.setBootstrapConfigScurityInfo(store);
BootstrapConfig bsConfig = store.getBootstrapConfig();
if (bsConfig.security != null) {
try {
bootstrapConfigStore.add(store.getEndpoint(), bsConfig);
} catch (InvalidConfigurationException e) {
log.error("Invalid Bootstrap Configuration", e);
try {
TbLwM2MSecurityInfo store = lwM2MCredentialsSecurityInfoValidator.getEndpointSecurityInfoByCredentialsId(identity, BOOTSTRAP);
if (store.getBootstrapCredentialConfig() != null && store.getSecurityMode() != null) {
/* add value to store from BootstrapJson */
this.setBootstrapConfigSecurityInfo(store);
BootstrapConfig bsConfig = store.getBootstrapConfig();
if (bsConfig.security != null) {
try {
bootstrapConfigStore.add(store.getEndpoint(), bsConfig);
} 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 */
LwM2MBootstrapConfig lwM2MBootstrapConfig = this.getParametersBootstrap(store);
if (lwM2MBootstrapConfig != null) {
@ -163,25 +141,23 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
*
* @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) {
LwM2MSecurityMode bootstrapServerSecurityMode = lwM2MBootstrapConfig.getBootstrapServer().getSecurityMode();
LwM2MSecurityMode lwm2mServerSecurityMode = lwM2MBootstrapConfig.getLwm2mServer().getSecurityMode();
AtomicBoolean validBs = new AtomicBoolean(true);
AtomicBoolean validLw = new AtomicBoolean(true);
AtomicBoolean validLw = new AtomicBoolean(true);
lwM2MBootstrapConfig.getServerConfiguration().forEach(serverCredential -> {
if (((AbstractLwM2MBootstrapServerCredential)serverCredential).isBootstrapServerIs()) {
if (((AbstractLwM2MBootstrapServerCredential) serverCredential).isBootstrapServerIs()) {
if (!bootstrapServerSecurityMode.equals(serverCredential.getSecurityMode())) {
validBs.set(false);
}
}
else {
} else {
if (!lwm2mServerSecurityMode.equals(serverCredential.getSecurityMode())) {
validLw.set(false);
}
}
});
return validBs.get()&validLw.get();
return validBs.get() && validLw.get();
}
public TransportProtos.SessionInfoProto getSessionByEndpoint(String endpoint) {
@ -191,4 +167,47 @@ public class LwM2MBootstrapSecurityStore implements BootstrapSecurityStore {
public TransportProtos.SessionInfoProto removeSessionByEndpoint(String 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.X509;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.BOOTSTRAP;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
@Slf4j
@Component
@ -89,7 +88,7 @@ public class LwM2mCredentialsSecurityInfoValidator {
}
TbLwM2MSecurityInfo securityInfo = resultSecurityStore[0];
if ((CLIENT.equals(keyValue) && securityInfo.getSecurityMode() == null)) {
if (securityInfo.getSecurityMode() == null) {
throw new LwM2MAuthException();
}
return securityInfo;
@ -119,12 +118,11 @@ public class LwM2mCredentialsSecurityInfoValidator {
createClientSecurityInfoRPK(result, endpoint, credentials.getClient());
break;
case X509:
createClientSecurityInfoX509(result, endpoint, credentials.getClient());
createClientSecurityInfoX509(result, endpoint);
break;
default:
break;
}
// } else
if (keyValue.equals(BOOTSTRAP)) {
LwM2MBootstrapConfig bootstrapCredentialConfig = new LwM2MBootstrapConfig(((Lwm2mDeviceProfileTransportConfiguration) msg.getDeviceProfile().getProfileData().getTransportConfiguration()).getBootstrap(),
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.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.security.auth.x500.X500Principal;
import java.net.InetSocketAddress;
import java.security.InvalidAlgorithmParameterException;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.security.cert.CertPath;
import java.security.cert.CertPathValidator;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateFactory;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.PKIXParameters;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import static org.thingsboard.server.transport.lwm2m.server.uplink.LwM2mTypeServer.CLIENT;
@ -95,18 +86,19 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
public void init() {
try {
/* by default trust all */
X509Certificate[] trustedCertificates = new X509Certificate[0];
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) {
log.info("Failed to initialize the ");
log.warn("Failed 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) {
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
@ -115,27 +107,32 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
} else {
try {
boolean x509CredentialsFound = false;
CertPath certpath = message.getCertificateChain();
X509Certificate[] chain = certpath.getCertificates().toArray(new X509Certificate[0]);
X509Certificate[] chain = certChain.getCertificates().toArray(new X509Certificate[0]);
for (X509Certificate cert : chain) {
try {
if (!skipValidityCheckForClientCert) {
cert.checkValidity();
}
TbLwM2MSecurityInfo securityInfo = null;
// verify if trust
if (config.getTrustSslCredentials() != null && config.getTrustSslCredentials().getTrustedCertificates().length > 0) {
if (verifyTrust(cert, config.getTrustSslCredentials().getTrustedCertificates()) != null) {
String endpoint = config.getTrustSslCredentials().getValueFromSubjectNameByKey(cert.getSubjectX500Principal().getName(), "CN");
securityInfo = StringUtils.isNotEmpty(endpoint) ? securityInfoValidator.getEndpointSecurityInfoByCredentialsId(endpoint, CLIENT) : null;
if (staticCertificateVerifier != null) {
HandshakeException exception = staticCertificateVerifier.verifyCertificate(cid, serverName, remotePeer, clientUsage, verifySubject, truncateCertificatePath, message).getException();
if (exception == null) {
try {
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
String strCert = SslUtil.getCertificateString(cert);
String sha3Hash = EncryptionUtil.getSha3Hash(strCert);
if (securityInfo == null) {
if (securityInfo == null || securityInfo.getMsg() == null) {
try {
securityInfo = securityInfoValidator.getEndpointSecurityInfoByCredentialsId(sha3Hash, CLIENT);
} catch (LwM2MAuthException e) {
@ -143,7 +140,7 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
}
}
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);
if (!credentials.getClient().getSecurityConfigClientMode().equals(LwM2MSecurityMode.X509)) {
continue;
@ -174,14 +171,10 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
}
}
if (!x509CredentialsFound) {
if (staticCertificateVerifier != null) {
staticCertificateVerifier.verifyCertificate(cid, serverName, remotePeer, clientUsage, verifySubject, truncateCertificatePath, message);
} else {
AlertMessage alert = new AlertMessage(AlertMessage.AlertLevel.FATAL, AlertMessage.AlertDescription.INTERNAL_ERROR);
throw new HandshakeException("x509 verification not enabled!", alert);
}
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) {
log.trace("Certificate validation failed!", e);
return new CertificateVerificationResult(cid, e, null);
@ -198,27 +191,4 @@ public class TbLwM2MDtlsCertificateVerifier implements NewAdvancedCertificateVer
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.TbLwM2MSecurityInfo;
import org.thingsboard.server.transport.lwm2m.server.client.LwM2MAuthException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
@ -75,8 +74,8 @@ public class TbLwM2mSecurityStore implements TbMainSecurityStore {
try {
securityInfo = fetchAndPutSecurityInfo(pskIdentity);
} catch (LwM2MAuthException e) {
log.info("Registration failed: FORBIDDEN, endpointId: [{}]", pskIdentity);
securityInfo = SecurityInfo.newPreSharedKeyInfo(pskIdentity, pskIdentity, new byte[]{0x00});
log.trace("Registration failed: No pre-shared key found for [identity: {}]", pskIdentity);
return null;
}
}
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<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds);
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.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
@ -139,11 +140,10 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
@Override
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
validateId(tenantId, INCORRECT_TENANT_ID + tenantId);
validateId(customerId, INCORRECT_CUSTOMER_ID + customerId);
return alarmDao.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds);
return alarmDao.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
}
@Override
@ -171,25 +171,25 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
private List<EntityId> createEntityAlarmRecords(Alarm alarm) throws InterruptedException, ExecutionException {
List<EntityId> propagatedEntitiesList;
Set<EntityId> propagatedEntitiesSet = new LinkedHashSet<>();
propagatedEntitiesSet.add(alarm.getOriginator());
if (alarm.isPropagate()) {
Set<EntityId> parentEntities = getParentEntities(alarm);
propagatedEntitiesList = new ArrayList<>(parentEntities.size() + 1);
for (EntityId parentId : parentEntities) {
propagatedEntitiesList.add(parentId);
createEntityAlarmRecord(alarm.getTenantId(), parentId, alarm);
}
propagatedEntitiesList.add(alarm.getOriginator());
} else {
propagatedEntitiesList = Collections.singletonList(alarm.getOriginator());
propagatedEntitiesSet.addAll(getRelatedEntities(alarm));
}
if (alarm.isPropagateToOwner()) {
propagatedEntitiesSet.add(alarm.getCustomerId() != null ? alarm.getCustomerId() : alarm.getTenantId());
}
createEntityAlarmRecord(alarm.getTenantId(), alarm.getOriginator(), alarm);
return propagatedEntitiesList;
if (alarm.isPropagateToTenant()) {
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();
//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);
query.setParameters(parameters);
List<String> propagateRelationTypes = alarm.getPropagateRelationTypes();
@ -216,11 +216,12 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
private AlarmOperationResult updateAlarm(Alarm oldAlarm, Alarm newAlarm) {
boolean oldPropagate = oldAlarm.isPropagate();
boolean newPropagate = newAlarm.isPropagate();
boolean propagationEnabled = !oldAlarm.isPropagate() && newAlarm.isPropagate();
boolean propagationToOwnerEnabled = !oldAlarm.isPropagateToOwner() && newAlarm.isPropagateToOwner();
boolean propagationToTenantEnabled = !oldAlarm.isPropagateToTenant() && newAlarm.isPropagateToTenant();
Alarm result = alarmDao.save(newAlarm.getTenantId(), merge(oldAlarm, newAlarm));
List<EntityId> propagatedEntitiesList;
if (!oldPropagate && newPropagate) {
if (propagationEnabled || propagationToOwnerEnabled || propagationToTenantEnabled) {
try {
propagatedEntitiesList = createEntityAlarmRecords(result);
} catch (InterruptedException | ExecutionException e) {
@ -372,6 +373,8 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
existing.setDetails(alarm.getDetails());
existing.setCustomerId(alarm.getCustomerId());
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> newRelationTypes = alarm.getPropagateRelationTypes();
if (!CollectionUtils.isEmpty(newRelationTypes)) {
@ -387,7 +390,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
}
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());
return entityAlarms.stream().map(EntityAlarm::getEntityId).collect(Collectors.toSet());
} 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_CLEAR_TS_PROPERTY = "clear_ts";
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_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_PROPAGATE_PROPERTY;
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_START_TS_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)
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)
private String propagateRelationTypes;
@ -130,6 +138,8 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
this.propagate = alarm.isPropagate();
this.propagateToOwner = alarm.isPropagateToOwner();
this.propagateToTenant = alarm.isPropagateToTenant();
this.startTs = alarm.getStartTs();
this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs();
@ -154,6 +164,8 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
this.severity = alarmEntity.getSeverity();
this.status = alarmEntity.getStatus();
this.propagate = alarmEntity.getPropagate();
this.propagateToOwner = alarmEntity.getPropagateToOwner();
this.propagateToTenant = alarmEntity.getPropagateToTenant();
this.startTs = alarmEntity.getStartTs();
this.endTs = alarmEntity.getEndTs();
this.ackTs = alarmEntity.getAckTs();
@ -176,6 +188,8 @@ public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity
alarm.setSeverity(severity);
alarm.setStatus(status);
alarm.setPropagate(propagate);
alarm.setPropagateToOwner(propagateToOwner);
alarm.setPropagateToTenant(propagateToTenant);
alarm.setStartTs(startTs);
alarm.setEndTs(endTs);
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
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return alarmQueryRepository.findAlarmDataByQueryForEntities(tenantId, customerId, query, orderedEntityIds);
public PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds) {
return alarmQueryRepository.findAlarmDataByQueryForEntities(tenantId, query, orderedEntityIds);
}
@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);
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, originatorId));
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.setSeverity(AlarmSeverity.valueOf(row.get(ModelConstants.ALARM_SEVERITY_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 {
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId,
PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId,
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_type as originator_type," +
" 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.start_ts as start_ts," +
" a.status as status, " +
@ -119,11 +121,10 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
}
@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 transactionTemplate.execute(status -> {
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()));
StringBuilder selectPart = new StringBuilder(FIELDS_SELECTION);
StringBuilder fromPart = new StringBuilder(" from alarm a ");
@ -134,7 +135,7 @@ public class DefaultAlarmQueryRepository implements AlarmQueryRepository {
if (pageLink.isSearchPropagatedAlarms()) {
selectPart.append(" ea.entity_id as entity_id ");
fromPart.append(JOIN_ENTITY_ALARMS);
wherePart.append(buildPermissionsQuery(tenantId, customerId, ctx));
wherePart.append(buildPermissionsQuery(tenantId, ctx));
addAnd = true;
} else {
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();
ctx.addUuidParameter("permissions_tenant_id", tenantId.getId());
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();
}

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,
customer_id uuid,
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 (

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.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());
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(deviceAlarm, customerAlarms.getData().get(0));
@ -283,7 +283,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
}
@Test
public void testFindPopagatedCustomerAlarm() throws ExecutionException, InterruptedException {
public void testFindPropagatedCustomerAssetAlarm() throws ExecutionException, InterruptedException {
Customer customer = new Customer();
customer.setTitle("TestCustomer");
customer.setTenantId(tenantId);
@ -341,7 +341,63 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setStatusList(Arrays.asList(AlarmSearchStatus.ACTIVE));
//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(customerAlarm, customerAlarms.getData().get(0));
}
@ -444,7 +500,7 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
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.assertEquals(1, alarms.getData().size());
@ -460,13 +516,13 @@ public abstract class BaseAlarmServiceTest extends AbstractServiceTest {
pageLink.setSeverityList(Arrays.asList(AlarmSeverity.CRITICAL, AlarmSeverity.WARNING));
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.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, new Alarm(alarms.getData().get(0)));
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.assertEquals(1, alarms.getData().size());
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.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.assertEquals(1, alarms.getData().size());
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.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.assertEquals(1, alarms.getData().size());
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.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.assertEquals(1, alarms.getData().size());
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.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.assertEquals(1, alarms.getData().size());
Assert.assertEquals(created, alarms.getData().get(0));

2
pom.xml

@ -88,7 +88,7 @@
<springfox-swagger.version>3.0.4</springfox-swagger.version>
<swagger-annotations.version>1.6.3</swagger-annotations.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>
<winsw.version>2.0.1</winsw.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);
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) {
existingAlarm.setSeverity(msgAlarm.getSeverity());
existingAlarm.setPropagate(msgAlarm.isPropagate());
existingAlarm.setPropagateToOwner(msgAlarm.isPropagateToOwner());
existingAlarm.setPropagateToTenant(msgAlarm.isPropagateToTenant());
existingAlarm.setPropagateRelationTypes(msgAlarm.getPropagateRelationTypes());
} else {
existingAlarm.setSeverity(processAlarmSeverity(msg));
existingAlarm.setPropagate(config.isPropagate());
existingAlarm.setPropagateToOwner(config.isPropagateToOwner());
existingAlarm.setPropagateToTenant(config.isPropagateToTenant());
existingAlarm.setPropagateRelationTypes(relationTypes);
}
existingAlarm.setDetails(details);
@ -160,6 +164,7 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
}
private Alarm buildAlarm(TbMsg msg, JsonNode details, TenantId tenantId) {
long ts = msg.getMetaDataTs();
return Alarm.builder()
.tenantId(tenantId)
.originator(msg.getOriginator())
@ -168,9 +173,8 @@ public class TbCreateAlarmNode extends TbAbstractAlarmNode<TbCreateAlarmNodeConf
.propagate(config.isPropagate())
.type(TbNodeUtils.processPattern(this.config.getAlarmType(), msg))
.propagateRelationTypes(relationTypes)
//todo-vp: alarm date should be taken from Message or current Time should be used?
// .startTs(System.currentTimeMillis())
// .endTs(System.currentTimeMillis())
.startTs(ts)
.endTs(ts)
.details(details)
.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 boolean propagate;
private boolean propagateToOwner;
private boolean propagateToTenant;
private boolean useMessageAlarmData;
private boolean dynamicSeverity;
@ -39,6 +41,8 @@ public class TbCreateAlarmNodeConfiguration extends TbAbstractAlarmNodeConfigura
configuration.setAlarmType("General Alarm");
configuration.setSeverity(AlarmSeverity.CRITICAL.name());
configuration.setPropagate(false);
configuration.setPropagateToOwner(false);
configuration.setPropagateToTenant(false);
configuration.setUseMessageAlarmData(false);
configuration.setRelationTypes(Collections.emptyList());
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.JsonElement;
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.jts.JtsSpatialContext;
import org.locationtech.spatial4j.context.jts.JtsSpatialContextFactory;
import org.locationtech.spatial4j.distance.DistanceUtils;
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.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 {
private static final SpatialContext distCtx = SpatialContext.GEO;
private static final JtsSpatialContext jtsCtx;
private static final JsonParser JSON_PARSER = new JsonParser();
static {
JtsSpatialContextFactory factory = new JtsSpatialContextFactory();
factory.normWrapLongitude = true;
@ -44,43 +59,148 @@ public class GeoUtil {
return unit.fromKm(distCtx.getDistCalc().distance(xLL, yLL) * DistanceUtils.DEG_TO_KM);
}
public static synchronized boolean contains(String polygon, Coordinates coordinates) {
JsonArray polygonArray = new JsonParser().parse(polygon).getAsJsonArray();
public static synchronized boolean contains(@NonNull String polygonInString, @NonNull Coordinates coordinates) {
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;
JsonArray innerArray = polygonArray.get(0).getAsJsonArray();
if (!containsPrimitives(innerArray)) {
arrayWithCoords = innerArray;
return globalGeometry.contains(point);
}
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);
Point point = jtsCtx.getShapeFactory().pointXY(coordinates.getLongitude(), coordinates.getLatitude());
return shape.relate(point).equals(SpatialRelation.CONTAINS);
private static JsonArray normalizePolygonsJson(JsonArray polygonsJsonArray) {
JsonArray result = new JsonArray();
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) {
ShapeFactory.PolygonBuilder polygonBuilder = jtsCtx.getShapeFactory().polygon();
boolean isFirst = true;
double firstLat = 0.0;
double firstLng = 0.0;
for (JsonElement element : coordinates) {
double lat = element.getAsJsonArray().get(0).getAsDouble();
double lng = element.getAsJsonArray().get(1).getAsDouble();
if (isFirst) {
firstLat = lat;
firstLng = lng;
isFirst = false;
private static Set<Geometry> extractHolesFrom(List<Geometry> polygons) {
Map<Geometry, List<Geometry>> polygonsHoles = new HashMap<>();
for (Geometry polygon : polygons) {
List<Geometry> holes = polygons.stream()
.filter(another -> !another.equalsExact(polygon))
.filter(another -> {
JtsGeometry currentGeo = jtsCtx.getShapeFactory().makeShape(polygon);
JtsGeometry anotherGeo = jtsCtx.getShapeFactory().makeShape(another);
boolean currentContainsAnother = currentGeo
.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) {
for (JsonElement element : array) {
return element.isJsonPrimitive();
}
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.TbRelationTypes;
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.kv.TsKvEntry;
import org.thingsboard.server.common.data.plugin.ComponentType;
@ -82,7 +81,7 @@ public class CalculateDeltaNode implements TbNode {
DonAsynchron.withCallback(getLastValue(msg.getOriginator()),
previousData -> {
double currentValue = json.get(inputKey).asDouble();
long currentTs = TbMsgTimeseriesNode.getTs(msg);
long currentTs = msg.getMetaDataTs();
if (useCache) {
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.setTenantId(ctx.getTenantId());
currentAlarm.setPropagate(alarmDefinition.isPropagate());
currentAlarm.setPropagateToOwner(alarmDefinition.isPropagateToOwner());
currentAlarm.setPropagateToTenant(alarmDefinition.isPropagateToTenant());
if (alarmDefinition.getPropagateRelationTypes() != null) {
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.profile.state.PersistedAlarmState;
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.Device;
import org.thingsboard.server.common.data.DeviceProfile;
@ -252,7 +251,7 @@ class DeviceState {
protected boolean processTelemetry(TbContext ctx, TbMsg msg) throws ExecutionException, InterruptedException {
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).
for (Map.Entry<Long, List<KvEntry>> entry : tsKvMap.entrySet()) {
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) {
return ignoreMetadataTs ? System.currentTimeMillis() : getTs(msg);
}
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;
return ignoreMetadataTs ? System.currentTimeMillis() : msg.getMetaDataTs();
}
@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": [
"node_modules/tinycolor2/dist/tinycolor-min.js",
"node_modules/split.js/dist/split.min.js",
"node_modules/systemjs/dist/system.js",
"node_modules/systemjs-babel/dist/systemjs-babel.js",
"node_modules/systemjs/dist/system.min.js",
"node_modules/marked/lib/marked.js",
"node_modules/prismjs/prism.js",
"node_modules/prismjs/components/prism-css.min.js",

1
ui-ngx/package.json

@ -89,7 +89,6 @@
"screenfull": "^6.0.0",
"split.js": "^1.6.4",
"systemjs": "6.11.0",
"systemjs-babel": "^0.3.1",
"tinycolor2": "^1.4.2",
"tooltipster": "^4.2.8",
"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(
(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) {
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 { EdgeService } from '@core/http/edge.service';
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 { AppState } from '@core/core.state';
import { ActionNotificationShow } from '@core/notification/notification.actions';
@ -94,9 +94,9 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
private onUpdate(attributes: any): void {
this.queueStartTs = 0;
let edge = attributes.reduce(function (map, attribute) {
map[attribute.key] = attribute;
return map;
const edge = attributes.reduce((attrMap, attribute) => {
attrMap[attribute.key] = attribute;
return attrMap;
}, {});
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%',
(entity) => this.updateEdgeEventStatus(entity.createdTime),
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),
new EntityActionTableColumn<EdgeEvent>('data', 'event.data',
{
@ -124,7 +125,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
onAction: ($event, entity) =>
{
this.prepareEdgeEventContent(entity).subscribe(
(content) => this.showEdgeEventContent($event, content,'event.data'),
(content) => this.showEdgeEventContent($event, content, 'event.data'),
() => this.showEntityNotFoundError()
);
}
@ -132,7 +133,7 @@ export class EdgeDownlinkTableConfig extends EntityTableConfig<EdgeEvent, TimePa
'40px'),
);
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"
[opened]="isDetailsOpen">
<tb-entity-details-panel
#entityDetailsPanel
[entitiesTableConfig]="entitiesTableConfig"
[entityId]="dataSource.currentEntity?.id"
[entityId]="isDetailsOpen ? dataSource.currentEntity?.id : null"
(closeEntityDetails)="isDetailsOpen = false; detailsPanelOpened.emit(isDetailsOpen);"
(entityUpdated)="onEntityUpdated($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 { hidePageSizePixelValue } from '@shared/models/constants';
import { IEntitiesTableComponent } from '@home/models/entity/entity-table-component.models';
import { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component';
@Component({
selector: 'tb-entities-table',
@ -118,6 +119,8 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
@ViewChild(MatPaginator) paginator: MatPaginator;
@ViewChild(MatSort) sort: MatSort;
@ViewChild('entityDetailsPanel') entityDetailsPanel: EntityDetailsPanelComponent;
private updateDataSubscription: Subscription;
private viewInited = false;
@ -182,7 +185,7 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
headerComponent.entitiesTableConfig = this.entitiesTableConfig;
}
this.entitiesTableConfig.table = this;
this.entitiesTableConfig.setTable(this);
this.translations = this.entitiesTableConfig.entityTranslations;
this.headerActionDescriptors = [...this.entitiesTableConfig.headerActionDescriptors];
@ -392,6 +395,9 @@ export class EntitiesTableComponent extends PageComponent implements IEntitiesTa
}
}
this.dataSource.loadEntities(this.pageLink);
if (this.isDetailsOpen && this.entityDetailsPanel) {
this.entityDetailsPanel.reloadEntity();
}
}
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 { AssetId } from '@shared/models/id/asset-id';
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 { EntityDetailsPanelComponent } from '@home/components/entity/entity-details-panel.component';
import { DialogService } from '@core/services/dialog.service';
import { IEntityDetailsPageComponent } from '@home/models/entity/entity-details-page-component.models';
@Component({
selector: 'tb-entity-details-page',
@ -43,7 +44,7 @@ import { DialogService } from '@core/services/dialog.service';
styleUrls: ['./entity-details-page.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class EntityDetailsPageComponent extends EntityDetailsPanelComponent implements OnInit, OnDestroy {
export class EntityDetailsPageComponent extends EntityDetailsPanelComponent implements IEntityDetailsPageComponent, OnInit, OnDestroy {
headerTitle: string;
headerSubtitle: string;
@ -54,6 +55,7 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
if (this.entitiesTableConfigValue !== entitiesTableConfig) {
this.entitiesTableConfigValue = entitiesTableConfig;
if (this.entitiesTableConfigValue) {
this.entitiesTableConfigValue.setEntityDetailsPage(this);
this.isEdit = false;
this.entity = null;
}
@ -81,9 +83,6 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
ngOnInit() {
this.headerSubtitle = '';
this.route.paramMap.subscribe( paramMap => {
this.entityId = new AssetId(paramMap.get('entityId'));
});
this.headerSubtitle = this.translate.instant(this.entitiesTableConfig.entityTranslations.details);
super.init();
this.entityComponent.isDetailsPage = true;
@ -92,6 +91,15 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
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() {
@ -99,20 +107,9 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
}
reload(): void {
this.isEdit = false;
this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe(
(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;
}
}
);
this.reloadEntity().subscribe(() => {
this.onUpdateEntity();
});
}
onToggleDetailsEditMode() {
@ -136,29 +133,23 @@ export class EntityDetailsPageComponent extends EntityDetailsPanelComponent impl
}
onApplyDetails() {
if (this.detailsForm && this.detailsForm.valid) {
const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()};
if (this.editingEntity.hasOwnProperty('additionalInfo')) {
editingEntity.additionalInfo =
mergeDeep((this.editingEntity as any).additionalInfo, this.entityComponent.entityFormValue()?.additionalInfo);
this.saveEntity(false).subscribe((entity) => {
if (entity) {
this.onUpdateEntity();
}
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 {
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>) {
if ($event) {
$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 { AppState } from '@core/core.state';
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 { FormGroup } from '@angular/forms';
import { EntityComponent } from './entity.component';
import { TbAnchorComponent } from '@shared/components/tb-anchor.component';
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 { EntityTabsComponent } from '@home/components/entity/entity-tabs.component';
import { deepClone, mergeDeep } from '@core/utils';
import { entityIdEquals } from '@shared/models/id/entity-id';
@Component({
selector: 'tb-entity-details-panel',
@ -102,9 +103,11 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
@Input()
set entityId(entityId: HasId) {
if (entityId && entityId !== this.currentEntityId) {
if (!hasIdEquals(entityId, this.currentEntityId)) {
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;
}
reload(): void {
reloadEntity(): Observable<BaseData<HasId>> {
const loadEntitySubject = new ReplaySubject<BaseData<HasId>>();
this.isEdit = false;
this.entitiesTableConfig.loadEntity(this.currentEntityId).subscribe(
(entity) => {
@ -237,8 +241,11 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
if (this.entityTabsComponent) {
this.entityTabsComponent.entity = entity;
}
loadEntitySubject.next(entity);
loadEntitySubject.complete();
}
);
return loadEntitySubject;
}
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) {
const editingEntity = {...this.editingEntity, ...this.entityComponent.entityFormValue()};
if (this.editingEntity.hasOwnProperty('additionalInfo')) {
@ -288,10 +296,18 @@ export class EntityDetailsPanelComponent extends PageComponent implements AfterV
this.entityTabsComponent.entity = entity;
}
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 {

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> {
eventTypeValue: EventType | DebugEventType;
hideClearEventAction = false;
private filterParams: FilterEventBody = {};
private filterColumns: FilterEntityColumn[] = [];
@ -140,15 +141,30 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
{
name: this.translate.instant('event.clean-events'),
icon: 'delete',
isEnabled: () => true,
onAction: ($event) => {
this.eventService.clearEvents(this.entityId, this.eventType, this.filterParams, this.tenantId, this.table.pageLink as TimePageLink).subscribe(
isEnabled: () => !this.hideClearEventAction,
onAction: $event => this.clearEvents($event)
});
}
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.table.updateData();
this.getTable().paginator.pageIndex = 0;
this.updateData();
}
);
}
}
});
}
@ -275,7 +291,7 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
break;
}
if (updateTableColumns) {
this.table.columnsUpdated(true);
this.getTable().columnsUpdated(true);
}
}
@ -345,8 +361,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
}
this.filterParams = {};
this.table.paginator.pageIndex = 0;
this.table.updateData();
this.getTable().paginator.pageIndex = 0;
this.updateData();
}
private editEventFilter($event: MouseEvent) {
@ -389,8 +405,8 @@ export class EventTableConfig extends EntityTableConfig<Event, TimePageLink> {
componentRef.onDestroy(() => {
if (componentRef.instance.result && !isEqual(this.filterParams, componentRef.instance.result.filterParams)) {
this.filterParams = componentRef.instance.result.filterParams;
this.table.paginator.pageIndex = 0;
this.table.updateData();
this.getTable().paginator.pageIndex = 0;
this.updateData();
}
});
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) {
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.
///
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 { DatePipe } from '@angular/common';
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 { DebugEventType, EventType } from '@shared/models/event.models';
import { Overlay } from '@angular/cdk/overlay';
import { Subscription } from 'rxjs';
@Component({
selector: 'tb-event-table',
templateUrl: './event-table.component.html',
styleUrls: ['./event-table.component.scss']
})
export class EventTableComponent implements OnInit {
export class EventTableComponent implements OnInit, AfterViewInit {
@Input()
tenantId: string;
@ -77,6 +78,8 @@ export class EventTableComponent implements OnInit {
eventTableConfig: EventTableConfig;
private isEmptyData$: Subscription;
constructor(private eventService: EventService,
private dialogService: DialogService,
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.
-->
<div fxLayout="row" fxLayoutAlign="start center" [formGroup]="alarmRuleConditionFormGroup" style="min-width: 0;">
<div class="tb-alarm-rule-condition" fxFlex fxLayout="column" fxLayoutAlign="center" (click)="openFilterDialog($event)">
<div fxLayout="row" fxLayoutAlign="start center" [formGroup]="alarmRuleConditionFormGroup" (click)="openFilterDialog($event)" style="min-width: 0;">
<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"
[nowrap]="true"
required

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

@ -27,6 +27,11 @@
opacity: 0.7;
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-form-field>
</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>
</mat-expansion-panel>
<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],
clearRule: [null],
propagate: [null],
propagateRelationTypes: [null]
propagateRelationTypes: [null],
propagateToOwner: [null],
propagateToTenant: [null]
}, { validators: deviceProfileAlarmValidator });
this.alarmFormGroup.valueChanges.subscribe(() => {
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-label translate>device-profile.alarm-details</mat-label>
<textarea matInput formControlName="alarmDetails" rows="5"></textarea>
<mat-hint [innerHTML]="'device-profile.alarm-rule-details-hint' | translate | safe: 'html'"></mat-hint>
</mat-form-field>
</div>
</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 {
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;
if (config) {
this.selectedTab = 0;
const displayWidgetTitle = isDefined(config.showTitle) ? config.showTitle : false;
this.widgetSettings.patchValue({
title: config.title,
showTitleIcon: isDefined(config.showTitleIcon) ? config.showTitleIcon : false,
showTitleIcon: isDefined(config.showTitleIcon) && displayWidgetTitle ? config.showTitleIcon : false,
titleIcon: isDefined(config.titleIcon) ? config.titleIcon : '',
iconColor: isDefined(config.iconColor) ? config.iconColor : 'rgba(0, 0, 0, 0.87)',
iconSize: isDefined(config.iconSize) ? config.iconSize : '24px',
titleTooltip: isDefined(config.titleTooltip) ? config.titleTooltip : '',
showTitle: isDefined(config.showTitle) ? config.showTitle : false,
showTitle: displayWidgetTitle,
dropShadow: isDefined(config.dropShadow) ? config.dropShadow : true,
enableFullscreen: isDefined(config.enableFullscreen) ? config.enableFullscreen : true,
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 { DAY, historyInterval } from '@shared/models/time/time.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 EntityStringFunction<T extends BaseData<HasId>> = (entity: T) => string;
@ -136,11 +138,13 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
constructor() {}
private table: IEntitiesTableComponent = null;
private entityDetailsPage: IEntityDetailsPageComponent = null;
componentsData: any = null;
loadDataOnInit = true;
onLoadAction: (route: ActivatedRoute) => void = null;
table: IEntitiesTableComponent = null;
useTimePageLink = false;
defaultTimewindowInterval = historyInterval(DAY);
entityType: EntityType = null;
@ -189,6 +193,40 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P
entityAdded: EntityVoidFunction<T> = () => {};
entityUpdated: EntityVoidFunction<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 {

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) {
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.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;
@ -295,7 +296,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
name: this.translate.instant('asset.add-asset-text'),
icon: 'insert_drive_file',
isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event)
onAction: ($event) => this.config.getTable().addEntity($event)
},
{
name: this.translate.instant('asset.import'),
@ -332,7 +333,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
this.homeDialogs.importEntities(EntityType.ASSET).subscribe((res) => {
if (res) {
this.broadcast.broadcast('assetSaved');
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -341,7 +342,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if ($event) {
$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);
}
@ -360,7 +361,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -379,7 +380,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if (res) {
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()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -431,7 +432,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if (res) {
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(
() => {
this.config.table.updateData();
this.config.updateData();
}
);
}
@ -503,7 +504,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -522,7 +523,7 @@ export class AssetsTableConfigResolver implements Resolve<EntityTableConfig<Asse
if (res) {
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(
() => {
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) {
$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);
}

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);
};
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>> {
@ -147,7 +148,8 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.dashboardScope);
this.config.groupActionDescriptors = this.configureGroupActions(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.deleteEnabled = () => this.config.componentsData.dashboardScope === 'tenant';
return this.config;
@ -322,7 +324,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
name: this.translate.instant('dashboard.create-new-dashboard'),
icon: 'insert_drive_file',
isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event)
onAction: ($event) => this.config.getTable().addEntity($event)
},
{
name: this.translate.instant('dashboard.import'),
@ -372,7 +374,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
this.importExport.importDashboard().subscribe(
(dashboard) => {
if (dashboard) {
this.config.table.updateData();
this.config.updateData();
}
}
);
@ -400,7 +402,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -420,7 +422,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}
}).afterClosed()
.subscribe(() => {
this.config.table.updateData();
this.config.updateData();
});
}
);
@ -440,7 +442,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) {
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()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -499,7 +501,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) {
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(
() => {
this.config.table.updateData();
this.config.updateData();
}
);
}
@ -577,7 +579,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -596,7 +598,7 @@ export class DashboardsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) {
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(
() => {
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(
(res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
}
);
@ -171,7 +171,7 @@ export class DeviceProfilesTableConfigResolver implements Resolve<EntityTableCon
if (res) {
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(
(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) {
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() {
this.entitiesTableConfig.table.updateData(false);
this.entitiesTableConfig.updateData(false);
}
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) {
$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);
}
@ -383,7 +383,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
this.homeDialogs.importEntities(EntityType.DEVICE).subscribe((res) => {
if (res) {
this.broadcast.broadcast('deviceSaved');
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -394,12 +394,12 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
entitiesTableConfig: this.config.table.entitiesTableConfig
entitiesTableConfig: this.config
}
}).afterClosed().subscribe(
(res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
}
);
@ -420,7 +420,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -439,7 +439,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
if (res) {
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()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -491,7 +491,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
if (res) {
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(
() => {
this.config.table.updateData();
this.config.updateData();
}
);
}
@ -586,7 +586,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -605,7 +605,7 @@ export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<Dev
if (res) {
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(
() => {
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) {
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'),
icon: 'insert_drive_file',
isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event)
onAction: ($event) => this.config.getTable().addEntity($event)
},
{
name: this.translate.instant('edge.import'),
@ -345,7 +345,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
this.homeDialogs.importEntities(EntityType.EDGE).subscribe((res) => {
if (res) {
this.broadcast.broadcast('edgeSaved');
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -365,7 +365,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -374,7 +374,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
if ($event) {
$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);
}
@ -392,7 +392,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
if (res) {
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()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -472,7 +472,7 @@ export class EdgesTableConfigResolver implements Resolve<EntityTableConfig<EdgeI
if (res) {
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(
() => {
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) {
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()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -337,7 +337,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if ($event) {
$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);
}
@ -355,7 +355,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if (res) {
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()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -407,7 +407,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if (res) {
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(
() => {
this.config.table.updateData();
this.config.updateData();
}
);
}
@ -479,7 +479,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
}).afterClosed()
.subscribe((res) => {
if (res) {
this.config.table.updateData();
this.config.updateData();
}
});
}
@ -498,7 +498,7 @@ export class EntityViewsTableConfigResolver implements Resolve<EntityTableConfig
if (res) {
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(
() => {
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'),
icon: 'insert_drive_file',
isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event)
onAction: ($event) => this.config.getTable().addEntity($event)
},
{
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(
(edge) => {
this.config.componentsData.edge = edge;
this.config.table.updateData();
this.config.updateData();
}
);
} else {
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) {
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'),
message, this.translate.instant('action.close'), true).subscribe(
() => {
this.config.table.updateData();
this.config.updateData();
}
);
} else {
this.config.table.updateData();
this.config.updateData();
}
}
);
@ -463,7 +463,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) {
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(
() => {
this.config.table.updateData();
this.config.updateData();
}
);
}
@ -513,7 +513,7 @@ export class RuleChainsTableConfigResolver implements Resolve<EntityTableConfig<
if (res) {
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) {
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'),
icon: 'insert_drive_file',
isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event)
onAction: ($event) => this.config.getTable().addEntity($event)
},
{
name: this.translate.instant('tenant-profile.import'),
@ -140,7 +140,7 @@ export class TenantProfilesTableConfigResolver implements Resolve<EntityTableCon
if (res) {
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(
(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() {
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) {
$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);
}

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

@ -19,7 +19,7 @@
<mat-toolbar color="primary">
<h2 translate>user.add</h2>
<span fxFlex></span>
<div [tb-help]="'user'"></div>
<div [tb-help]="'users'"></div>
<button mat-icon-button
(click)="cancel()"
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) {
$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);
}

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'),
icon: 'insert_drive_file',
isEnabled: () => true,
onAction: ($event) => this.config.table.addEntity($event)
onAction: ($event) => this.config.getTable().addEntity($event)
},
{
name: this.translate.instant('widgets-bundle.import'),
@ -137,7 +137,7 @@ export class WidgetsBundlesTableConfigResolver implements Resolve<EntityTableCon
this.importExport.importWidgetsBundle().subscribe(
(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 { HasUUID } from '@shared/models/id/has-uuid';
import { isDefinedAndNotNull } from '@core/utils';
export declare type HasId = EntityId | HasUUID;
@ -25,3 +26,11 @@ export interface BaseData<T extends HasId> {
name?: 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};
clearRule?: AlarmRule;
propagate?: boolean;
propagateToOwner?: boolean;
propagateToTenant?: boolean;
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 './alarm.models';
export * from './alias.models';
export * from './asset.models';
export * from './audit-log.models';
export * from './authority.enum';
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 { ProtobufContentComponent } from '@shared/components/protobuf-content.component';
import { CssComponent } from '@shared/components/css.component';
import { SafePipe } from '@shared/pipe/safe.pipe';
export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService) {
return markedOptionsService;
@ -172,6 +173,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TruncatePipe,
TbJsonPipe,
FileSizePipe,
SafePipe,
{
provide: FlowInjectionToken,
useValue: Flow
@ -263,6 +265,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TruncatePipe,
TbJsonPipe,
FileSizePipe,
SafePipe,
SelectableColumnsPipe,
KeyboardShortcutPipe,
TbJsonToStringDirective,
@ -457,6 +460,7 @@ export function MarkedOptionsFactory(markedOptionsService: MarkedOptionsService)
TbJsonPipe,
KeyboardShortcutPipe,
FileSizePipe,
SafePipe,
SelectableColumnsPipe,
RouterModule,
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.",
"advanced-settings": "Advanced settings",
"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",
"alarm-rule-mobile-dashboard": "Mobile dashboard",
"alarm-rule-mobile-dashboard-hint": "Used by mobile application as an alarm details dashboard",
"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-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-rule-condition": "Alarm rule condition",
"enter-alarm-rule-condition-prompt": "Please add alarm rule condition",
@ -1888,7 +1891,9 @@
"has-error": "Has error",
"entity-id": "Entity Id",
"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": {
"extensions": "Extensions",

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

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

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

@ -1211,7 +1211,9 @@
"messages-processed": "Повідомлення опрацьовані",
"errors-occurred": "Виникли помилки",
"all-events": "Всі",
"entity-type": "Тип сутності"
"entity-type": "Тип сутності",
"clear-request-title": "Видалити всі події",
"clear-request-text": "Ви впевнені, що хочете видалити всі події?"
},
"extension": {
"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"
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:
version "6.11.0"
resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-6.11.0.tgz#8df8e74fc05822e6c40170aa409b9ca64833315f"

Loading…
Cancel
Save