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(required = false) String status,
@RequestParam int pageSize, @RequestParam int pageSize,
@RequestParam int page, @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 startTime,
@RequestParam(required = false) Long endTime, @RequestParam(required = false) Long endTime,
@RequestParam(required = false, defaultValue = "false") boolean ascOrder,
@RequestParam(required = false) Boolean fetchOriginator @RequestParam(required = false) Boolean fetchOriginator
) throws ThingsboardException { ) throws ThingsboardException {
checkParameter("EntityId", strEntityId); 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); "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
} }
checkEntityId(entityId, Operation.READ); checkEntityId(entityId, Operation.READ);
TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
try { 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()); return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
} catch (Exception e) { } catch (Exception e) {
throw handleException(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); super(alarm);
} }
public AlarmInfo(Alarm alarm, String originatorName) {
super(alarm);
this.originatorName = originatorName;
}
public String getOriginatorName() { public String getOriginatorName() {
return originatorName; 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); 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 @Override
public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) { 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()) { if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
alarms = Futures.transformAsync(alarms, input -> { List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size());
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.getData().size()); for (AlarmInfo alarmInfo : alarms.getData()) {
for (AlarmInfo alarmInfo : input.getData()) { alarmFutures.add(Futures.transform(
alarmFutures.add(Futures.transform( entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { if (originatorName == null) {
if (originatorName == null) { originatorName = "Deleted";
originatorName = "Deleted";
}
alarmInfo.setOriginatorName(originatorName);
return alarmInfo;
} }
)); alarmInfo.setOriginatorName(originatorName);
} return alarmInfo;
return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> { }
return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext()); ));
}); }
return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
return new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), alarms.hasNext());
}); });
} }
return alarms; return Futures.immediateFuture(alarms);
} }
@Override @Override
@ -293,14 +291,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
AlarmQuery query; AlarmQuery query;
while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) { while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) {
query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false); query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false);
PageData<AlarmInfo> alarms; PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
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);
}
if (alarms.hasNext()) { if (alarms.hasNext()) {
nextPageLink = nextPageLink.nextPageLink(); 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) { public AbstractDeviceEntity(DeviceEntity deviceEntity) {
this.setId(deviceEntity.getId());; this.setId(deviceEntity.getId());
this.tenantId = deviceEntity.getTenantId(); this.tenantId = deviceEntity.getTenantId();
this.customerId = deviceEntity.getCustomerId(); this.customerId = deviceEntity.getCustomerId();
this.type = deviceEntity.getType(); 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; 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.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef; 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.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 org.thingsboard.server.dao.util.mapping.JsonStringType;
import javax.persistence.Column;
import javax.persistence.Entity; import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table; 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_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 @Data
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Entity @Entity
@TypeDef(name = "json", typeClass = JsonStringType.class) @TypeDef(name = "json", typeClass = JsonStringType.class)
@Table(name = ALARM_COLUMN_FAMILY_NAME) @Table(name = ALARM_COLUMN_FAMILY_NAME)
public final class AlarmEntity extends BaseSqlEntity<Alarm> implements BaseEntity<Alarm> { public final class AlarmEntity extends AbstractAlarmEntity<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 AlarmEntity() { public AlarmEntity() {
super(); super();
} }
public AlarmEntity(Alarm alarm) { public AlarmEntity(Alarm alarm) {
if (alarm.getId() != null) { super(alarm);
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();
} }
@Override @Override
public Alarm toData() { public Alarm toData() {
Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id))); return super.toAlarm();
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;
} }
}
}

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; package org.thingsboard.server.dao.sql.alarm;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository; import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
import org.thingsboard.server.common.data.EntityType; import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.dao.model.sql.AlarmEntity; import org.thingsboard.server.dao.model.sql.AlarmEntity;
import org.thingsboard.server.dao.model.sql.AlarmInfoEntity;
import org.thingsboard.server.dao.util.SqlDao; import org.thingsboard.server.dao.util.SqlDao;
import java.util.List; import java.util.List;
@ -38,4 +40,26 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
@Param("entityType") EntityType entityType, @Param("entityType") EntityType entityType,
@Param("alarmType") String alarmType, @Param("alarmType") String alarmType,
Pageable pageable); 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.ArrayList;
import java.util.List; import java.util.List;
import java.util.Objects;
import java.util.UUID; 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. * Created by Valerii Sosliuk on 5/19/2017.
*/ */
@ -93,7 +98,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
} }
@Override @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()); log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink());
EntityId affectedEntity = query.getAffectedEntityId(); EntityId affectedEntity = query.getAffectedEntityId();
String searchStatusName; String searchStatusName;
@ -105,17 +110,18 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
searchStatusName = query.getStatus().name(); searchStatusName = query.getStatus().name();
} }
String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName; 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 -> { return DaoUtil.toPageData(
List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.getData().size()); alarmRepository.findAlarms(
for (EntityRelation relation : input.getData()) { fromTimeUUID(tenantId.getId()),
alarmFutures.add(Futures.transform( fromTimeUUID(affectedEntity.getId()),
findAlarmByIdAsync(tenantId, relation.getTo().getId()), affectedEntity.getEntityType().name(),
AlarmInfo::new)); relationType,
} startTimeToId(query.getPageLink().getStartTime()),
return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> { endTimeToId(query.getPageLink().getEndTime()),
return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext()); 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 { EventTableComponent } from '@home/components/event/event-table.component';
import { RelationTableComponent } from '@home/components/relation/relation-table.component'; import { RelationTableComponent } from '@home/components/relation/relation-table.component';
import { RelationDialogComponent } from './relation/relation-dialog.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({ @NgModule({
entryComponents: [ entryComponents: [
AddEntityDialogComponent, AddEntityDialogComponent,
AuditLogDetailsDialogComponent, AuditLogDetailsDialogComponent,
EventTableHeaderComponent, EventTableHeaderComponent,
RelationDialogComponent RelationDialogComponent,
AlarmTableHeaderComponent,
AlarmDetailsDialogComponent
], ],
declarations: declarations:
[ [
@ -48,7 +53,10 @@ import { RelationDialogComponent } from './relation/relation-dialog.component';
EventTableHeaderComponent, EventTableHeaderComponent,
EventTableComponent, EventTableComponent,
RelationTableComponent, RelationTableComponent,
RelationDialogComponent RelationDialogComponent,
AlarmTableHeaderComponent,
AlarmTableComponent,
AlarmDetailsDialogComponent
], ],
imports: [ imports: [
CommonModule, CommonModule,
@ -62,7 +70,9 @@ import { RelationDialogComponent } from './relation/relation-dialog.component';
ContactComponent, ContactComponent,
AuditLogTableComponent, AuditLogTableComponent,
EventTableComponent, EventTableComponent,
RelationTableComponent RelationTableComponent,
AlarmTableComponent,
AlarmDetailsDialogComponent
] ]
}) })
export class HomeComponentsModule { } export class HomeComponentsModule { }

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

