Commit e91fd302d6c6a9b598f17b18f1d470f172099758

Authored by Igor Kulikov
1 parent 52ab3d6d

Implement alarms table

Showing 31 changed files with 1141 additions and 151 deletions
... ... @@ -150,9 +150,11 @@ public class AlarmController extends BaseController {
150 150 @RequestParam(required = false) String status,
151 151 @RequestParam int pageSize,
152 152 @RequestParam int page,
  153 + @RequestParam(required = false) String textSearch,
  154 + @RequestParam(required = false) String sortProperty,
  155 + @RequestParam(required = false) String sortOrder,
153 156 @RequestParam(required = false) Long startTime,
154 157 @RequestParam(required = false) Long endTime,
155   - @RequestParam(required = false, defaultValue = "false") boolean ascOrder,
156 158 @RequestParam(required = false) Boolean fetchOriginator
157 159 ) throws ThingsboardException {
158 160 checkParameter("EntityId", strEntityId);
... ... @@ -165,9 +167,8 @@ public class AlarmController extends BaseController {
165 167 "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS);
166 168 }
167 169 checkEntityId(entityId, Operation.READ);
  170 + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime);
168 171 try {
169   - TimePageLink pageLink = createTimePageLink(pageSize, page, "",
170   - "id", ascOrder ? "asc" : "desc", startTime, endTime);
171 172 return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(entityId, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get());
172 173 } catch (Exception e) {
173 174 throw handleException(e);
... ...
... ... @@ -29,6 +29,11 @@ public class AlarmInfo extends Alarm {
29 29 super(alarm);
30 30 }
31 31
  32 + public AlarmInfo(Alarm alarm, String originatorName) {
  33 + super(alarm);
  34 + this.originatorName = originatorName;
  35 + }
  36 +
32 37 public String getOriginatorName() {
33 38 return originatorName;
34 39 }
... ...
... ... @@ -40,5 +40,5 @@ public interface AlarmDao extends Dao<Alarm> {
40 40
41 41 Alarm save(TenantId tenantId, Alarm alarm);
42 42
43   - ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query);
  43 + PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query);
44 44 }
... ...
... ... @@ -261,27 +261,25 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
261 261
262 262 @Override
263 263 public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {
264   - ListenableFuture<PageData<AlarmInfo>> alarms = alarmDao.findAlarms(tenantId, query);
  264 + PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
265 265 if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) {
266   - alarms = Futures.transformAsync(alarms, input -> {
267   - List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.getData().size());
268   - for (AlarmInfo alarmInfo : input.getData()) {
269   - alarmFutures.add(Futures.transform(
270   - entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
271   - if (originatorName == null) {
272   - originatorName = "Deleted";
273   - }
274   - alarmInfo.setOriginatorName(originatorName);
275   - return alarmInfo;
  266 + List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size());
  267 + for (AlarmInfo alarmInfo : alarms.getData()) {
  268 + alarmFutures.add(Futures.transform(
  269 + entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> {
  270 + if (originatorName == null) {
  271 + originatorName = "Deleted";
276 272 }
277   - ));
278   - }
279   - return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
280   - return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext());
281   - });
  273 + alarmInfo.setOriginatorName(originatorName);
  274 + return alarmInfo;
  275 + }
  276 + ));
  277 + }
  278 + return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
  279 + return new PageData(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), alarms.hasNext());
282 280 });
283 281 }
284   - return alarms;
  282 + return Futures.immediateFuture(alarms);
285 283 }
286 284
287 285 @Override
... ... @@ -293,14 +291,7 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ
293 291 AlarmQuery query;
294 292 while (hasNext && AlarmSeverity.CRITICAL != highestSeverity) {
295 293 query = new AlarmQuery(entityId, nextPageLink, alarmSearchStatus, alarmStatus, false);
296   - PageData<AlarmInfo> alarms;
297   - try {
298   - alarms = alarmDao.findAlarms(tenantId, query).get();
299   - } catch (ExecutionException | InterruptedException e) {
300   - log.warn("Failed to find highest alarm severity. EntityId: [{}], AlarmSearchStatus: [{}], AlarmStatus: [{}]",
301   - entityId, alarmSearchStatus, alarmStatus);
302   - throw new RuntimeException(e);
303   - }
  294 + PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query);
304 295 if (alarms.hasNext()) {
305 296 nextPageLink = nextPageLink.nextPageLink();
306 297 }
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import com.datastax.driver.core.utils.UUIDs;
  19 +import com.fasterxml.jackson.databind.JsonNode;
  20 +import lombok.Data;
  21 +import lombok.EqualsAndHashCode;
  22 +import org.hibernate.annotations.Type;
  23 +import org.hibernate.annotations.TypeDef;
  24 +import org.thingsboard.server.common.data.EntityType;
  25 +import org.thingsboard.server.common.data.UUIDConverter;
  26 +import org.thingsboard.server.common.data.alarm.Alarm;
  27 +import org.thingsboard.server.common.data.alarm.AlarmId;
  28 +import org.thingsboard.server.common.data.alarm.AlarmSeverity;
  29 +import org.thingsboard.server.common.data.alarm.AlarmStatus;
  30 +import org.thingsboard.server.common.data.id.EntityIdFactory;
  31 +import org.thingsboard.server.common.data.id.TenantId;
  32 +import org.thingsboard.server.dao.model.BaseEntity;
  33 +import org.thingsboard.server.dao.model.BaseSqlEntity;
  34 +import org.thingsboard.server.dao.model.ModelConstants;
  35 +import org.thingsboard.server.dao.util.mapping.JsonStringType;
  36 +
  37 +import javax.persistence.*;
  38 +
  39 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
  40 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
  41 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;
  42 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY;
  43 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;
  44 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
  45 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY;
  46 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY;
  47 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY;
  48 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY;
  49 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY;
  50 +import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY;
  51 +
  52 +@Data
  53 +@EqualsAndHashCode(callSuper = true)
  54 +@TypeDef(name = "json", typeClass = JsonStringType.class)
  55 +@MappedSuperclass
  56 +public abstract class AbstractAlarmEntity<T extends Alarm> extends BaseSqlEntity<T> implements BaseEntity<T> {
  57 +
  58 + @Column(name = ALARM_TENANT_ID_PROPERTY)
  59 + private String tenantId;
  60 +
  61 + @Column(name = ALARM_ORIGINATOR_ID_PROPERTY)
  62 + private String originatorId;
  63 +
  64 + @Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY)
  65 + private EntityType originatorType;
  66 +
  67 + @Column(name = ALARM_TYPE_PROPERTY)
  68 + private String type;
  69 +
  70 + @Enumerated(EnumType.STRING)
  71 + @Column(name = ALARM_SEVERITY_PROPERTY)
  72 + private AlarmSeverity severity;
  73 +
  74 + @Enumerated(EnumType.STRING)
  75 + @Column(name = ALARM_STATUS_PROPERTY)
  76 + private AlarmStatus status;
  77 +
  78 + @Column(name = ALARM_START_TS_PROPERTY)
  79 + private Long startTs;
  80 +
  81 + @Column(name = ALARM_END_TS_PROPERTY)
  82 + private Long endTs;
  83 +
  84 + @Column(name = ALARM_ACK_TS_PROPERTY)
  85 + private Long ackTs;
  86 +
  87 + @Column(name = ALARM_CLEAR_TS_PROPERTY)
  88 + private Long clearTs;
  89 +
  90 + @Type(type = "json")
  91 + @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
  92 + private JsonNode details;
  93 +
  94 + @Column(name = ALARM_PROPAGATE_PROPERTY)
  95 + private Boolean propagate;
  96 +
  97 + public AbstractAlarmEntity() {
  98 + super();
  99 + }
  100 +
  101 + public AbstractAlarmEntity(Alarm alarm) {
  102 + if (alarm.getId() != null) {
  103 + this.setId(alarm.getId().getId());
  104 + }
  105 + if (alarm.getTenantId() != null) {
  106 + this.tenantId = UUIDConverter.fromTimeUUID(alarm.getTenantId().getId());
  107 + }
  108 + this.type = alarm.getType();
  109 + this.originatorId = UUIDConverter.fromTimeUUID(alarm.getOriginator().getId());
  110 + this.originatorType = alarm.getOriginator().getEntityType();
  111 + this.type = alarm.getType();
  112 + this.severity = alarm.getSeverity();
  113 + this.status = alarm.getStatus();
  114 + this.propagate = alarm.isPropagate();
  115 + this.startTs = alarm.getStartTs();
  116 + this.endTs = alarm.getEndTs();
  117 + this.ackTs = alarm.getAckTs();
  118 + this.clearTs = alarm.getClearTs();
  119 + this.details = alarm.getDetails();
  120 + }
  121 +
  122 + public AbstractAlarmEntity(AlarmEntity alarmEntity) {
  123 + this.setId(alarmEntity.getId());
  124 + this.tenantId = alarmEntity.getTenantId();
  125 + this.type = alarmEntity.getType();
  126 + this.originatorId = alarmEntity.getOriginatorId();
  127 + this.originatorType = alarmEntity.getOriginatorType();
  128 + this.type = alarmEntity.getType();
  129 + this.severity = alarmEntity.getSeverity();
  130 + this.status = alarmEntity.getStatus();
  131 + this.propagate = alarmEntity.getPropagate();
  132 + this.startTs = alarmEntity.getStartTs();
  133 + this.endTs = alarmEntity.getEndTs();
  134 + this.ackTs = alarmEntity.getAckTs();
  135 + this.clearTs = alarmEntity.getClearTs();
  136 + this.details = alarmEntity.getDetails();
  137 + }
  138 +
  139 + protected Alarm toAlarm() {
  140 + Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id)));
  141 + alarm.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id)));
  142 + if (tenantId != null) {
  143 + alarm.setTenantId(new TenantId(UUIDConverter.fromString(tenantId)));
  144 + }
  145 + alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, UUIDConverter.fromString(originatorId)));
  146 + alarm.setType(type);
  147 + alarm.setSeverity(severity);
  148 + alarm.setStatus(status);
  149 + alarm.setPropagate(propagate);
  150 + alarm.setStartTs(startTs);
  151 + alarm.setEndTs(endTs);
  152 + alarm.setAckTs(ackTs);
  153 + alarm.setClearTs(clearTs);
  154 + alarm.setDetails(details);
  155 + return alarm;
  156 + }
  157 +}
