Browse Source

Implement alarms table

pull/2147/head
Igor Kulikov 7 years ago
parent
commit
e91fd302d6
  1. 7
      application/src/main/java/org/thingsboard/server/controller/AlarmController.java
  2. 5
      common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java
  3. 2
      dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java
  4. 41
      dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java
  5. 157
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java
  6. 2
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java
  7. 107
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java
  8. 40
      dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java
  9. 24
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java
  10. 32
      dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java
  11. 78
      ui-ngx/src/app/core/http/alarm.service.ts
  12. 123
      ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html
  13. 155
      ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts
  14. 137
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts
  15. 27
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html
  16. 35
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss
  17. 47
      ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts
  18. 18
      ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html
  19. 22
      ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss
  20. 90
      ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts
  21. 16
      ui-ngx/src/app/modules/home/components/home-components.module.ts
  22. 4
      ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html
  23. 4
      ui-ngx/src/app/modules/home/pages/customer/customer-tabs.component.html
  24. 2
      ui-ngx/src/app/modules/home/pages/dashboard/dashboard-form.component.html
  25. 4
      ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html
  26. 4
      ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html
  27. 4
      ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html
  28. 4
      ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html
  29. 1
      ui-ngx/src/app/shared/components/dialog.component.ts
  30. 87
      ui-ngx/src/app/shared/models/alarm.models.ts
  31. 13
      ui-ngx/src/app/shared/models/entity-type.models.ts

7
application/src/main/java/org/thingsboard/server/controller/AlarmController.java

@ -150,9 +150,11 @@ public class AlarmController extends BaseController {
@RequestParam(required = false) String status,
@RequestParam int pageSize,
@RequestParam int page,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String sortProperty,
@RequestParam(required = false) String sortOrder,
@RequestParam(required = false) Long startTime,
@RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) Boolean fetchOriginator
) throws ThingsboardException {
checkParameter("EntityId", strEntityId);
@ -165,9 +167,8 @@ public class AlarmController extends BaseController {
"and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
}
checkEntityId(entityId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try {
TimePageLink pageLink = createTimePageLink(pageSize, page, "",
"id", ascOrder ? "asc" : "desc", startTime, endTime);
return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
} catch (Exception e) {
throw handleException(e);

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

@ -29,6 +29,11 @@ public class AlarmInfo extends Alarm {
super(alarm);
}
public AlarmInfo(Alarm alarm, String originatorName) {
super(alarm);
this.originatorName = originatorName;
}
public String getOriginatorName() {
return originatorName;
}

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

@ -40,5 +40,5 @@ public interface AlarmDao extends Dao<Alarm> {
Alarm save(TenantId tenantId, Alarm alarm);
ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query);
PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query);
}

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

@ -261,27 +261,25 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
@Override
public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {
ListenableFuture<PageData<AlarmInfo>> alarms = alarmDao.findAlarms(tenantId, query);
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
alarms = Futures.transformAsync(alarms, input -> {
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.getData().size());
for (AlarmInfo alarmInfo : input.getData()) {
alarmFutures.add(Futures.transform(
entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
if (originatorName == null) {
originatorName = "Deleted";
}
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size());
for (AlarmInfo alarmInfo : alarms.getData()) {
alarmFutures.add(Futures.transform(
entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
if (originatorName == null) {
originatorName = "Deleted";
}
));
}
return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext());
});
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
}
));
}
return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
return new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), alarms.hasNext());
});
}
return alarms;
return Futures.immediateFuture(alarms);
}
@Override
@ -293,14 +291,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
AlarmQuery query;
while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) {
query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false);
PageData<AlarmInfo> alarms;
try {
alarms = alarmDao.findAlarms(tenantId, query).get();
} catch (ExecutionException | InterruptedException e) {
log.warn("Failed to find highest alarm severity. EntityId: [{}], AlarmSearchStatus: [{}], AlarmStatus: [{}]",
entityId, alarmSearchStatus, alarmStatus);
throw new RuntimeException(e);
}
PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
if (alarms.hasNext()) {
nextPageLink = nextPageLink.nextPageLink();
}

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