@ -15,6 +15,10 @@
limitations under the License. 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" <mat-tab *ngIf="entity"
label="{{ 'asset.events' | translate }}" #eventsTab="matTab"> label="{{ 'asset.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" <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. 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" <mat-tab *ngIf="entity"
label="{{ 'customer.events' | translate }}" #eventsTab="matTab"> label="{{ 'customer.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" <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>
<div class="mat-padding" fxLayout="column"> <div class="mat-padding" fxLayout="column">
<mat-form-field class="mat-block" <mat-form-field class="mat-block"
[fxShow]="!isEdit && assignedCustomersText [fxShow]="!isEdit && assignedCustomersText?.length
&& dashboardScope === 'tenant'"> && dashboardScope === 'tenant'">
<mat-label translate>dashboard.assignedToCustomers</mat-label> <mat-label translate>dashboard.assignedToCustomers</mat-label>
<input matInput disabled [ngModel]="assignedCustomersText"> <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. 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" <mat-tab *ngIf="entity"
label="{{ 'device.events' | translate }}" #eventsTab="matTab"> label="{{ 'device.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" <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. 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" <mat-tab *ngIf="entity"
label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab"> label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id" <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. 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" <mat-tab *ngIf="entity"
label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab"> label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" <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. 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" <mat-tab *ngIf="entity"
label="{{ 'tenant.events' | translate }}" #eventsTab="matTab"> label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
<tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid" <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 { ngOnDestroy(): void {
console.log('Dialog destroy called');
super.ngOnDestroy(); super.ngOnDestroy();
if (this.routerSubscription) { if (this.routerSubscription) {
this.routerSubscription.unsubscribe(); 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 {CustomerId} from '@shared/models/id/customer-id';
import {AlarmId} from '@shared/models/id/alarm-id'; import {AlarmId} from '@shared/models/id/alarm-id';
import {EntityId} from '@shared/models/id/entity-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 { export enum AlarmSeverity {
CRITICAL = 'CRITICAL', CRITICAL = 'CRITICAL',
@ -36,6 +38,53 @@ export enum AlarmStatus {
CLEARED_ACK = 'CLEARED_ACK' 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> { export interface Alarm extends BaseData<AlarmId> {
tenantId: TenantId; tenantId: TenantId;
type: string; type: string;
@ -49,3 +98,41 @@ export interface Alarm extends BaseData<AlarmId> {
propagate: boolean; propagate: boolean;
details?: any; 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' 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, EntityType.WIDGETS_BUNDLE,
{ {

Loading…
Cancel
Save