... ...
... ... @@ -83,7 +83,7 @@ public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEnti
83 83 }
84 84
85 85 public AbstractDeviceEntity(DeviceEntity deviceEntity) {
86   - this.setId(deviceEntity.getId());;
  86 + this.setId(deviceEntity.getId());
87 87 this.tenantId = deviceEntity.getTenantId();
88 88 this.customerId = deviceEntity.getCustomerId();
89 89 this.type = deviceEntity.getType();
... ...
... ... @@ -15,133 +15,34 @@
15 15 */
16 16 package org.thingsboard.server.dao.model.sql;
17 17
18   -import com.datastax.driver.core.utils.UUIDs;
19   -import com.fasterxml.jackson.databind.JsonNode;
20 18 import lombok.Data;
21 19 import lombok.EqualsAndHashCode;
22   -import org.hibernate.annotations.Type;
23 20 import org.hibernate.annotations.TypeDef;
24   -import org.thingsboard.server.common.data.EntityType;
25   -import org.thingsboard.server.common.data.UUIDConverter;
26 21 import org.thingsboard.server.common.data.alarm.Alarm;
27   -import org.thingsboard.server.common.data.alarm.AlarmId;
28   -import org.thingsboard.server.common.data.alarm.AlarmSeverity;
29   -import org.thingsboard.server.common.data.alarm.AlarmStatus;
30   -import org.thingsboard.server.common.data.id.EntityIdFactory;
31   -import org.thingsboard.server.common.data.id.TenantId;
32   -import org.thingsboard.server.dao.model.BaseEntity;
33   -import org.thingsboard.server.dao.model.BaseSqlEntity;
34   -import org.thingsboard.server.dao.model.ModelConstants;
35 22 import org.thingsboard.server.dao.util.mapping.JsonStringType;
36 23
37   -import javax.persistence.Column;
38 24 import javax.persistence.Entity;
39   -import javax.persistence.EnumType;
40   -import javax.persistence.Enumerated;
41 25 import javax.persistence.Table;
42 26
43   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ACK_TS_PROPERTY;
44   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_CLEAR_TS_PROPERTY;
45 27 import static org.thingsboard.server.dao.model.ModelConstants.ALARM_COLUMN_FAMILY_NAME;
46   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_END_TS_PROPERTY;
47   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_ID_PROPERTY;
48   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_ORIGINATOR_TYPE_PROPERTY;
49   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_PROPAGATE_PROPERTY;
50   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_SEVERITY_PROPERTY;
51   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_START_TS_PROPERTY;
52   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_STATUS_PROPERTY;
53   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TENANT_ID_PROPERTY;
54   -import static org.thingsboard.server.dao.model.ModelConstants.ALARM_TYPE_PROPERTY;
55 28
56 29 @Data
57 30 @EqualsAndHashCode(callSuper = true)
58 31 @Entity
59 32 @TypeDef(name = "json", typeClass = JsonStringType.class)
60 33 @Table(name = ALARM_COLUMN_FAMILY_NAME)
61   -public final class AlarmEntity extends BaseSqlEntity<Alarm> implements BaseEntity<Alarm> {
62   -
63   - @Column(name = ALARM_TENANT_ID_PROPERTY)
64   - private String tenantId;
65   -
66   - @Column(name = ALARM_ORIGINATOR_ID_PROPERTY)
67   - private String originatorId;
68   -
69   - @Column(name = ALARM_ORIGINATOR_TYPE_PROPERTY)
70   - private EntityType originatorType;
71   -
72   - @Column(name = ALARM_TYPE_PROPERTY)
73   - private String type;
74   -
75   - @Enumerated(EnumType.STRING)
76   - @Column(name = ALARM_SEVERITY_PROPERTY)
77   - private AlarmSeverity severity;
78   -
79   - @Enumerated(EnumType.STRING)
80   - @Column(name = ALARM_STATUS_PROPERTY)
81   - private AlarmStatus status;
82   -
83   - @Column(name = ALARM_START_TS_PROPERTY)
84   - private Long startTs;
85   -
86   - @Column(name = ALARM_END_TS_PROPERTY)
87   - private Long endTs;
88   -
89   - @Column(name = ALARM_ACK_TS_PROPERTY)
90   - private Long ackTs;
91   -
92   - @Column(name = ALARM_CLEAR_TS_PROPERTY)
93   - private Long clearTs;
94   -
95   - @Type(type = "json")
96   - @Column(name = ModelConstants.ASSET_ADDITIONAL_INFO_PROPERTY)
97   - private JsonNode details;
98   -
99   - @Column(name = ALARM_PROPAGATE_PROPERTY)
100   - private Boolean propagate;
  34 +public final class AlarmEntity extends AbstractAlarmEntity<Alarm> {
101 35
102 36 public AlarmEntity() {
103 37 super();
104 38 }
105 39
106 40 public AlarmEntity(Alarm alarm) {
107   - if (alarm.getId() != null) {
108   - this.setId(alarm.getId().getId());
109   - }
110   - if (alarm.getTenantId() != null) {
111   - this.tenantId = UUIDConverter.fromTimeUUID(alarm.getTenantId().getId());
112   - }
113   - this.type = alarm.getType();
114   - this.originatorId = UUIDConverter.fromTimeUUID(alarm.getOriginator().getId());
115   - this.originatorType = alarm.getOriginator().getEntityType();
116   - this.type = alarm.getType();
117   - this.severity = alarm.getSeverity();
118   - this.status = alarm.getStatus();
119   - this.propagate = alarm.isPropagate();
120   - this.startTs = alarm.getStartTs();
121   - this.endTs = alarm.getEndTs();
122   - this.ackTs = alarm.getAckTs();
123   - this.clearTs = alarm.getClearTs();
124   - this.details = alarm.getDetails();
  41 + super(alarm);
125 42 }
126 43
127 44 @Override
128 45 public Alarm toData() {
129   - Alarm alarm = new Alarm(new AlarmId(UUIDConverter.fromString(id)));
130   - alarm.setCreatedTime(UUIDs.unixTimestamp(UUIDConverter.fromString(id)));
131   - if (tenantId != null) {
132   - alarm.setTenantId(new TenantId(UUIDConverter.fromString(tenantId)));
133   - }
134   - alarm.setOriginator(EntityIdFactory.getByTypeAndUuid(originatorType, UUIDConverter.fromString(originatorId)));
135   - alarm.setType(type);
136   - alarm.setSeverity(severity);
137   - alarm.setStatus(status);
138   - alarm.setPropagate(propagate);
139   - alarm.setStartTs(startTs);
140   - alarm.setEndTs(endTs);
141   - alarm.setAckTs(ackTs);
142   - alarm.setClearTs(clearTs);
143   - alarm.setDetails(details);
144   - return alarm;
  46 + return super.toAlarm();
145 47 }
146   -
147   -}
\ No newline at end of file
  48 +}
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.dao.model.sql;
  17 +
  18 +import lombok.Data;
  19 +import lombok.EqualsAndHashCode;
  20 +import org.thingsboard.server.common.data.alarm.AlarmInfo;
  21 +
  22 +@Data
  23 +@EqualsAndHashCode(callSuper = true)
  24 +public class AlarmInfoEntity extends AbstractAlarmEntity<AlarmInfo> {
  25 +
  26 + private String originatorName;
  27 +
  28 + public AlarmInfoEntity() {
  29 + super();
  30 + }
  31 +
  32 + public AlarmInfoEntity(AlarmEntity alarmEntity) {
  33 + super(alarmEntity);
  34 + }
  35 +
  36 + @Override
  37 + public AlarmInfo toData() {
  38 + return new AlarmInfo(super.toAlarm(), this.originatorName);
  39 + }
  40 +}
... ...
... ... @@ -15,12 +15,14 @@
15 15 */
16 16 package org.thingsboard.server.dao.sql.alarm;
17 17
  18 +import org.springframework.data.domain.Page;
18 19 import org.springframework.data.domain.Pageable;
19 20 import org.springframework.data.jpa.repository.Query;
20 21 import org.springframework.data.repository.CrudRepository;
21 22 import org.springframework.data.repository.query.Param;
22 23 import org.thingsboard.server.common.data.EntityType;
23 24 import org.thingsboard.server.dao.model.sql.AlarmEntity;
  25 +import org.thingsboard.server.dao.model.sql.AlarmInfoEntity;
24 26 import org.thingsboard.server.dao.util.SqlDao;
25 27
26 28 import java.util.List;
... ... @@ -38,4 +40,26 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, String> {
38 40 @Param("entityType") EntityType entityType,
39 41 @Param("alarmType") String alarmType,
40 42 Pageable pageable);
  43 +
  44 + @Query("SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a, " +
  45 + "RelationEntity re " +
  46 + "WHERE a.tenantId = :tenantId " +
  47 + "AND a.id = re.toId AND re.toType = 'ALARM' " +
  48 + "AND re.relationTypeGroup = 'ALARM' " +
  49 + "AND re.relationType = :relationType " +
  50 + "AND re.fromId = :affectedEntityId " +
  51 + "AND re.fromType = :affectedEntityType " +
  52 + "AND (:startId IS NULL OR a.id >= :startId) " +
  53 + "AND (:endId IS NULL OR a.id <= :endId) " +
  54 + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%'))" +
  55 + "OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%'))" +
  56 + "OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%')))")
  57 + Page<AlarmInfoEntity> findAlarms(@Param("tenantId") String tenantId,
  58 + @Param("affectedEntityId") String affectedEntityId,
  59 + @Param("affectedEntityType") String affectedEntityType,
  60 + @Param("relationType") String relationType,
  61 + @Param("startId") String startId,
  62 + @Param("endId") String endId,
  63 + @Param("searchText") String searchText,
  64 + Pageable pageable);
41 65 }
... ...
... ... @@ -43,8 +43,13 @@ import org.thingsboard.server.dao.util.SqlDao;
43 43
44 44 import java.util.ArrayList;
45 45 import java.util.List;
  46 +import java.util.Objects;