@ -0,0 +1,157 @@
/**
* Copyright © 2016-2019 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.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.*;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_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;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY;
@Data
@EqualsAndHashCode(callSuper = true)
@TypeDef(name = "json", typeClass = JsonStringType.class)
@MappedSuperclass
public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity<T> implements BaseEntity<T> {
@Column(name = ALARM_TENANT_ID_PROPERTY)
private String tenantId;
@Column(name = ALARM_ORIGINATOR_ID_PROPERTY)
private String originatorId;
@Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY)
private EntityType originatorType;
@Column(name = ALARM_TYPE_PROPERTY)
private String type;
@Enumerated(EnumType.STRING)
@Column(name = ALARM_SEVERITY_PROPERTY)
private AlarmSeverity severity;
@Enumerated(EnumType.STRING)
@Column(name = ALARM_STATUS_PROPERTY)
private AlarmStatus status;
@Column(name = ALARM_START_TS_PROPERTY)
private Long startTs;
@Column(name = ALARM_END_TS_PROPERTY)
private Long endTs;
@Column(name = ALARM_ACK_TS_PROPERTY)
private Long ackTs;
@Column(name = ALARM_CLEAR_TS_PROPERTY)
private Long clearTs;
@Type(type = "json")
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
private JsonNode details;
@Column(name = ALARM_PROPAGATE_PROPERTY)
private Boolean propagate;
public AbstractAlarmEntity() {
super();
}
public AbstractAlarmEntity(Alarm alarm) {
if (alarm.getId() != null) {
this.setId(alarm.getId().getId());
}
if (alarm.getTenantId() != null) {
this.tenantId = UUIDConverter.fromTimeUUID(alarm.getTenantId().getId());
}
this.type = alarm.getType();
this.originatorId = UUIDConverter.fromTimeUUID(alarm.getOriginator().getId());
this.originatorType = alarm.getOriginator().getEntityType();
this.type = alarm.getType();
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
this.propagate = alarm.isPropagate();
this.startTs = alarm.getStartTs();
this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails();
}
public AbstractAlarmEntity(AlarmEntity alarmEntity) {
this.setId(alarmEntity.getId());
this.tenantId = alarmEntity.getTenantId();
this.type = alarmEntity.getType();
this.originatorId = alarmEntity.getOriginatorId();
this.originatorType = alarmEntity.getOriginatorType();
this.type = alarmEntity.getType();
this.severity = alarmEntity.getSeverity();
this.status = alarmEntity.getStatus();
this.propagate = alarmEntity.getPropagate();
this.startTs = alarmEntity.getStartTs();
this.endTs = alarmEntity.getEndTs();
this.ackTs = alarmEntity.getAckTs();
this.clearTs = alarmEntity.getClearTs();
this.details = alarmEntity.getDetails();
}
protected Alarm toAlarm() {
Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id)));
alarm.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id)));
if (tenantId != null) {
alarm.setTenantId(new TenantId(UUIDConverter.fromString(tenantId)));
}
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, UUIDConverter.fromString(originatorId)));
alarm.setType(type);
alarm.setSeverity(severity);
alarm.setStatus(status);
alarm.setPropagate(propagate);
alarm.setStartTs(startTs);
alarm.setEndTs(endTs);
alarm.setAckTs(ackTs);
alarm.setClearTs(clearTs);
alarm.setDetails(details);
return alarm;
}
}

2
dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java

@ -83,7 +83,7 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
}
public AbstractDeviceEntity(DeviceEntity deviceEntity) {
this.setId(deviceEntity.getId());;
this.setId(deviceEntity.getId());
this.tenantId = deviceEntity.getTenantId();
this.customerId = deviceEntity.getCustomerId();
this.type = deviceEntity.getType();

107
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java

@ -15,133 +15,34 @@
*/
package org.thingsboard.server.dao.model.sql;
import com.datastax.driver.core.utils.UUIDs;
import com.fasterxml.jackson.databind.JsonNode;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.UUIDConverter;
import org.thingsboard.server.common.data.alarm.Alarm;
import org.thingsboard.server.common.data.alarm.AlarmId;
import org.thingsboard.server.common.data.alarm.AlarmSeverity;
import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityIdFactory;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.dao.model.BaseEntity;
import org.thingsboard.server.dao.model.BaseSqlEntity;
import org.thingsboard.server.dao.model.ModelConstants;
import org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_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;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY;
import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY;
@Data
@EqualsAndHashCode(callSuper = true)
@Entity
@TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ALARM_COLUMN_FAMILY_NAME)
public final class AlarmEntity extends BaseSqlEntity<Alarm> implements BaseEntity<Alarm> {
@Column(name = ALARM_TENANT_ID_PROPERTY)
private String tenantId;
@Column(name = ALARM_ORIGINATOR_ID_PROPERTY)
private String originatorId;
@Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY)
private EntityType originatorType;
@Column(name = ALARM_TYPE_PROPERTY)
private String type;
@Enumerated(EnumType.STRING)
@Column(name = ALARM_SEVERITY_PROPERTY)
private AlarmSeverity severity;
@Enumerated(EnumType.STRING)
@Column(name = ALARM_STATUS_PROPERTY)
private AlarmStatus status;
@Column(name = ALARM_START_TS_PROPERTY)
private Long startTs;
@Column(name = ALARM_END_TS_PROPERTY)
private Long endTs;
@Column(name = ALARM_ACK_TS_PROPERTY)
private Long ackTs;
@Column(name = ALARM_CLEAR_TS_PROPERTY)
private Long clearTs;
@Type(type = "json")
@Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
private JsonNode details;
@Column(name = ALARM_PROPAGATE_PROPERTY)
private Boolean propagate;
public final class AlarmEntity extends AbstractAlarmEntity<Alarm> {
public AlarmEntity() {
super();
}
public AlarmEntity(Alarm alarm) {
if (alarm.getId() != null) {
this.setId(alarm.getId().getId());
}
if (alarm.getTenantId() != null) {
this.tenantId = UUIDConverter.fromTimeUUID(alarm.getTenantId().getId());
}
this.type = alarm.getType();
this.originatorId = UUIDConverter.fromTimeUUID(alarm.getOriginator().getId());
this.originatorType = alarm.getOriginator().getEntityType();
this.type = alarm.getType();
this.severity = alarm.getSeverity();
this.status = alarm.getStatus();
this.propagate = alarm.isPropagate();
this.startTs = alarm.getStartTs();
this.endTs = alarm.getEndTs();
this.ackTs = alarm.getAckTs();
this.clearTs = alarm.getClearTs();
this.details = alarm.getDetails();
super(alarm);
}
@Override
public Alarm toData() {
Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id)));
alarm.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id)));
if (tenantId != null) {
alarm.setTenantId(new TenantId(UUIDConverter.fromString(tenantId)));
}
alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, UUIDConverter.fromString(originatorId)));
alarm.setType(type);
alarm.setSeverity(severity);
alarm.setStatus(status);
alarm.setPropagate(propagate);
alarm.setStartTs(startTs);
alarm.setEndTs(endTs);
alarm.setAckTs(ackTs);
alarm.setClearTs(clearTs);
alarm.setDetails(details);
return alarm;
return super.toAlarm();
}
}
}

