diff --git a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java b/application/src/main/java/org/thingsboard/server/controller/AlarmController.java index 620e7dc104..89896d38ef 100644 --- a/application/src/main/java/org/thingsboard/server/controller/AlarmController.java +++ b/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); diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java b/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java index 48bcd2c8d3..76f23ea910 100644 --- a/common/data/src/main/java/org/thingsboard/server/common/data/alarm/AlarmInfo.java +++ b/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; } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java index c9b996cba7..f8036ec763 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java +++ b/dao/src/main/java/org/thingsboard/server/dao/alarm/AlarmDao.java @@ -40,5 +40,5 @@ public interface AlarmDao extends Dao { Alarm save(TenantId tenantId, Alarm alarm); - ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query); + PageData findAlarms(TenantId tenantId, AlarmQuery query); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java b/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java index 83ef00c6af..8fdcf39e4b 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/alarm/BaseAlarmService.java +++ b/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> findAlarms(TenantId tenantId, AlarmQuery query) { - ListenableFuture> alarms = alarmDao.findAlarms(tenantId, query); + PageData alarms = alarmDao.findAlarms(tenantId, query); if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { - alarms = Futures.transformAsync(alarms, input -> { - List> 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> 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 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 alarms = alarmDao.findAlarms(tenantId, query); if (alarms.hasNext()) { nextPageLink = nextPageLink.nextPageLink(); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractAlarmEntity.java new file mode 100644 index 0000000000..7f281c777b --- /dev/null +++ b/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 extends BaseSqlEntity implements BaseEntity { + + @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; + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java index b023f127de..4ee62a1413 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java +++ b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AbstractDeviceEntity.java @@ -83,7 +83,7 @@ public abstract class AbstractDeviceEntity 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(); diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java index f6cfb7189c..a0655cd507 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmEntity.java +++ b/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 implements BaseEntity { - - @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 { 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(); } - -} \ No newline at end of file +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/sql/AlarmInfoEntity.java new file mode 100644 index 0000000000..0802121416 --- /dev/null +++ b/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 { + + private String originatorName; + + public AlarmInfoEntity() { + super(); + } + + public AlarmInfoEntity(AlarmEntity alarmEntity) { + super(alarmEntity); + } + + @Override + public AlarmInfo toData() { + return new AlarmInfo(super.toAlarm(), this.originatorName); + } +} diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java index a3d5b45a39..9f843ad069 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/AlarmRepository.java +++ b/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 { @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 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); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java b/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java index 5f4259dd8f..5ec7ab364a 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/sql/alarm/JpaAlarmDao.java +++ b/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 implements A } @Override - public ListenableFuture> findAlarms(TenantId tenantId, AlarmQuery query) { + public PageData 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 implements A searchStatusName = query.getStatus().name(); } String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName; - ListenableFuture> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink()); - return Futures.transformAsync(relations, input -> { - List> 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()) + ) + ); } } diff --git a/ui-ngx/src/app/core/http/alarm.service.ts b/ui-ngx/src/app/core/http/alarm.service.ts new file mode 100644 index 0000000000..bdc2ed7950 --- /dev/null +++ b/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 { + return this.http.get(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.get(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + return this.http.post(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getAlarms(query: AlarmQuery, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable> { + return this.http.get>(`/api/alarm${query.toQuery()}`, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } + + public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus, + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable { + let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`; + if (alarmSearchStatus) { + url += `?searchStatus=${alarmSearchStatus}`; + } else if (alarmStatus) { + url += `?status=${alarmStatus}`; + } + return this.http.get(url, + defaultHttpOptions(ignoreLoading, ignoreErrors)); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html new file mode 100644 index 0000000000..7d0f82b39c --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.html @@ -0,0 +1,123 @@ + +
+ +

{{ 'alarm.alarm-details' | translate }}

+ + +
+ + +
+
+
+
+ + alarm.created-time + + + + alarm.originator + + +
+
+ + alarm.start-time + + + + alarm.end-time + + + +
+
+ + alarm.ack-time + + + + alarm.clear-time + + + +
+
+ + alarm.type + + + + alarm.severity + + + + alarm.status + + +
+ + +
+
+
+
+ + +
+ + +
+
diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-details-dialog.component.ts new file mode 100644 index 0000000000..8d8efcd472 --- /dev/null +++ b/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 implements OnInit { + + alarmFormGroup: FormGroup; + + allowAcknowledgment: boolean; + allowClear: boolean; + displayDetails: boolean; + + loadAlarmSubject = new Subject(); + alarm$: Observable = this.loadAlarmSubject.asObservable().pipe( + tap(alarm => this.loadAlarmFields(alarm)), + share() + ); + + alarmSeverityColorsMap = alarmSeverityColors; + alarmStatuses = AlarmStatus; + + alarmUpdated = false; + + constructor(protected store: Store, + protected router: Router, + private datePipe: DatePipe, + private translate: TranslateService, + @Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData, + private alarmService: AlarmService, + public dialogRef: MatDialogRef, + 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(); } + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-config.ts new file mode 100644 index 0000000000..632cd8ecd3 --- /dev/null +++ b/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 { + + 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('createdTime', 'alarm.created-time', this.datePipe, '150px')); + this.columns.push( + new EntityTableColumn('originatorName', 'alarm.originator', '100%', + (entity) => entity.originatorName, entity => ({}), false)); + this.columns.push( + new EntityTableColumn('type', 'alarm.type', '100%')); + this.columns.push( + new EntityTableColumn('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('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> { + const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true); + return this.alarmService.getAlarms(query); + } + + showAlarmDetails(entity: AlarmInfo) { + this.dialog.open + (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(); + } + } + ); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html new file mode 100644 index 0000000000..e30afb1baf --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.html @@ -0,0 +1,27 @@ + + + + alarm.alarm-status + + + {{ alarmSearchStatusTranslationsMap.get(status) | translate }} + + + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.scss new file mode 100644 index 0000000000..8f552a7e43 --- /dev/null +++ b/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; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table-header.component.ts new file mode 100644 index 0000000000..6f075f8517 --- /dev/null +++ b/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 { + + alarmSearchStatusTranslationsMap = alarmSearchStatusTranslations; + + alarmSearchStatusTypes = Object.keys(AlarmSearchStatus); + + get alarmTableConfig(): AlarmTableConfig { + return this.entitiesTableConfig as AlarmTableConfig; + } + + constructor(protected store: Store) { + super(store); + } + + searchStatusChanged(searchStatus: AlarmSearchStatus) { + this.alarmTableConfig.searchStatus = searchStatus; + this.alarmTableConfig.table.updateData(); + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html new file mode 100644 index 0000000000..9421e1c767 --- /dev/null +++ b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.html @@ -0,0 +1,18 @@ + + diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.scss new file mode 100644 index 0000000000..0d3cc92455 --- /dev/null +++ b/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; + } + } +} diff --git a/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts b/ui-ngx/src/app/modules/home/components/alarm/alarm-table.component.ts new file mode 100644 index 0000000000..cbd749631e --- /dev/null +++ b/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) { + } + + ngOnInit() { + this.dirtyValue = !this.activeValue; + this.alarmTableConfig = new AlarmTableConfig( + this.alarmService, + this.dialogService, + this.translate, + this.datePipe, + this.dialog, + this.entityIdValue, + AlarmSearchStatus.ANY + ); + } + +} diff --git a/ui-ngx/src/app/modules/home/components/home-components.module.ts b/ui-ngx/src/app/modules/home/components/home-components.module.ts index 3cf870f96d..61c2c0f458 100644 --- a/ui-ngx/src/app/modules/home/components/home-components.module.ts +++ b/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 { } diff --git a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html index 85e41ac114..7b5ff69b80 100644 --- a/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/asset/asset-tabs.component.html @@ -15,6 +15,10 @@ limitations under the License. --> + + + + + +
dashboard.assignedToCustomers diff --git a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html index cdcc19e08c..84ecb1bfab 100644 --- a/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html +++ b/ui-ngx/src/app/modules/home/pages/device/device-tabs.component.html @@ -15,6 +15,10 @@ limitations under the License. --> + + + + + + + + + + + + extends PageComponent implemen } ngOnDestroy(): void { - console.log('Dialog destroy called'); super.ngOnDestroy(); if (this.routerSubscription) { this.routerSubscription.unsubscribe(); diff --git a/ui-ngx/src/app/shared/models/alarm.models.ts b/ui-ngx/src/app/shared/models/alarm.models.ts index 8fbd24de3b..4265041835 100644 --- a/ui-ngx/src/app/shared/models/alarm.models.ts +++ b/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.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.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.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.CRITICAL, 'red'], + [AlarmSeverity.MAJOR, 'orange'], + [AlarmSeverity.MINOR, '#ffca3d'], + [AlarmSeverity.WARNING, '#abab00'], + [AlarmSeverity.INDETERMINATE, 'green'] + ] +); + export interface Alarm extends BaseData { tenantId: TenantId; type: string; @@ -49,3 +98,41 @@ export interface Alarm extends BaseData { 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; + } + +} diff --git a/ui-ngx/src/app/shared/models/entity-type.models.ts b/ui-ngx/src/app/shared/models/entity-type.models.ts index 1d8b72cb0f..598fa983a1 100644 --- a/ui-ngx/src/app/shared/models/entity-type.models.ts +++ b/ui-ngx/src/app/shared/models/entity-type.models.ts @@ -165,6 +165,19 @@ export const entityTypeTranslations = new Map