46 47 import java.util.UUID;
47 48
  49 +import static org.thingsboard.server.common.data.UUIDConverter.fromTimeUUID;
  50 +import static org.thingsboard.server.dao.DaoUtil.endTimeToId;
  51 +import static org.thingsboard.server.dao.DaoUtil.startTimeToId;
  52 +
48 53 /**
49 54 * Created by Valerii Sosliuk on 5/19/2017.
50 55 */
... ... @@ -93,7 +98,7 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
93 98 }
94 99
95 100 @Override
96   - public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) {
  101 + public PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query) {
97 102 log.trace("Try to find alarms by entity [{}], status [{}] and pageLink [{}]", query.getAffectedEntityId(), query.getStatus(), query.getPageLink());
98 103 EntityId affectedEntity = query.getAffectedEntityId();
99 104 String searchStatusName;
... ... @@ -105,17 +110,18 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A
105 110 searchStatusName = query.getStatus().name();
106 111 }
107 112 String relationType = BaseAlarmService.ALARM_RELATION_PREFIX + searchStatusName;
108   - ListenableFuture<PageData<EntityRelation>> relations = relationDao.findRelations(tenantId, affectedEntity, relationType, RelationTypeGroup.ALARM, EntityType.ALARM, query.getPageLink());
109   - return Futures.transformAsync(relations, input -> {
110   - List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(input.getData().size());
111   - for (EntityRelation relation : input.getData()) {
112   - alarmFutures.add(Futures.transform(
113   - findAlarmByIdAsync(tenantId, relation.getTo().getId()),
114   - AlarmInfo::new));
115   - }
116   - return Futures.transform(Futures.successfulAsList(alarmFutures), alarmInfos -> {
117   - return new PageData(alarmInfos, input.getTotalPages(), input.getTotalElements(), input.hasNext());
118   - });
119   - });
  113 +
  114 + return DaoUtil.toPageData(
  115 + alarmRepository.findAlarms(
  116 + fromTimeUUID(tenantId.getId()),
  117 + fromTimeUUID(affectedEntity.getId()),
  118 + affectedEntity.getEntityType().name(),
  119 + relationType,
  120 + startTimeToId(query.getPageLink().getStartTime()),
  121 + endTimeToId(query.getPageLink().getEndTime()),
  122 + Objects.toString(query.getPageLink().getTextSearch(), ""),
  123 + DaoUtil.toPageable(query.getPageLink())
  124 + )
  125 + );