40
dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java

@ -0,0 +1,40 @@
/**
* Copyright © 2016-2019 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.dao.model.sql;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.thingsboard.server.common.data.alarm.AlarmInfo;
@Data
@EqualsAndHashCode(callSuper = true)
public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
private String originatorName;
public AlarmInfoEntity() {
super();
}
public AlarmInfoEntity(AlarmEntity alarmEntity) {
super(alarmEntity);
}
@Override
public AlarmInfo toData() {
return new AlarmInfo(super.toAlarm(), this.originatorName);
}
}

24
dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java

@ -15,12 +15,14 @@
*/
package org.thingsboard.server.dao.sql.alarm;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.AlarmInfoEntity;
import org.thingsboard.server.dao.util.SqlDao;
import java.util.List;
@ -38,4 +40,26 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
@Param("entityType") EntityType entityType,
@Param("alarmType") String alarmType,
Pageable pageable);
@Query("SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a, " +
"RelationEntity re " +
"WHERE a.tenantId = :tenantId " +
"AND a.id = re.toId AND re.toType = 'ALARM' " +
"AND re.relationTypeGroup = 'ALARM' " +
"AND re.relationType = :relationType " +
"AND re.fromId = :affectedEntityId " +
"AND re.fromType = :affectedEntityType " +
"AND (:startId IS NULL OR a.id >= :startId) " +
"AND (:endId IS NULL OR a.id <= :endId) " +
"AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" +
"OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" +
"OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))")
Page<AlarmInfoEntity> findAlarms(@Param("tenantId") String tenantId,
@Param("affectedEntityId") String affectedEntityId,
@Param("affectedEntityType") String affectedEntityType,
@Param("relationType") String relationType,
@Param("startId") String startId,
@Param("endId") String endId,
@Param("searchText") String searchText,
Pageable pageable);
}

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

@ -43,8 +43,13 @@ import org.thingsboard.server.dao.util.SqlDao;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
import static org.thingsboard.server.dao.DaoUtil.endTimeToId;
import static org.thingsboard.server.dao.DaoUtil.startTimeToId;
/**
* Created by Valerii Sosliuk on 5/19/2017.
*/
@ -93,7 +98,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
}
@Override
public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {
public PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query) {
log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink());
EntityId affectedEntity = query.getAffectedEntityId();
String searchStatusName;
@ -105,17 +110,18 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
searchStatusName = query.getStatus().name();
}
String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName;
ListenableFuture<PageData<EntityRelation>> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink());
return Futures.transformAsync(relations, input -> {
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.getData().size());
for (EntityRelation relation : input.getData()) {
alarmFutures.add(Futures.transform(
findAlarmByIdAsync(tenantId, relation.getTo().getId()),
AlarmInfo::new));
}
return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext());
});
});
return DaoUtil.toPageData(
alarmRepository.findAlarms(
fromTimeUUID(tenantId.getId()),
fromTimeUUID(affectedEntity.getId()),
affectedEntity.getEntityType().name(),
relationType,
startTimeToId(query.getPageLink().getStartTime()),
endTimeToId(query.getPageLink().getEndTime()),
Objects.toString(query.getPageLink().getTextSearch(), ""),
DaoUtil.toPageable(query.getPageLink())
)
);
}
}

78
ui-ngx/src/app/core/http/alarm.service.ts