120 126 }
121 127 }
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Injectable } from '@angular/core';
  18 +import { defaultHttpOptions } from './http-utils';
  19 +import { Observable } from 'rxjs/index';
  20 +import { HttpClient } from '@angular/common/http';
  21 +import { PageData } from '@shared/models/page/page-data';
  22 +import { EntityId } from '@shared/models/id/entity-id';
  23 +import {
  24 + Alarm,
  25 + AlarmInfo,
  26 + AlarmQuery,
  27 + AlarmSearchStatus,
  28 + AlarmSeverity,
  29 + AlarmStatus
  30 +} from '@shared/models/alarm.models';
  31 +
  32 +@Injectable({
  33 + providedIn: 'root'
  34 +})
  35 +export class AlarmService {
  36 +
  37 + constructor(
  38 + private http: HttpClient
  39 + ) { }
  40 +
  41 + public getAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> {
  42 + return this.http.get<Alarm>(`/api/alarm/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors));
  43 + }
  44 +
  45 + public getAlarmInfo(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmInfo> {
  46 + return this.http.get<AlarmInfo>(`/api/alarm/info/${alarmId}`, defaultHttpOptions(ignoreLoading, ignoreErrors));
  47 + }
  48 +
  49 + public saveAlarm(alarm: Alarm, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Alarm> {
  50 + return this.http.post<Alarm>('/api/alarm', alarm, defaultHttpOptions(ignoreLoading, ignoreErrors));
  51 + }
  52 +
  53 + public ackAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> {
  54 + return this.http.post<void>(`/api/alarm/${alarmId}/ack`, null, defaultHttpOptions(ignoreLoading, ignoreErrors));
  55 + }
  56 +
  57 + public clearAlarm(alarmId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<void> {
  58 + return this.http.post<void>(`/api/alarm/${alarmId}/clear`, null, defaultHttpOptions(ignoreLoading, ignoreErrors));
  59 + }
  60 +
  61 + public getAlarms(query: AlarmQuery,
  62 + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<PageData<AlarmInfo>> {
  63 + return this.http.get<PageData<AlarmInfo>>(`/api/alarm${query.toQuery()}`,
  64 + defaultHttpOptions(ignoreLoading, ignoreErrors));
  65 + }
  66 +
  67 + public getHighestAlarmSeverity(entityId: EntityId, alarmSearchStatus: AlarmSearchStatus, alarmStatus: AlarmStatus,
  68 + ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<AlarmSeverity> {
  69 + let url = `/api/alarm/highestSeverity/${entityId.entityType}/${entityId.entityType}`;
  70 + if (alarmSearchStatus) {
  71 + url += `?searchStatus=${alarmSearchStatus}`;
  72 + } else if (alarmStatus) {
  73 + url += `?status=${alarmStatus}`;
  74 + }
  75 + return this.http.get<AlarmSeverity>(url,
  76 + defaultHttpOptions(ignoreLoading, ignoreErrors));
  77 + }
  78 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<form [formGroup]="alarmFormGroup" style="width: 600px;">
  19 + <mat-toolbar fxLayout="row" color="primary">
  20 + <h2>{{ 'alarm.alarm-details' | translate }}</h2>
  21 + <span fxFlex></span>
  22 + <button mat-button mat-icon-button
  23 + (click)="close()"
  24 + type="button">
  25 + <mat-icon class="material-icons">close</mat-icon>
  26 + </button>
  27 + </mat-toolbar>
  28 + <mat-progress-bar color="warn" mode="indeterminate" *ngIf="isLoading$ | async">
  29 + </mat-progress-bar>
  30 + <div style="height: 4px;" *ngIf="!(isLoading$ | async)"></div>
  31 + <div mat-dialog-content>
  32 + <fieldset [disabled]="isLoading$ | async">
  33 + <div fxLayout="row" fxLayoutGap="6px">
  34 + <mat-form-field class="mat-block">
  35 + <mat-label translate>alarm.created-time</mat-label>
  36 + <input matInput formControlName="createdTime" readonly>
  37 + </mat-form-field>
  38 + <mat-form-field fxFlex class="mat-block">
  39 + <mat-label translate>alarm.originator</mat-label>
  40 + <input matInput formControlName="originatorName" readonly>
  41 + </mat-form-field>
  42 + </div>
  43 + <div fxLayout="row" fxLayoutGap="6px" *ngIf="alarmFormGroup.get('startTime').value ||
  44 + alarmFormGroup.get('endTime').value">
  45 + <mat-form-field *ngIf="alarmFormGroup.get('startTime').value" fxFlex class="mat-block">
  46 + <mat-label translate>alarm.start-time</mat-label>
  47 + <input matInput formControlName="startTime" readonly>
  48 + </mat-form-field>
  49 + <mat-form-field *ngIf="alarmFormGroup.get('endTime').value" fxFlex class="mat-block">
  50 + <mat-label translate>alarm.end-time</mat-label>
  51 + <input matInput formControlName="endTime" readonly>
  52 + </mat-form-field>
  53 + <span fxFlex *ngIf="!alarmFormGroup.get('startTime').value || !alarmFormGroup.get('endTime').value"></span>
  54 + </div>
  55 + <div fxLayout="row" fxLayoutGap="6px" *ngIf="alarmFormGroup.get('ackTime').value ||
  56 + alarmFormGroup.get('clearTime').value">
  57 + <mat-form-field *ngIf="alarmFormGroup.get('ackTime').value" fxFlex class="mat-block">
  58 + <mat-label translate>alarm.ack-time</mat-label>
  59 + <input matInput formControlName="ackTime" readonly>
  60 + </mat-form-field>
  61 + <mat-form-field *ngIf="alarmFormGroup.get('clearTime').value" fxFlex class="mat-block">
  62 + <mat-label translate>alarm.clear-time</mat-label>
  63 + <input matInput formControlName="clearTime" readonly>
  64 + </mat-form-field>
  65 + <span fxFlex *ngIf="!alarmFormGroup.get('ackTime').value || !alarmFormGroup.get('clearTime').value"></span>
  66 + </div>
  67 + <div fxLayout="row" fxLayoutGap="6px">
  68 + <mat-form-field fxFlex class="mat-block">
  69 + <mat-label translate>alarm.type</mat-label>
  70 + <input matInput formControlName="type" readonly>
  71 + </mat-form-field>
  72 + <mat-form-field fxFlex class="mat-block">
  73 + <mat-label translate>alarm.severity</mat-label>
  74 + <input matInput formControlName="alarmSeverity" readonly
  75 + [ngStyle]="{fontWeight: 'bold', color: alarmSeverityColorsMap.get((alarm$ | async)?.severity)}">
  76 + </mat-form-field>
  77 + <mat-form-field fxFlex class="mat-block">
  78 + <mat-label translate>alarm.status</mat-label>
  79 + <input matInput formControlName="alarmStatus" readonly>
  80 + </mat-form-field>
  81 + </div>
  82 + <tb-json-object-edit
  83 + *ngIf="displayDetails"
  84 + formControlName="alarmDetails"
  85 + readonly
  86 + label="{{ 'alarm.details' | translate }}">
  87 + </tb-json-object-edit>
  88 + </fieldset>
  89 + </div>
  90 + <div mat-dialog-actions fxLayout="row">
  91 + <div fxLayout="row" *ngIf="alarm$ | async; let alarm;">
  92 + <button *ngIf="allowAcknowledgment && (alarm.status === alarmStatuses.ACTIVE_UNACK ||
  93 + alarm.status === alarmStatuses.CLEARED_UNACK)"
  94 + mat-button
  95 + mat-raised-button
  96 + color="primary"
  97 + type="button"
  98 + (click)="acknowledge()"
  99 + style="margin-right: 20px;"
  100 + [disabled]="(isLoading$ | async)">
  101 + {{ 'alarm.acknowledge' | translate }}
  102 + </button>
  103 + <button *ngIf="allowClear && (alarm.status === alarmStatuses.ACTIVE_ACK ||
  104 + alarm.status === alarmStatuses.ACTIVE_UNACK)"
  105 + mat-button
  106 + mat-raised-button
  107 + color="primary"
  108 + type="button"
  109 + (click)="clear()"
  110 + [disabled]="(isLoading$ | async)">
  111 + {{ 'alarm.clear' | translate }}
  112 + </button>
  113 + </div>
  114 + <span fxFlex></span>
  115 + <button mat-button color="primary"
  116 + style="margin-right: 20px;"
  117 + type="button"
  118 + [disabled]="(isLoading$ | async)"
  119 + (click)="close()" cdkFocusInitial>
  120 + {{ 'action.close' | translate }}
  121 + </button>
  122 + </div>
  123 +</form>
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Inject, OnInit } from '@angular/core';
  18 +import { MAT_DIALOG_DATA, MatDialogRef } from '@angular/material';
  19 +import { Store } from '@ngrx/store';
  20 +import { AppState } from '@core/core.state';
  21 +import { FormBuilder, FormGroup } from '@angular/forms';
  22 +import { Observable, Subject } from 'rxjs';
  23 +import { Router } from '@angular/router';
  24 +import { DialogComponent } from '@app/shared/components/dialog.component';
  25 +import {
  26 + AlarmInfo,
  27 + alarmSeverityColors,
  28 + alarmSeverityTranslations,
  29 + AlarmStatus,
  30 + alarmStatusTranslations
  31 +} from '@app/shared/models/alarm.models';
  32 +import { AlarmService } from '@core/http/alarm.service';
  33 +import { share, tap } from 'rxjs/operators';
  34 +import { DatePipe } from '@angular/common';
  35 +import { TranslateService } from '@ngx-translate/core';
  36 +
  37 +export interface AlarmDetailsDialogData {
  38 + alarmId: string;
  39 + allowAcknowledgment: boolean;
  40 + allowClear: boolean;
  41 + displayDetails: boolean;
  42 +}
  43 +
  44 +@Component({
  45 + selector: 'tb-alarm-details-dialog',
  46 + templateUrl: './alarm-details-dialog.component.html',
  47 + styleUrls: []
  48 +})
  49 +export class AlarmDetailsDialogComponent extends DialogComponent<AlarmDetailsDialogComponent, boolean> implements OnInit {
  50 +
  51 + alarmFormGroup: FormGroup;
  52 +
  53 + allowAcknowledgment: boolean;
  54 + allowClear: boolean;
  55 + displayDetails: boolean;
  56 +
  57 + loadAlarmSubject = new Subject<AlarmInfo>();
  58 + alarm$: Observable<AlarmInfo> = this.loadAlarmSubject.asObservable().pipe(
  59 + tap(alarm => this.loadAlarmFields(alarm)),
  60 + share()
  61 + );
  62 +
  63 + alarmSeverityColorsMap = alarmSeverityColors;
  64 + alarmStatuses = AlarmStatus;
  65 +
  66 + alarmUpdated = false;
  67 +
  68 + constructor(protected store: Store<AppState>,
  69 + protected router: Router,
  70 + private datePipe: DatePipe,
  71 + private translate: TranslateService,
  72 + @Inject(MAT_DIALOG_DATA) public data: AlarmDetailsDialogData,
  73 + private alarmService: AlarmService,
  74 + public dialogRef: MatDialogRef<AlarmDetailsDialogComponent, boolean>,
  75 + public fb: FormBuilder) {
  76 + super(store, router, dialogRef);
  77 +
  78 + this.allowAcknowledgment = data.allowAcknowledgment;
  79 + this.allowClear = data.allowClear;
  80 + this.displayDetails = data.displayDetails;
  81 +
  82 + this.alarmFormGroup = this.fb.group(
  83 + {
  84 + createdTime: [''],
  85 + originatorName: [''],
  86 + startTime: [''],
  87 + endTime: [''],
  88 + ackTime: [''],
  89 + clearTime: [''],
  90 + type: [''],
  91 + alarmSeverity: [''],
  92 + alarmStatus: [''],
  93 + alarmDetails: [null]
  94 + }
  95 + );
  96 +
  97 + this.loadAlarm();
  98 +
  99 + }
  100 +
  101 + loadAlarm() {
  102 + this.alarmService.getAlarmInfo(this.data.alarmId).subscribe(
  103 + alarm => this.loadAlarmSubject.next(alarm)
  104 + );
  105 + }
  106 +
  107 + loadAlarmFields(alarm: AlarmInfo) {
  108 + this.alarmFormGroup.get('createdTime')
  109 + .patchValue(this.datePipe.transform(alarm.createdTime, 'yyyy-MM-dd HH:mm:ss'));
  110 + this.alarmFormGroup.get('originatorName')
  111 + .patchValue(alarm.originatorName);
  112 + if (alarm.startTs) {
  113 + this.alarmFormGroup.get('startTime')
  114 + .patchValue(this.datePipe.transform(alarm.startTs, 'yyyy-MM-dd HH:mm:ss'));
  115 + }
  116 + if (alarm.endTs) {
  117 + this.alarmFormGroup.get('endTime')
  118 + .patchValue(this.datePipe.transform(alarm.endTs, 'yyyy-MM-dd HH:mm:ss'));
  119 + }
  120 + if (alarm.ackTs) {
  121 + this.alarmFormGroup.get('ackTime')
  122 + .patchValue(this.datePipe.transform(alarm.ackTs, 'yyyy-MM-dd HH:mm:ss'));
  123 + }
  124 + if (alarm.clearTs) {
  125 + this.alarmFormGroup.get('clearTime')
  126 + .patchValue(this.datePipe.transform(alarm.clearTs, 'yyyy-MM-dd HH:mm:ss'));
  127 + }
  128 + this.alarmFormGroup.get('type').patchValue(alarm.type);
  129 + this.alarmFormGroup.get('alarmSeverity')
  130 + .patchValue(this.translate.instant(alarmSeverityTranslations.get(alarm.severity)));
  131 + this.alarmFormGroup.get('alarmStatus')
  132 + .patchValue(this.translate.instant(alarmStatusTranslations.get(alarm.status)));
  133 + this.alarmFormGroup.get('alarmDetails').patchValue(alarm.details);
  134 + }
  135 +
  136 + ngOnInit(): void {
  137 + }
  138 +
  139 + close(): void {
  140 + this.dialogRef.close(this.alarmUpdated);
  141 + }
  142 +
  143 + acknowledge(): void {
  144 + this.alarmService.ackAlarm(this.data.alarmId).subscribe(
  145 + () => { this.alarmUpdated = true; this.loadAlarm(); }
  146 + );
  147 + }
  148 +
  149 + clear(): void {
  150 + this.alarmService.clearAlarm(this.data.alarmId).subscribe(
  151 + () => { this.alarmUpdated = true; this.loadAlarm(); }
  152 + );
  153 + }
  154 +
  155 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import {
  18 + DateEntityTableColumn,
  19 + EntityTableColumn,
  20 + EntityTableConfig
  21 +} from '@home/models/entity/entities-table-config.models';
  22 +import { EntityType, EntityTypeResource, entityTypeTranslations } from '@shared/models/entity-type.models';
  23 +import { TranslateService } from '@ngx-translate/core';
  24 +import { DatePipe } from '@angular/common';
  25 +import { Direction } from '@shared/models/page/sort-order';
  26 +import { MatDialog } from '@angular/material';
  27 +import { TimePageLink } from '@shared/models/page/page-link';
  28 +import { Observable } from 'rxjs';
  29 +import { PageData } from '@shared/models/page/page-data';
  30 +import { EntityId } from '@shared/models/id/entity-id';
  31 +import {
  32 + AlarmInfo,
  33 + AlarmQuery,
  34 + AlarmSearchStatus,
  35 + alarmSeverityColors,
  36 + alarmSeverityTranslations,
  37 + alarmStatusTranslations
  38 +} from '@app/shared/models/alarm.models';
  39 +import { AlarmService } from '@app/core/http/alarm.service';
  40 +import { DialogService } from '@core/services/dialog.service';
  41 +import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component';
  42 +import {
  43 + AuditLogDetailsDialogComponent,
  44 + AuditLogDetailsDialogData
  45 +} from '@home/components/audit-log/audit-log-details-dialog.component';
  46 +import {
  47 + AlarmDetailsDialogComponent,
  48 + AlarmDetailsDialogData
  49 +} from '@home/components/alarm/alarm-details-dialog.component';
  50 +
  51 +export class AlarmTableConfig extends EntityTableConfig<AlarmInfo, TimePageLink> {
  52 +
  53 + searchStatus: AlarmSearchStatus;
  54 +
  55 + constructor(private alarmService: AlarmService,
  56 + private dialogService: DialogService,
  57 + private translate: TranslateService,
  58 + private datePipe: DatePipe,
  59 + private dialog: MatDialog,
  60 + public entityId: EntityId = null,
  61 + private defaultSearchStatus: AlarmSearchStatus = AlarmSearchStatus.ANY) {
  62 + super();
  63 + this.loadDataOnInit = false;
  64 + this.tableTitle = '';
  65 + this.useTimePageLink = true;
  66 + this.detailsPanelEnabled = false;
  67 + this.selectionEnabled = false;
  68 + this.searchEnabled = true;
  69 + this.addEnabled = false;
  70 + this.entitiesDeleteEnabled = false;
  71 + this.actionsColumnTitle = 'alarm.details';
  72 + this.entityType = EntityType.ALARM;
  73 + this.entityTranslations = entityTypeTranslations.get(EntityType.ALARM);
  74 + this.entityResources = {
  75 + } as EntityTypeResource;
  76 + this.searchStatus = defaultSearchStatus;
  77 +
  78 + this.headerComponent = AlarmTableHeaderComponent;
  79 +
  80 + this.entitiesFetchFunction = pageLink => this.fetchAlarms(pageLink);
  81 +
  82 + this.defaultSortOrder = {property: 'createdTime', direction: Direction.DESC};
  83 +
  84 + this.columns.push(
  85 + new DateEntityTableColumn<AlarmInfo>('createdTime', 'alarm.created-time', this.datePipe, '150px'));
  86 + this.columns.push(
  87 + new EntityTableColumn<AlarmInfo>('originatorName', 'alarm.originator', '100%',
  88 + (entity) => entity.originatorName, entity => ({}), false));
  89 + this.columns.push(
  90 + new EntityTableColumn<AlarmInfo>('type', 'alarm.type', '100%'));
  91 + this.columns.push(
  92 + new EntityTableColumn<AlarmInfo>('severity', 'alarm.severity', '100%',
  93 + (entity) => this.translate.instant(alarmSeverityTranslations.get(entity.severity)),
  94 + entity => ({
  95 + fontWeight: 'bold',
  96 + color: alarmSeverityColors.get(entity.severity)
  97 + })));
  98 + this.columns.push(
  99 + new EntityTableColumn<AlarmInfo>('status', 'alarm.status', '100%',
  100 + (entity) => this.translate.instant(alarmStatusTranslations.get(entity.status))));
  101 +
  102 + this.cellActionDescriptors.push(
  103 + {
  104 + name: this.translate.instant('alarm.details'),
  105 + icon: 'more_horiz',
  106 + isEnabled: () => true,
  107 + onAction: ($event, entity) => this.showAlarmDetails(entity)
  108 + }
  109 + );
  110 + }
  111 +
  112 + fetchAlarms(pageLink: TimePageLink): Observable<PageData<AlarmInfo>> {
  113 + const query = new AlarmQuery(this.entityId, pageLink, this.searchStatus, null, true);
  114 + return this.alarmService.getAlarms(query);
  115 + }
  116 +
  117 + showAlarmDetails(entity: AlarmInfo) {
  118 + this.dialog.open<AlarmDetailsDialogComponent, AlarmDetailsDialogData, boolean>
  119 + (AlarmDetailsDialogComponent,
  120 + {
  121 + disableClose: true,
  122 + panelClass: ['tb-dialog', 'tb-fullscreen-dialog'],
  123 + data: {
  124 + alarmId: entity.id.id,
  125 + allowAcknowledgment: true,
  126 + allowClear: true,
  127 + displayDetails: true
  128 + }
  129 + }).afterClosed().subscribe(
  130 + (res) => {
  131 + if (res) {
  132 + this.table.updateData();
  133 + }
  134 + }
  135 + );
  136 + }
  137 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  19 +<mat-form-field class="mat-block" style="width: 200px;">
  20 + <mat-label translate>alarm.alarm-status</mat-label>
  21 + <mat-select matInput [ngModel]="alarmTableConfig.searchStatus"
  22 + (ngModelChange)="searchStatusChanged($event)">
  23 + <mat-option *ngFor="let status of alarmSearchStatusTypes" [value]="status">
  24 + {{ alarmSearchStatusTranslationsMap.get(status) | translate }}
  25 + </mat-option>
  26 + </mat-select>
  27 +</mat-form-field>
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host {
  17 + /* flex: 1;
  18 + display: flex;
  19 + justify-content: flex-start; */
  20 + padding-right: 8px;
  21 +}
  22 +
  23 +:host ::ng-deep {
  24 + mat-form-field {
  25 + font-size: 16px;
  26 +
  27 + .mat-form-field-wrapper {
  28 + padding-bottom: 0;
  29 + }
  30 +
  31 + .mat-form-field-underline {
  32 + bottom: 0;
  33 + }
  34 + }
  35 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component } from '@angular/core';
  18 +import { Store } from '@ngrx/store';
  19 +import { AppState } from '@core/core.state';
  20 +import { EntityTableHeaderComponent } from '../../components/entity/entity-table-header.component';
  21 +import { AlarmInfo, AlarmSearchStatus, alarmSearchStatusTranslations } from '@shared/models/alarm.models';
  22 +import { AlarmTableConfig } from './alarm-table-config';
  23 +
  24 +@Component({
  25 + selector: 'tb-alarm-table-header',
  26 + templateUrl: './alarm-table-header.component.html',
  27 + styleUrls: ['./alarm-table-header.component.scss']
  28 +})
  29 +export class AlarmTableHeaderComponent extends EntityTableHeaderComponent<AlarmInfo> {
  30 +
  31 + alarmSearchStatusTranslationsMap = alarmSearchStatusTranslations;
  32 +
  33 + alarmSearchStatusTypes = Object.keys(AlarmSearchStatus);
  34 +
  35 + get alarmTableConfig(): AlarmTableConfig {
  36 + return this.entitiesTableConfig as AlarmTableConfig;
  37 + }
  38 +
  39 + constructor(protected store: Store<AppState>) {
  40 + super(store);
  41 + }
  42 +
  43 + searchStatusChanged(searchStatus: AlarmSearchStatus) {
  44 + this.alarmTableConfig.searchStatus = searchStatus;
  45 + this.alarmTableConfig.table.updateData();
  46 + }
  47 +}
... ...
  1 +<!--
  2 +
  3 + Copyright © 2016-2019 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +<tb-entities-table [entitiesTableConfig]="alarmTableConfig"></tb-entities-table>
... ...
  1 +/**
  2 + * Copyright © 2016-2019 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +:host ::ng-deep {
  17 + tb-entities-table {
  18 + .mat-drawer-container {
  19 + background-color: white;
  20 + }
  21 + }
  22 +}
... ...
  1 +///
  2 +/// Copyright © 2016-2019 The Thingsboard Authors
  3 +///
  4 +/// Licensed under the Apache License, Version 2.0 (the "License");
  5 +/// you may not use this file except in compliance with the License.
  6 +/// You may obtain a copy of the License at
  7 +///
  8 +/// http://www.apache.org/licenses/LICENSE-2.0
  9 +///
  10 +/// Unless required by applicable law or agreed to in writing, software
  11 +/// distributed under the License is distributed on an "AS IS" BASIS,
  12 +/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 +/// See the License for the specific language governing permissions and
  14 +/// limitations under the License.
  15 +///
  16 +
  17 +import { Component, Input, OnInit, ViewChild } from '@angular/core';
  18 +import { TranslateService } from '@ngx-translate/core';
  19 +import { DatePipe } from '@angular/common';
  20 +import { MatDialog } from '@angular/material';
  21 +import { EntityId } from '@shared/models/id/entity-id';
  22 +import { EntitiesTableComponent } from '@home/components/entity/entities-table.component';
  23 +import { Store } from '@ngrx/store';
  24 +import { AppState } from '@core/core.state';
  25 +import { DialogService } from '@core/services/dialog.service';
  26 +import { AlarmTableConfig } from './alarm-table-config';
  27 +import { AlarmSearchStatus } from '@shared/models/alarm.models';
  28 +import { AlarmService } from '@app/core/http/alarm.service';
  29 +
  30 +@Component({
  31 + selector: 'tb-alarm-table',
  32 + templateUrl: './alarm-table.component.html',
  33 + styleUrls: ['./alarm-table.component.scss']
  34 +})
  35 +export class AlarmTableComponent implements OnInit {
  36 +
  37 + activeValue = false;
  38 + dirtyValue = false;
  39 + entityIdValue: EntityId;
  40 +
  41 + @Input()
  42 + set active(active: boolean) {
  43 + if (this.activeValue !== active) {
  44 + this.activeValue = active;
  45 + if (this.activeValue && this.dirtyValue) {
  46 + this.dirtyValue = false;
  47 + this.entitiesTable.updateData();
  48 + }
  49 + }
  50 + }
  51 +
  52 + @Input()
  53 + set entityId(entityId: EntityId) {
  54 + this.entityIdValue = entityId;
  55 + if (this.alarmTableConfig && this.alarmTableConfig.entityId !== entityId) {
  56 + this.alarmTableConfig.searchStatus = AlarmSearchStatus.ANY;
  57 + this.alarmTableConfig.entityId = entityId;
  58 + this.entitiesTable.resetSortAndFilter(this.activeValue);
  59 + if (!this.activeValue) {
  60 + this.dirtyValue = true;
  61 + }
  62 + }
  63 + }
  64 +
  65 + @ViewChild(EntitiesTableComponent, {static: true}) entitiesTable: EntitiesTableComponent;
  66 +
  67 + alarmTableConfig: AlarmTableConfig;
  68 +
  69 + constructor(private alarmService: AlarmService,
  70 + private dialogService: DialogService,
  71 + private translate: TranslateService,
  72 + private datePipe: DatePipe,
  73 + private dialog: MatDialog,
  74 + private store: Store<AppState>) {
  75 + }
  76 +
  77 + ngOnInit() {
  78 + this.dirtyValue = !this.activeValue;
  79 + this.alarmTableConfig = new AlarmTableConfig(
  80 + this.alarmService,
  81 + this.dialogService,
  82 + this.translate,
  83 + this.datePipe,
  84 + this.dialog,
  85 + this.entityIdValue,
  86 + AlarmSearchStatus.ANY
  87 + );
  88 + }
  89 +
  90 +}
... ...
... ... @@ -28,13 +28,18 @@ import { EventTableHeaderComponent } from '@home/components/event/event-table-he
28 28 import { EventTableComponent } from '@home/components/event/event-table.component';
29 29 import { RelationTableComponent } from '@home/components/relation/relation-table.component';
30 30 import { RelationDialogComponent } from './relation/relation-dialog.component';
  31 +import { AlarmTableHeaderComponent } from '@home/components/alarm/alarm-table-header.component';
  32 +import { AlarmTableComponent } from '@home/components/alarm/alarm-table.component';
  33 +import { AlarmDetailsDialogComponent } from '@home/components/alarm/alarm-details-dialog.component';
31 34
32 35 @NgModule({
33 36 entryComponents: [
34 37 AddEntityDialogComponent,
35 38 AuditLogDetailsDialogComponent,
36 39 EventTableHeaderComponent,
37   - RelationDialogComponent
  40 + RelationDialogComponent,
  41 + AlarmTableHeaderComponent,
  42 + AlarmDetailsDialogComponent
38 43 ],
39 44 declarations:
40 45 [
... ... @@ -48,7 +53,10 @@ import { RelationDialogComponent } from './relation/relation-dialog.component';
48 53 EventTableHeaderComponent,
49 54 EventTableComponent,
50 55 RelationTableComponent,
51   - RelationDialogComponent
  56 + RelationDialogComponent,
  57 + AlarmTableHeaderComponent,
  58 + AlarmTableComponent,
  59 + AlarmDetailsDialogComponent
52 60 ],
53 61 imports: [
54 62 CommonModule,
... ... @@ -62,7 +70,9 @@ import { RelationDialogComponent } from './relation/relation-dialog.component';
62 70 ContactComponent,
63 71 AuditLogTableComponent,
64 72 EventTableComponent,
65   - RelationTableComponent
  73 + RelationTableComponent,
  74 + AlarmTableComponent,
  75 + AlarmDetailsDialogComponent
66 76 ]
67 77 })
68 78 export class HomeComponentsModule { }
... ...
... ... @@ -16,6 +16,10 @@
16 16
17 17 -->
18 18 <mat-tab *ngIf="entity"
  19 + label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
  20 + <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
  21 +</mat-tab>
  22 +<mat-tab *ngIf="entity"
19 23 label="{{ 'asset.events' | translate }}" #eventsTab="matTab">
20 24 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
21 25 [entityId]="entity.id"></tb-event-table>
... ...
... ... @@ -16,6 +16,10 @@
16 16
17 17 -->
18 18 <mat-tab *ngIf="entity"
  19 + label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
  20 + <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
  21 +</mat-tab>
  22 +<mat-tab *ngIf="entity"
19 23 label="{{ 'customer.events' | translate }}" #eventsTab="matTab">
20 24 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
21 25 [entityId]="entity.id"></tb-event-table>
... ...
... ... @@ -62,7 +62,7 @@
62 62 </div>
63 63 <div class="mat-padding" fxLayout="column">
64 64 <mat-form-field class="mat-block"
65   - [fxShow]="!isEdit && assignedCustomersText
  65 + [fxShow]="!isEdit && assignedCustomersText?.length
66 66 && dashboardScope === 'tenant'">
67 67 <mat-label translate>dashboard.assignedToCustomers</mat-label>
68 68 <input matInput disabled [ngModel]="assignedCustomersText">
... ...
... ... @@ -16,6 +16,10 @@
16 16
17 17 -->
18 18 <mat-tab *ngIf="entity"
  19 + label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
  20 + <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
  21 +</mat-tab>
  22 +<mat-tab *ngIf="entity"
19 23 label="{{ 'device.events' | translate }}" #eventsTab="matTab">
20 24 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
21 25 [entityId]="entity.id"></tb-event-table>
... ...
... ... @@ -16,6 +16,10 @@
16 16
17 17 -->
18 18 <mat-tab *ngIf="entity"
  19 + label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
  20 + <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
  21 +</mat-tab>
  22 +<mat-tab *ngIf="entity"
19 23 label="{{ 'entity-view.events' | translate }}" #eventsTab="matTab">
20 24 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="entity.tenantId.id"
21 25 [entityId]="entity.id"></tb-event-table>
... ...
... ... @@ -16,6 +16,10 @@
16 16
17 17 -->
18 18 <mat-tab *ngIf="entity"
  19 + label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
  20 + <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
  21 +</mat-tab>
  22 +<mat-tab *ngIf="entity"
19 23 label="{{ 'rulechain.events' | translate }}" #eventsTab="matTab">
20 24 <tb-event-table [active]="eventsTab.isActive"
21 25 [debugEventTypes]="[debugEventTypes.DEBUG_RULE_CHAIN]"
... ...
... ... @@ -16,6 +16,10 @@
16 16
17 17 -->
18 18 <mat-tab *ngIf="entity"
  19 + label="{{ 'alarm.alarms' | translate }}" #alarmsTab="matTab">
  20 + <tb-alarm-table [active]="alarmsTab.isActive" [entityId]="entity.id"></tb-alarm-table>
  21 +</mat-tab>
  22 +<mat-tab *ngIf="entity"
19 23 label="{{ 'tenant.events' | translate }}" #eventsTab="matTab">
20 24 <tb-event-table [active]="eventsTab.isActive" [defaultEventType]="eventTypes.ERROR" [tenantId]="nullUid"
21 25 [entityId]="entity.id"></tb-event-table>
... ...
... ... @@ -42,7 +42,6 @@ export abstract class DialogComponent<T, R = any> extends PageComponent implemen
42 42 }
43 43
44 44 ngOnDestroy(): void {
45   - console.log('Dialog destroy called');
46 45 super.ngOnDestroy();
47 46 if (this.routerSubscription) {
48 47 this.routerSubscription.unsubscribe();
... ...
... ... @@ -20,6 +20,8 @@ import {TenantId} from '@shared/models/id/tenant-id';
20 20 import {CustomerId} from '@shared/models/id/customer-id';
21 21 import {AlarmId} from '@shared/models/id/alarm-id';
22 22 import {EntityId} from '@shared/models/id/entity-id';
  23 +import { ActionStatus } from '@shared/models/audit-log.models';
  24 +import { TimePageLink } from '@shared/models/page/page-link';
23 25
24 26 export enum AlarmSeverity {
25 27 CRITICAL = 'CRITICAL',
... ... @@ -36,6 +38,53 @@ export enum AlarmStatus {
36 38 CLEARED_ACK = 'CLEARED_ACK'
37 39 }
38 40
  41 +export enum AlarmSearchStatus {
  42 + ANY = 'ANY',
  43 + ACTIVE = 'ACTIVE',
  44 + CLEARED = 'CLEARED',
  45 + ACK = 'ACK',
  46 + UNACK = 'UNACK'
  47 +}
  48 +
  49 +export const alarmSeverityTranslations = new Map<AlarmSeverity, string>(
  50 + [
  51 + [AlarmSeverity.CRITICAL, 'alarm.severity-critical'],
  52 + [AlarmSeverity.MAJOR, 'alarm.severity-major'],
  53 + [AlarmSeverity.MINOR, 'alarm.severity-minor'],
  54 + [AlarmSeverity.WARNING, 'alarm.severity-warning'],
  55 + [AlarmSeverity.INDETERMINATE, 'alarm.severity-indeterminate']
  56 + ]
  57 +);
  58 +
  59 +export const alarmStatusTranslations = new Map<AlarmStatus, string>(
  60 + [
  61 + [AlarmStatus.ACTIVE_UNACK, 'alarm.display-status.ACTIVE_UNACK'],
  62 + [AlarmStatus.ACTIVE_ACK, 'alarm.display-status.ACTIVE_ACK'],
  63 + [AlarmStatus.CLEARED_UNACK, 'alarm.display-status.CLEARED_UNACK'],
  64 + [AlarmStatus.CLEARED_ACK, 'alarm.display-status.CLEARED_ACK'],
  65 + ]
  66 +);
  67 +
  68 +export const alarmSearchStatusTranslations = new Map<AlarmSearchStatus, string>(
  69 + [
  70 + [AlarmSearchStatus.ANY, 'alarm.search-status.ANY'],
  71 + [AlarmSearchStatus.ACTIVE, 'alarm.search-status.ACTIVE'],
  72 + [AlarmSearchStatus.CLEARED, 'alarm.search-status.CLEARED'],
  73 + [AlarmSearchStatus.ACK, 'alarm.search-status.ACK'],
  74 + [AlarmSearchStatus.UNACK, 'alarm.search-status.UNACK']
  75 + ]
  76 +);
  77 +
  78 +export const alarmSeverityColors = new Map<AlarmSeverity, string>(
  79 + [
  80 + [AlarmSeverity.CRITICAL, 'red'],
  81 + [AlarmSeverity.MAJOR, 'orange'],
  82 + [AlarmSeverity.MINOR, '#ffca3d'],
  83 + [AlarmSeverity.WARNING, '#abab00'],
  84 + [AlarmSeverity.INDETERMINATE, 'green']
  85 + ]
  86 +);
  87 +
39 88 export interface Alarm extends BaseData<AlarmId> {
40 89 tenantId: TenantId;
41 90 type: string;
... ... @@ -49,3 +98,41 @@ export interface Alarm extends BaseData<AlarmId> {
49 98 propagate: boolean;
50 99 details?: any;
51 100 }
  101 +
  102 +export interface AlarmInfo extends Alarm {
  103 + originatorName: string;
  104 +}
  105 +
  106 +export class AlarmQuery {
  107 +
  108 + affectedEntityId: EntityId;
  109 + pageLink: TimePageLink;
  110 + searchStatus: AlarmSearchStatus;
  111 + status: AlarmStatus;
  112 + fetchOriginator: boolean;
  113 +
  114 + constructor(entityId: EntityId, pageLink: TimePageLink,
  115 + searchStatus: AlarmSearchStatus, status: AlarmStatus,
  116 + fetchOriginator: boolean) {
  117 + this.affectedEntityId = entityId;
  118 + this.pageLink = pageLink;
  119 + this.searchStatus = searchStatus;
  120 + this.status = status;
  121 + this.fetchOriginator = fetchOriginator;
  122 + }
  123 +
  124 + public toQuery(): string {
  125 + let query = `/${this.affectedEntityId.entityType}/${this.affectedEntityId.id}`;
  126 + query += this.pageLink.toQuery();
  127 + if (this.searchStatus) {
  128 + query += `&searchStatus=${this.searchStatus}`;
  129 + } else if (this.status) {
  130 + query += `&status=${this.status}`;
  131 + }
  132 + if (typeof this.fetchOriginator !== 'undefined' && this.fetchOriginator !== null) {
  133 + query += `&fetchOriginator=${this.fetchOriginator}`;
  134 + }
  135 + return query;
  136 + }
  137 +
  138 +}
... ...
... ... @@ -166,6 +166,19 @@ export const entityTypeTranslations = new Map<EntityType | AliasEntityType, Enti
166 166 }
167 167 ],
168 168 [
  169 + EntityType.ALARM,
  170 + {
  171 + type: 'entity.type-alarm',
  172 + typePlural: 'entity.type-alarms',
  173 + list: 'entity.list-of-alarms',
  174 + nameStartsWith: 'entity.alarm-name-starts-with',
  175 + details: 'dashboard.dashboard-details',
  176 + noEntities: 'alarm.no-alarms-prompt',
  177 + search: 'alarm.search',
  178 + selectedEntities: 'alarm.selected-alarms'
  179 + }
  180 + ],
  181 + [
169 182 EntityType.WIDGETS_BUNDLE,
170 183 {
171 184 details: 'widgets-bundle.widgets-bundle-details',
... ...