@ -0,0 +1,78 @@
///
/// Copyright © 2016-2019 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 { Injectable } from '@angular/core';
import { defaultHttpOptions } from './http-utils';
import { Observable } from 'rxjs/index';
import { HttpClient } from '@angular/common/http';
import { PageData } from '@shared/models/page/page-data';
import { EntityId } from '@shared/models/id/entity-id';
import {
Alarm,
AlarmInfo,
AlarmQuery,
AlarmSearchStatus,
AlarmSeverity,
AlarmStatus
} from '@shared/models/alarm.models';
@Injectable({
providedIn: 'root'
})
export class AlarmService {
constructor(
private http: HttpClient
) { }
public getAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> {
return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmInfo> {
return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> {
return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> {
return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> {
return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public getAlarms(query: AlarmQuery,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AlarmInfo>> {
return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,
defaultHttpOptions(ignoreLoading, ignoreErrors));
}
public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus,
ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmSeverity> {
let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`;
if (alarmSearchStatus) {
url += `?searchStatus=${alarmSearchStatus}`;
} else if (alarmStatus) {
url += `?status=${alarmStatus}`;
}
return this.http.get<AlarmSeverity>(url,
defaultHttpOptions(ignoreLoading, ignoreErrors));
}
}

123
ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html

@ -0,0 +1,123 @@
<!--
Copyright © 2016-2019 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.
-->
<form [formGroup]="alarmFormGroup" style="width: 600px;">
<mat-toolbar fxLayout="row" color="primary">
<h2>{{ 'alarm.alarm-details' | translate }}</h2>
<span fxFlex></span>
<button mat-button mat-icon-button
(click)="close()"
type="button">
<mat-icon class="material-icons">close</mat-icon>
</button>
</mat-toolbar>
<mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
</mat-progress-bar>
<div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
<div mat-dialog-content>
<fieldset [disabled]="isLoading$ | async">
<div fxLayout="row" fxLayoutGap="6px">
<mat-form-field class="mat-block">
<mat-label translate>alarm.created-time</mat-label>
<input matInput formControlName="createdTime" readonly>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>alarm.originator</mat-label>
<input matInput formControlName="originatorName" readonly>
</mat-form-field>
</div>
<div fxLayout="row" fxLayoutGap="6px" *ngIf="alarmFormGroup.get('startTime').value ||
alarmFormGroup.get('endTime').value">
<mat-form-field *ngIf="alarmFormGroup.get('startTime').value" fxFlex class="mat-block">
<mat-label translate>alarm.start-time</mat-label>
<input matInput formControlName="startTime" readonly>
</mat-form-field>
<mat-form-field *ngIf="alarmFormGroup.get('endTime').value" fxFlex class="mat-block">
<mat-label translate>alarm.end-time</mat-label>
<input matInput formControlName="endTime" readonly>
</mat-form-field>
<span fxFlex *ngIf="!alarmFormGroup.get('startTime').value || !alarmFormGroup.get('endTime').value"></span>
</div>
<div fxLayout="row" fxLayoutGap="6px" *ngIf="alarmFormGroup.get('ackTime').value ||
alarmFormGroup.get('clearTime').value">
<mat-form-field *ngIf="alarmFormGroup.get('ackTime').value" fxFlex class="mat-block">
<mat-label translate>alarm.ack-time</mat-label>
<input matInput formControlName="ackTime" readonly>
</mat-form-field>
<mat-form-field *ngIf="alarmFormGroup.get('clearTime').value" fxFlex class="mat-block">
<mat-label translate>alarm.clear-time</mat-label>
<input matInput formControlName="clearTime" readonly>
</mat-form-field>
<span fxFlex *ngIf="!alarmFormGroup.get('ackTime').value || !alarmFormGroup.get('clearTime').value"></span>
</div>
<div fxLayout="row" fxLayoutGap="6px">
<mat-form-field fxFlex class="mat-block">
<mat-label translate>alarm.type</mat-label>
<input matInput formControlName="type" readonly>
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>alarm.severity</mat-label>
<input matInput formControlName="alarmSeverity" readonly
[ngStyle]="{fontWeight: 'bold', color: alarmSeverityColorsMap.get((alarm$ | async)?.severity)}">
</mat-form-field>
<mat-form-field fxFlex class="mat-block">
<mat-label translate>alarm.status</mat-label>
<input matInput formControlName="alarmStatus" readonly>
</mat-form-field>
</div>
<tb-json-object-edit
*ngIf="displayDetails"
formControlName="alarmDetails"
readonly
label="{{ 'alarm.details' | translate }}">
</tb-json-object-edit>
</fieldset>
</div>
<div mat-dialog-actions fxLayout="row">
<div fxLayout="row" *ngIf="alarm$ | async; let alarm;">
<button *ngIf="allowAcknowledgment && (alarm.status === alarmStatuses.ACTIVE_UNACK ||
alarm.status === alarmStatuses.CLEARED_UNACK)"
mat-button
mat-raised-button
color="primary"
type="button"
(click)="acknowledge()"
style="margin-right: 20px;"
[disabled]="(isLoading$ | async)">
{{ 'alarm.acknowledge' | translate }}
</button>
<button *ngIf="allowClear && (alarm.status === alarmStatuses.ACTIVE_ACK ||
alarm.status === alarmStatuses.ACTIVE_UNACK)"
mat-button
mat-raised-button
color="primary"
type="button"
(click)="clear()"
[disabled]="(isLoading$ | async)">
{{ 'alarm.clear' | translate }}
</button>
</div>
<span fxFlex></span>
<button mat-button color="primary"
style="margin-right: 20px;"
type="button"
[disabled]="(isLoading$ | async)"
(click)="close()" cdkFocusInitial>
{{ 'action.close' | translate }}
</button>
</div>
</form>

155
ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts

@ -0,0 +1,155 @@
///
/// Copyright © 2016-2019 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 { Component, Inject, OnInit } from '@angular/core';
import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { FormBuilder, FormGroup } from '@angular/forms';
import { Observable, Subject } from 'rxjs';
import { Router } from '@angular/router';
import { DialogComponent } from '@app/shared/components/dialog.component';
import {
AlarmInfo,
alarmSeverityColors,
alarmSeverityTranslations,
AlarmStatus,
alarmStatusTranslations
} from '@app/shared/models/alarm.models';
import { AlarmService } from '@core/http/alarm.service';
import { share, tap } from 'rxjs/operators';
import { DatePipe } from '@angular/common';
import { TranslateService } from '@ngx-translate/core';
export interface AlarmDetailsDialogData {
alarmId: string;
allowAcknowledgment: boolean;
allowClear: boolean;
displayDetails: boolean;
}
@Component({
selector: 'tb-alarm-details-dialog',
templateUrl: './alarm-details-dialog.component.html',
styleUrls: []
})
export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDialogComponent, boolean> implements OnInit {
alarmFormGroup: FormGroup;
allowAcknowledgment: boolean;
allowClear: boolean;
displayDetails: boolean;
loadAlarmSubject = new Subject<AlarmInfo>();
alarm$: Observable<AlarmInfo> = this.loadAlarmSubject.asObservable().pipe(
tap(alarm => this.loadAlarmFields(alarm)),
share()
);
alarmSeverityColorsMap = alarmSeverityColors;
alarmStatuses = AlarmStatus;
alarmUpdated = false;
constructor(protected store: Store<AppState>,
protected router: Router,
private datePipe: DatePipe,
private translate: TranslateService,
@Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData,
private alarmService: AlarmService,
public dialogRef: MatDialogRef<AlarmDetailsDialogComponent, boolean>,
public fb: FormBuilder) {
super(store, router, dialogRef);
this.allowAcknowledgment = data.allowAcknowledgment;
this.allowClear = data.allowClear;
this.displayDetails = data.displayDetails;
this.alarmFormGroup = this.fb.group(
{
createdTime: [''],
originatorName: [''],
startTime: [''],
endTime: [''],
ackTime: [''],
clearTime: [''],
type: [''],
alarmSeverity: [''],
alarmStatus: [''],
alarmDetails: [null]
}
);
this.loadAlarm();
}
loadAlarm() {
this.alarmService.getAlarmInfo(this.data.alarmId).subscribe(
alarm => this.loadAlarmSubject.next(alarm)
);
}
loadAlarmFields(alarm: AlarmInfo) {
this.alarmFormGroup.get('createdTime')
.patchValue(this.datePipe.transform(alarm.createdTime, 'yyyy-MM-dd HH:mm:ss'));
this.alarmFormGroup.get('originatorName')
.patchValue(alarm.originatorName);
if (alarm.startTs) {
this.alarmFormGroup.get('startTime')
.patchValue(this.datePipe.transform(alarm.startTs, 'yyyy-MM-dd HH:mm:ss'));
}
if (alarm.endTs) {
this.alarmFormGroup.get('endTime')
.patchValue(this.datePipe.transform(alarm.endTs, 'yyyy-MM-dd HH:mm:ss'));
}
if (alarm.ackTs) {
this.alarmFormGroup.get('ackTime')
.patchValue(this.datePipe.transform(alarm.ackTs, 'yyyy-MM-dd HH:mm:ss'));
}
if (alarm.clearTs) {
this.alarmFormGroup.get('clearTime')
.patchValue(this.datePipe.transform(alarm.clearTs, 'yyyy-MM-dd HH:mm:ss'));
}
this.alarmFormGroup.get('type').patchValue(alarm.type);
this.alarmFormGroup.get('alarmSeverity')
.patchValue(this.translate.instant(alarmSeverityTranslations.get(alarm.severity)));
this.alarmFormGroup.get('alarmStatus')
.patchValue(this.translate.instant(alarmStatusTranslations.get(alarm.status)));
this.alarmFormGroup.get('alarmDetails').patchValue(alarm.details);
}
ngOnInit(): void {
}
close(): void {
this.dialogRef.close(this.alarmUpdated);
}
acknowledge(): void {
this.alarmService.ackAlarm(this.data.alarmId).subscribe(
() => { this.alarmUpdated = true; this.loadAlarm(); }
);
}
clear(): void {
this.alarmService.clearAlarm(this.data.alarmId).subscribe(
() => { this.alarmUpdated = true; this.loadAlarm(); }
);
}
}

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

@ -0,0 +1,137 @@
///
/// Copyright © 2016-2019 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 {
DateEntityTableColumn,
EntityTableColumn,
EntityTableConfig
} from '@home/models/entity/entities-table-config.models';
import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { Direction } from '@shared/models/page/sort-order';
import { MatDialog } from '@angular/material';
import { TimePageLink } from '@shared/models/page/page-link';
import { Observable } from 'rxjs';
import { PageData } from '@shared/models/page/page-data';
import { EntityId } from '@shared/models/id/entity-id';
import {
AlarmInfo,
AlarmQuery,
AlarmSearchStatus,
alarmSeverityColors,
alarmSeverityTranslations,
alarmStatusTranslations
} from '@app/shared/models/alarm.models';
import { AlarmService } from '@app/core/http/alarm.service';
import { DialogService } from '@core/services/dialog.service';
import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component';
import {
AuditLogDetailsDialogComponent,
AuditLogDetailsDialogData
} from '@home/components/audit-log/audit-log-details-dialog.component';
import {
AlarmDetailsDialogComponent,
AlarmDetailsDialogData
} from '@home/components/alarm/alarm-details-dialog.component';
export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> {
searchStatus: AlarmSearchStatus;
constructor(private alarmService: AlarmService,
private dialogService: DialogService,
private translate: TranslateService,
private datePipe: DatePipe,
private dialog: MatDialog,
public entityId: EntityId = null,
private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY) {
super();
this.loadDataOnInit = false;
this.tableTitle = '';
this.useTimePageLink = true;
this.detailsPanelEnabled = false;
this.selectionEnabled = false;
this.searchEnabled = true;
this.addEnabled = false;
this.entitiesDeleteEnabled = false;
this.actionsColumnTitle = 'alarm.details';
this.entityType = EntityType.ALARM;
this.entityTranslations = entityTypeTranslations.get(EntityType.ALARM);
this.entityResources = {
} as EntityTypeResource;
this.searchStatus = defaultSearchStatus;
this.headerComponent = AlarmTableHeaderComponent;
this.entitiesFetchFunction = pageLink => this.fetchAlarms(pageLink);
this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
this.columns.push(
new DateEntityTableColumn<AlarmInfo>('createdTime', 'alarm.created-time', this.datePipe, '150px'));
this.columns.push(
new EntityTableColumn<AlarmInfo>('originatorName', 'alarm.originator', '100%',
(entity) => entity.originatorName, entity => ({}), false));
this.columns.push(
new EntityTableColumn<AlarmInfo>('type', 'alarm.type', '100%'));
this.columns.push(
new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '100%',
(entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)),
entity => ({
fontWeight: 'bold',
color: alarmSeverityColors.get(entity.severity)
})));
this.columns.push(
new EntityTableColumn<AlarmInfo>('status', 'alarm.status', '100%',
(entity) => this.translate.instant(alarmStatusTranslations.get(entity.status))));
this.cellActionDescriptors.push(
{
name: this.translate.instant('alarm.details'),
icon: 'more_horiz',
isEnabled: () => true,
onAction: ($event, entity) => this.showAlarmDetails(entity)
}
);
}
fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> {
const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true);
return this.alarmService.getAlarms(query);
}
showAlarmDetails(entity: AlarmInfo) {
this.dialog.open<AlarmDetailsDialogComponent, AlarmDetailsDialogData, boolean>
(AlarmDetailsDialogComponent,
{
disableClose: true,
panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
data: {
alarmId: entity.id.id,
allowAcknowledgment: true,
allowClear: true,
displayDetails: true
}
}).afterClosed().subscribe(
(res) => {
if (res) {
this.table.updateData();
}
}
);
}
}

27
ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html

@ -0,0 +1,27 @@
<!--
Copyright © 2016-2019 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.
-->
<mat-form-field class="mat-block" style="width: 200px;">
<mat-label translate>alarm.alarm-status</mat-label>
<mat-select matInput [ngModel]="alarmTableConfig.searchStatus"
(ngModelChange)="searchStatusChanged($event)">
<mat-option *ngFor="let status of alarmSearchStatusTypes" [value]="status">
{{ alarmSearchStatusTranslationsMap.get(status) | translate }}
</mat-option>
</mat-select>
</mat-form-field>

35
ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss

@ -0,0 +1,35 @@
/**
* Copyright © 2016-2019 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.
*/
:host {
/* flex: 1;
display: flex;
justify-content: flex-start; */
padding-right: 8px;
}
:host ::ng-deep {
mat-form-field {
font-size: 16px;
.mat-form-field-wrapper {
padding-bottom: 0;
}
.mat-form-field-underline {
bottom: 0;
}
}
}

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

@ -0,0 +1,47 @@
///
/// Copyright © 2016-2019 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 { Component } from '@angular/core';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component';
import { AlarmInfo, AlarmSearchStatus, alarmSearchStatusTranslations } from '@shared/models/alarm.models';
import { AlarmTableConfig } from './alarm-table-config';
@Component({
selector: 'tb-alarm-table-header',
templateUrl: './alarm-table-header.component.html',
styleUrls: ['./alarm-table-header.component.scss']
})
export class AlarmTableHeaderComponent extends EntityTableHeaderComponent<AlarmInfo> {
alarmSearchStatusTranslationsMap = alarmSearchStatusTranslations;
alarmSearchStatusTypes = Object.keys(AlarmSearchStatus);
get alarmTableConfig(): AlarmTableConfig {
return this.entitiesTableConfig as AlarmTableConfig;
}
constructor(protected store: Store<AppState>) {
super(store);
}
searchStatusChanged(searchStatus: AlarmSearchStatus) {
this.alarmTableConfig.searchStatus = searchStatus;
this.alarmTableConfig.table.updateData();
}
}

18
ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html

@ -0,0 +1,18 @@
<!--
Copyright © 2016-2019 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.
-->
<tb-entities-table [entitiesTableConfig]="alarmTableConfig"></tb-entities-table>

22
ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss

@ -0,0 +1,22 @@
/**
* Copyright © 2016-2019 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.
*/
:host ::ng-deep {
tb-entities-table {
.mat-drawer-container {
background-color: white;
}
}
}

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

@ -0,0 +1,90 @@
///
/// Copyright © 2016-2019 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 { Component, Input, OnInit, ViewChild } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { DatePipe } from '@angular/common';
import { MatDialog } from '@angular/material';
import { EntityId } from '@shared/models/id/entity-id';
import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
import { Store } from '@ngrx/store';
import { AppState } from '@core/core.state';
import { DialogService } from '@core/services/dialog.service';
import { AlarmTableConfig } from './alarm-table-config';
import { AlarmSearchStatus } from '@shared/models/alarm.models';
import { AlarmService } from '@app/core/http/alarm.service';
@Component({
selector: 'tb-alarm-table',
templateUrl: './alarm-table.component.html',
styleUrls: ['./alarm-table.component.scss']
})
export class AlarmTableComponent implements OnInit {
activeValue = false;
dirtyValue = false;
entityIdValue: EntityId;
@Input()
set active(active: boolean) {
if (this.activeValue !== active) {
this.activeValue = active;
if (this.activeValue && this.dirtyValue) {
this.dirtyValue = false;
this.entitiesTable.updateData();
}
}
}
@Input()
set entityId(entityId: EntityId) {
this.entityIdValue = entityId;
if (this.alarmTableConfig && this.alarmTableConfig.entityId !== entityId) {
this.alarmTableConfig.searchStatus = AlarmSearchStatus.ANY;
this.alarmTableConfig.entityId = entityId;
this.entitiesTable.resetSortAndFilter(this.activeValue);
if (!this.activeValue) {
this.dirtyValue = true;
}
}
}
@ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent;
alarmTableConfig: AlarmTableConfig;
constructor(private alarmService: AlarmService,
private dialogService: DialogService,
private translate: TranslateService,
private datePipe: DatePipe,
private dialog: MatDialog,
private store: Store<AppState>) {
}
ngOnInit() {
this.dirtyValue = !this.activeValue;
this.alarmTableConfig = new AlarmTableConfig(
this.alarmService,
this.dialogService,
this.translate,
this.datePipe,
this.dialog,
this.entityIdValue,
AlarmSearchStatus.ANY
);
}
}

16
ui-ngx/src/app/modules/home/components/home-components.module.ts

@ -28,13 +28,18 @@ import { EventTableHeaderComponent } from '@home/components/event/event-table-he
import { EventTableComponent } from '@home/components/event/event-table.component';
import { RelationTableComponent } from '@home/components/relation/relation-table.component';
import { RelationDialogComponent } from './relation/relation-dialog.component';
import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component';
import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component';
import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component';
@NgModule({
entryComponents: [
AddEntityDialogComponent,
AuditLogDetailsDialogComponent,
EventTableHeaderComponent,
RelationDialogComponent
RelationDialogComponent,
AlarmTableHeaderComponent,
AlarmDetailsDialogComponent
],
declarations:
[
@ -48,7 +53,10 @@ import { RelationDialogComponent } from './relation/relation-dialog.component';
EventTableHeaderComponent,
EventTableComponent,
RelationTableComponent,
RelationDialogComponent
RelationDialogComponent,
AlarmTableHeaderComponent,
AlarmTableComponent,
AlarmDetailsDialogComponent
],
imports: [
CommonModule,
@ -62,7 +70,9 @@ import { RelationDialogComponent } from './relation/relation-dialog.component';
ContactComponent,
AuditLogTableComponent,
EventTableComponent,
RelationTableComponent
RelationTableComponent,
AlarmTableComponent,
AlarmDetailsDialogComponent
]
})
export class HomeComponentsModule { }

4
ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html

@ -15,6 +15,10 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
<tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'asset.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"

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

@ -15,6 +15,10 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
<tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'customer.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"

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

@ -62,7 +62,7 @@
</div>
<div class="mat-padding" fxLayout="column">
<mat-form-field class="mat-block"
[fxShow]="!isEdit && assignedCustomersText
[fxShow]="!isEdit && assignedCustomersText?.length
&& dashboardScope === 'tenant'">
<mat-label translate>dashboard.assignedToCustomers</mat-label>
<input matInput disabled [ngModel]="assignedCustomersText">

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

@ -15,6 +15,10 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
<tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'device.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"

4
ui-ngx/src/app/modules/home/pages/entity-view/entity-view-tabs.component.html

@ -15,6 +15,10 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
<tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"

4
ui-ngx/src/app/modules/home/pages/rulechain/rulechain-tabs.component.html

@ -15,6 +15,10 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
<tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive"

4
ui-ngx/src/app/modules/home/pages/tenant/tenant-tabs.component.html

@ -15,6 +15,10 @@
limitations under the License.
-->
<mat-tab *ngIf="entity"
label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
<tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
</mat-tab>
<mat-tab *ngIf="entity"
label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid"

1
ui-ngx/src/app/shared/components/dialog.component.ts

@ -42,7 +42,6 @@ export abstract class DialogComponent<T, R = any> extends PageComponent implemen
}
ngOnDestroy(): void {
console.log('Dialog destroy called');
super.ngOnDestroy();
if (this.routerSubscription) {
this.routerSubscription.unsubscribe();

87
ui-ngx/src/app/shared/models/alarm.models.ts

@ -20,6 +20,8 @@ import {TenantId} from '@shared/models/id/tenant-id';
import {CustomerId} from '@shared/models/id/customer-id';
import {AlarmId} from '@shared/models/id/alarm-id';
import {EntityId} from '@shared/models/id/entity-id';
import { ActionStatus } from '@shared/models/audit-log.models';
import { TimePageLink } from '@shared/models/page/page-link';
export enum AlarmSeverity {
CRITICAL = 'CRITICAL',
@ -36,6 +38,53 @@ export enum AlarmStatus {
CLEARED_ACK = 'CLEARED_ACK'
}
export enum AlarmSearchStatus {
ANY = 'ANY',
ACTIVE = 'ACTIVE',
CLEARED = 'CLEARED',
ACK = 'ACK',
UNACK = 'UNACK'
}
export const alarmSeverityTranslations = new Map<AlarmSeverity, string>(
[
[AlarmSeverity.CRITICAL, 'alarm.severity-critical'],
[AlarmSeverity.MAJOR, 'alarm.severity-major'],
[AlarmSeverity.MINOR, 'alarm.severity-minor'],
[AlarmSeverity.WARNING, 'alarm.severity-warning'],
[AlarmSeverity.INDETERMINATE, 'alarm.severity-indeterminate']
]
);
export const alarmStatusTranslations = new Map<AlarmStatus, string>(
[
[AlarmStatus.ACTIVE_UNACK, 'alarm.display-status.ACTIVE_UNACK'],
[AlarmStatus.ACTIVE_ACK, 'alarm.display-status.ACTIVE_ACK'],
[AlarmStatus.CLEARED_UNACK, 'alarm.display-status.CLEARED_UNACK'],
[AlarmStatus.CLEARED_ACK, 'alarm.display-status.CLEARED_ACK'],
]
);
export const alarmSearchStatusTranslations = new Map<AlarmSearchStatus, string>(
[
[AlarmSearchStatus.ANY, 'alarm.search-status.ANY'],
[AlarmSearchStatus.ACTIVE, 'alarm.search-status.ACTIVE'],
[AlarmSearchStatus.CLEARED, 'alarm.search-status.CLEARED'],
[AlarmSearchStatus.ACK, 'alarm.search-status.ACK'],
[AlarmSearchStatus.UNACK, 'alarm.search-status.UNACK']
]
);
export const alarmSeverityColors = new Map<AlarmSeverity, string>(
[
[AlarmSeverity.CRITICAL, 'red'],
[AlarmSeverity.MAJOR, 'orange'],
[AlarmSeverity.MINOR, '#ffca3d'],
[AlarmSeverity.WARNING, '#abab00'],
[AlarmSeverity.INDETERMINATE, 'green']
]
);
export interface Alarm extends BaseData<AlarmId> {
tenantId: TenantId;
type: string;
@ -49,3 +98,41 @@ export interface Alarm extends BaseData<AlarmId> {
propagate: boolean;
details?: any;
}
export interface AlarmInfo extends Alarm {
originatorName: string;
}
export class AlarmQuery {
affectedEntityId: EntityId;
pageLink: TimePageLink;
searchStatus: AlarmSearchStatus;
status: AlarmStatus;
fetchOriginator: boolean;
constructor(entityId: EntityId, pageLink: TimePageLink,
searchStatus: AlarmSearchStatus, status: AlarmStatus,
fetchOriginator: boolean) {
this.affectedEntityId = entityId;
this.pageLink = pageLink;
this.searchStatus = searchStatus;
this.status = status;
this.fetchOriginator = fetchOriginator;
}
public toQuery(): string {
let query = `/${this.affectedEntityId.entityType}/${this.affectedEntityId.id}`;
query += this.pageLink.toQuery();
if (this.searchStatus) {
query += `&searchStatus=${this.searchStatus}`;
} else if (this.status) {
query += `&status=${this.status}`;
}
if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) {
query += `&fetchOriginator=${this.fetchOriginator}`;
}
return query;
}
}

13
ui-ngx/src/app/shared/models/entity-type.models.ts

@ -165,6 +165,19 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
selectedEntities: 'dashboard.selected-dashboards'
}
],
[
EntityType.ALARM,
{
type: 'entity.type-alarm',
typePlural: 'entity.type-alarms',
list: 'entity.list-of-alarms',
nameStartsWith: 'entity.alarm-name-starts-with',
details: 'dashboard.dashboard-details',
noEntities: 'alarm.no-alarms-prompt',
search: 'alarm.search',
selectedEntities: 'alarm.selected-alarms'
}
],
[
EntityType.WIDGETS_BUNDLE,
{

Loading…
Cancel
Save