Commit d5b640d6021d4a3b57ae1b70cb1706084809a0af
1 parent
89c1743f
Add image fields for dashboard and device profile entities. Introduce getAllAlar…
…ms and getCustomerAlarms API
Showing
31 changed files
with
257 additions
and
32 deletions
... | ... | @@ -78,7 +78,11 @@ CREATE TABLE IF NOT EXISTS firmware ( |
78 | 78 | CONSTRAINT firmware_tenant_title_version_unq_key UNIQUE (tenant_id, title, version) |
79 | 79 | ); |
80 | 80 | |
81 | +ALTER TABLE dashboard | |
82 | + ADD COLUMN IF NOT EXISTS image varchar(1000000); | |
83 | + | |
81 | 84 | ALTER TABLE device_profile |
85 | + ADD COLUMN IF NOT EXISTS image varchar(1000000), | |
82 | 86 | ADD COLUMN IF NOT EXISTS firmware_id uuid, |
83 | 87 | ADD COLUMN IF NOT EXISTS software_id uuid; |
84 | 88 | ... | ... |
... | ... | @@ -197,6 +197,41 @@ public class AlarmController extends BaseController { |
197 | 197 | } |
198 | 198 | |
199 | 199 | @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") |
200 | + @RequestMapping(value = "/alarms", method = RequestMethod.GET) | |
201 | + @ResponseBody | |
202 | + public PageData<AlarmInfo> getAllAlarms( | |
203 | + @RequestParam(required = false) String searchStatus, | |
204 | + @RequestParam(required = false) String status, | |
205 | + @RequestParam int pageSize, | |
206 | + @RequestParam int page, | |
207 | + @RequestParam(required = false) String textSearch, | |
208 | + @RequestParam(required = false) String sortProperty, | |
209 | + @RequestParam(required = false) String sortOrder, | |
210 | + @RequestParam(required = false) Long startTime, | |
211 | + @RequestParam(required = false) Long endTime, | |
212 | + @RequestParam(required = false) Boolean fetchOriginator | |
213 | + ) throws ThingsboardException { | |
214 | + accessControlService.checkPermission(getCurrentUser(), Resource.ALARM, Operation.READ); | |
215 | + AlarmSearchStatus alarmSearchStatus = StringUtils.isEmpty(searchStatus) ? null : AlarmSearchStatus.valueOf(searchStatus); | |
216 | + AlarmStatus alarmStatus = StringUtils.isEmpty(status) ? null : AlarmStatus.valueOf(status); | |
217 | + if (alarmSearchStatus != null && alarmStatus != null) { | |
218 | + throw new ThingsboardException("Invalid alarms search query: Both parameters 'searchStatus' " + | |
219 | + "and 'status' can't be specified at the same time!", ThingsboardErrorCode.BAD_REQUEST_PARAMS); | |
220 | + } | |
221 | + TimePageLink pageLink = createTimePageLink(pageSize, page, textSearch, sortProperty, sortOrder, startTime, endTime); | |
222 | + | |
223 | + try { | |
224 | + if (getCurrentUser().isCustomerUser()) { | |
225 | + return checkNotNull(alarmService.findCustomerAlarms(getCurrentUser().getTenantId(), getCurrentUser().getCustomerId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); | |
226 | + } else { | |
227 | + return checkNotNull(alarmService.findAlarms(getCurrentUser().getTenantId(), new AlarmQuery(null, pageLink, alarmSearchStatus, alarmStatus, fetchOriginator)).get()); | |
228 | + } | |
229 | + } catch (Exception e) { | |
230 | + throw handleException(e); | |
231 | + } | |
232 | + } | |
233 | + | |
234 | + @PreAuthorize("hasAnyAuthority('SYS_ADMIN', 'TENANT_ADMIN', 'CUSTOMER_USER')") | |
200 | 235 | @RequestMapping(value = "/alarm/highestSeverity/{entityType}/{entityId}", method = RequestMethod.GET) |
201 | 236 | @ResponseBody |
202 | 237 | public AlarmSeverity getHighestAlarmSeverity( | ... | ... |
... | ... | @@ -128,6 +128,11 @@ public class DefaultAlarmSubscriptionService extends AbstractSubscriptionService |
128 | 128 | } |
129 | 129 | |
130 | 130 | @Override |
131 | + public ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { | |
132 | + return alarmService.findCustomerAlarms(tenantId, customerId, query); | |
133 | + } | |
134 | + | |
135 | + @Override | |
131 | 136 | public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus) { |
132 | 137 | return alarmService.findHighestAlarmSeverity(tenantId, entityId, alarmSearchStatus, alarmStatus); |
133 | 138 | } | ... | ... |
... | ... | @@ -313,7 +313,7 @@ public abstract class BaseDeviceProfileControllerTest extends AbstractController |
313 | 313 | Collections.sort(loadedDeviceProfileInfos, deviceProfileInfoIdComparator); |
314 | 314 | |
315 | 315 | List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream().map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), |
316 | - deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); | |
316 | + deviceProfile.getName(), deviceProfile.getImage(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); | |
317 | 317 | |
318 | 318 | Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); |
319 | 319 | ... | ... |
... | ... | @@ -53,6 +53,8 @@ public interface AlarmService { |
53 | 53 | |
54 | 54 | ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query); |
55 | 55 | |
56 | + ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); | |
57 | + | |
56 | 58 | AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, |
57 | 59 | AlarmStatus alarmStatus); |
58 | 60 | ... | ... |
... | ... | @@ -30,6 +30,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa |
30 | 30 | private TenantId tenantId; |
31 | 31 | @NoXss |
32 | 32 | private String title; |
33 | + private String image; | |
33 | 34 | @Valid |
34 | 35 | private Set<ShortCustomerInfo> assignedCustomers; |
35 | 36 | |
... | ... | @@ -45,6 +46,7 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa |
45 | 46 | super(dashboardInfo); |
46 | 47 | this.tenantId = dashboardInfo.getTenantId(); |
47 | 48 | this.title = dashboardInfo.getTitle(); |
49 | + this.image = dashboardInfo.getImage(); | |
48 | 50 | this.assignedCustomers = dashboardInfo.getAssignedCustomers(); |
49 | 51 | } |
50 | 52 | |
... | ... | @@ -64,6 +66,14 @@ public class DashboardInfo extends SearchTextBased<DashboardId> implements HasNa |
64 | 66 | this.title = title; |
65 | 67 | } |
66 | 68 | |
69 | + public String getImage() { | |
70 | + return image; | |
71 | + } | |
72 | + | |
73 | + public void setImage(String image) { | |
74 | + this.image = image; | |
75 | + } | |
76 | + | |
67 | 77 | public Set<ShortCustomerInfo> getAssignedCustomers() { |
68 | 78 | return assignedCustomers; |
69 | 79 | } | ... | ... |
... | ... | @@ -43,6 +43,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H |
43 | 43 | private String name; |
44 | 44 | @NoXss |
45 | 45 | private String description; |
46 | + private String image; | |
46 | 47 | private boolean isDefault; |
47 | 48 | private DeviceProfileType type; |
48 | 49 | private DeviceTransportType transportType; |
... | ... | @@ -74,6 +75,7 @@ public class DeviceProfile extends SearchTextBased<DeviceProfileId> implements H |
74 | 75 | this.tenantId = deviceProfile.getTenantId(); |
75 | 76 | this.name = deviceProfile.getName(); |
76 | 77 | this.description = deviceProfile.getDescription(); |
78 | + this.image = deviceProfile.getImage(); | |
77 | 79 | this.isDefault = deviceProfile.isDefault(); |
78 | 80 | this.defaultRuleChainId = deviceProfile.getDefaultRuleChainId(); |
79 | 81 | this.defaultQueueName = deviceProfile.getDefaultQueueName(); | ... | ... |
... | ... | @@ -30,21 +30,25 @@ import java.util.UUID; |
30 | 30 | @ToString(callSuper = true) |
31 | 31 | public class DeviceProfileInfo extends EntityInfo { |
32 | 32 | |
33 | + private final String image; | |
33 | 34 | private final DeviceProfileType type; |
34 | 35 | private final DeviceTransportType transportType; |
35 | 36 | |
36 | 37 | @JsonCreator |
37 | 38 | public DeviceProfileInfo(@JsonProperty("id") EntityId id, |
38 | 39 | @JsonProperty("name") String name, |
40 | + @JsonProperty("image") String image, | |
39 | 41 | @JsonProperty("type") DeviceProfileType type, |
40 | 42 | @JsonProperty("transportType") DeviceTransportType transportType) { |
41 | 43 | super(id, name); |
44 | + this.image = image; | |
42 | 45 | this.type = type; |
43 | 46 | this.transportType = transportType; |
44 | 47 | } |
45 | 48 | |
46 | - public DeviceProfileInfo(UUID uuid, String name, DeviceProfileType type, DeviceTransportType transportType) { | |
49 | + public DeviceProfileInfo(UUID uuid, String name, String image, DeviceProfileType type, DeviceTransportType transportType) { | |
47 | 50 | super(EntityIdFactory.getByTypeAndUuid(EntityType.DEVICE_PROFILE, uuid), name); |
51 | + this.image = image; | |
48 | 52 | this.type = type; |
49 | 53 | this.transportType = transportType; |
50 | 54 | } | ... | ... |
... | ... | @@ -48,6 +48,8 @@ public interface AlarmDao extends Dao<Alarm> { |
48 | 48 | |
49 | 49 | PageData<AlarmInfo> findAlarms(TenantId tenantId, AlarmQuery query); |
50 | 50 | |
51 | + PageData<AlarmInfo> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); | |
52 | + | |
51 | 53 | PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, |
52 | 54 | AlarmDataQuery query, Collection<EntityId> orderedEntityIds); |
53 | 55 | ... | ... |
... | ... | @@ -292,26 +292,39 @@ public class BaseAlarmService extends AbstractEntityService implements AlarmServ |
292 | 292 | public ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query) { |
293 | 293 | PageData<AlarmInfo> alarms = alarmDao.findAlarms(tenantId, query); |
294 | 294 | if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { |
295 | - List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size()); | |
296 | - for (AlarmInfo alarmInfo : alarms.getData()) { | |
297 | - alarmFutures.add(Futures.transform( | |
298 | - entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { | |
299 | - if (originatorName == null) { | |
300 | - originatorName = "Deleted"; | |
301 | - } | |
302 | - alarmInfo.setOriginatorName(originatorName); | |
303 | - return alarmInfo; | |
304 | - }, MoreExecutors.directExecutor() | |
305 | - )); | |
306 | - } | |
307 | - return Futures.transform(Futures.successfulAsList(alarmFutures), | |
308 | - alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), | |
309 | - alarms.hasNext()), MoreExecutors.directExecutor()); | |
295 | + return fetchAlarmsOriginators(tenantId, alarms); | |
310 | 296 | } |
311 | 297 | return Futures.immediateFuture(alarms); |
312 | 298 | } |
313 | 299 | |
314 | 300 | @Override |
301 | + public ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { | |
302 | + PageData<AlarmInfo> alarms = alarmDao.findCustomerAlarms(tenantId, customerId, query); | |
303 | + if (query.getFetchOriginator() != null && query.getFetchOriginator().booleanValue()) { | |
304 | + return fetchAlarmsOriginators(tenantId, alarms); | |
305 | + } | |
306 | + return Futures.immediateFuture(alarms); | |
307 | + } | |
308 | + | |
309 | + private ListenableFuture<PageData<AlarmInfo>> fetchAlarmsOriginators(TenantId tenantId, PageData<AlarmInfo> alarms) { | |
310 | + List<ListenableFuture<AlarmInfo>> alarmFutures = new ArrayList<>(alarms.getData().size()); | |
311 | + for (AlarmInfo alarmInfo : alarms.getData()) { | |
312 | + alarmFutures.add(Futures.transform( | |
313 | + entityService.fetchEntityNameAsync(tenantId, alarmInfo.getOriginator()), originatorName -> { | |
314 | + if (originatorName == null) { | |
315 | + originatorName = "Deleted"; | |
316 | + } | |
317 | + alarmInfo.setOriginatorName(originatorName); | |
318 | + return alarmInfo; | |
319 | + }, MoreExecutors.directExecutor() | |
320 | + )); | |
321 | + } | |
322 | + return Futures.transform(Futures.successfulAsList(alarmFutures), | |
323 | + alarmInfos -> new PageData<>(alarmInfos, alarms.getTotalPages(), alarms.getTotalElements(), | |
324 | + alarms.hasNext()), MoreExecutors.directExecutor()); | |
325 | + } | |
326 | + | |
327 | + @Override | |
315 | 328 | public AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, |
316 | 329 | AlarmStatus alarmStatus) { |
317 | 330 | Set<AlarmStatus> statusList = null; | ... | ... |
... | ... | @@ -170,6 +170,7 @@ public class ModelConstants { |
170 | 170 | public static final String DEVICE_PROFILE_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; |
171 | 171 | public static final String DEVICE_PROFILE_NAME_PROPERTY = "name"; |
172 | 172 | public static final String DEVICE_PROFILE_TYPE_PROPERTY = "type"; |
173 | + public static final String DEVICE_PROFILE_IMAGE_PROPERTY = "image"; | |
173 | 174 | public static final String DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY = "transport_type"; |
174 | 175 | public static final String DEVICE_PROFILE_PROVISION_TYPE_PROPERTY = "provision_type"; |
175 | 176 | public static final String DEVICE_PROFILE_PROFILE_DATA_PROPERTY = "profile_data"; |
... | ... | @@ -333,6 +334,7 @@ public class ModelConstants { |
333 | 334 | public static final String DASHBOARD_COLUMN_FAMILY_NAME = "dashboard"; |
334 | 335 | public static final String DASHBOARD_TENANT_ID_PROPERTY = TENANT_ID_PROPERTY; |
335 | 336 | public static final String DASHBOARD_TITLE_PROPERTY = TITLE_PROPERTY; |
337 | + public static final String DASHBOARD_IMAGE_PROPERTY = "image"; | |
336 | 338 | public static final String DASHBOARD_CONFIGURATION_PROPERTY = "configuration"; |
337 | 339 | public static final String DASHBOARD_ASSIGNED_CUSTOMERS_PROPERTY = "assigned_customers"; |
338 | 340 | ... | ... |
... | ... | @@ -58,7 +58,10 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S |
58 | 58 | |
59 | 59 | @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) |
60 | 60 | private String title; |
61 | - | |
61 | + | |
62 | + @Column(name = ModelConstants.DASHBOARD_IMAGE_PROPERTY) | |
63 | + private String image; | |
64 | + | |
62 | 65 | @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) |
63 | 66 | private String searchText; |
64 | 67 | |
... | ... | @@ -82,6 +85,7 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S |
82 | 85 | this.tenantId = dashboard.getTenantId().getId(); |
83 | 86 | } |
84 | 87 | this.title = dashboard.getTitle(); |
88 | + this.image = dashboard.getImage(); | |
85 | 89 | if (dashboard.getAssignedCustomers() != null) { |
86 | 90 | try { |
87 | 91 | this.assignedCustomers = objectMapper.writeValueAsString(dashboard.getAssignedCustomers()); |
... | ... | @@ -110,6 +114,7 @@ public final class DashboardEntity extends BaseSqlEntity<Dashboard> implements S |
110 | 114 | dashboard.setTenantId(new TenantId(tenantId)); |
111 | 115 | } |
112 | 116 | dashboard.setTitle(title); |
117 | + dashboard.setImage(image); | |
113 | 118 | if (!StringUtils.isEmpty(assignedCustomers)) { |
114 | 119 | try { |
115 | 120 | dashboard.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); | ... | ... |
... | ... | @@ -54,6 +54,9 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements |
54 | 54 | @Column(name = ModelConstants.DASHBOARD_TITLE_PROPERTY) |
55 | 55 | private String title; |
56 | 56 | |
57 | + @Column(name = ModelConstants.DASHBOARD_IMAGE_PROPERTY) | |
58 | + private String image; | |
59 | + | |
57 | 60 | @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) |
58 | 61 | private String searchText; |
59 | 62 | |
... | ... | @@ -73,6 +76,7 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements |
73 | 76 | this.tenantId = dashboardInfo.getTenantId().getId(); |
74 | 77 | } |
75 | 78 | this.title = dashboardInfo.getTitle(); |
79 | + this.image = dashboardInfo.getImage(); | |
76 | 80 | if (dashboardInfo.getAssignedCustomers() != null) { |
77 | 81 | try { |
78 | 82 | this.assignedCustomers = objectMapper.writeValueAsString(dashboardInfo.getAssignedCustomers()); |
... | ... | @@ -104,6 +108,7 @@ public class DashboardInfoEntity extends BaseSqlEntity<DashboardInfo> implements |
104 | 108 | dashboardInfo.setTenantId(new TenantId(tenantId)); |
105 | 109 | } |
106 | 110 | dashboardInfo.setTitle(title); |
111 | + dashboardInfo.setImage(image); | |
107 | 112 | if (!StringUtils.isEmpty(assignedCustomers)) { |
108 | 113 | try { |
109 | 114 | dashboardInfo.setAssignedCustomers(objectMapper.readValue(assignedCustomers, assignedCustomersType)); | ... | ... |
... | ... | @@ -60,6 +60,9 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl |
60 | 60 | @Column(name = ModelConstants.DEVICE_PROFILE_TYPE_PROPERTY) |
61 | 61 | private DeviceProfileType type; |
62 | 62 | |
63 | + @Column(name = ModelConstants.DEVICE_PROFILE_IMAGE_PROPERTY) | |
64 | + private String image; | |
65 | + | |
63 | 66 | @Enumerated(EnumType.STRING) |
64 | 67 | @Column(name = ModelConstants.DEVICE_PROFILE_TRANSPORT_TYPE_PROPERTY) |
65 | 68 | private DeviceTransportType transportType; |
... | ... | @@ -110,6 +113,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl |
110 | 113 | this.setCreatedTime(deviceProfile.getCreatedTime()); |
111 | 114 | this.name = deviceProfile.getName(); |
112 | 115 | this.type = deviceProfile.getType(); |
116 | + this.image = deviceProfile.getImage(); | |
113 | 117 | this.transportType = deviceProfile.getTransportType(); |
114 | 118 | this.provisionType = deviceProfile.getProvisionType(); |
115 | 119 | this.description = deviceProfile.getDescription(); |
... | ... | @@ -151,6 +155,7 @@ public final class DeviceProfileEntity extends BaseSqlEntity<DeviceProfile> impl |
151 | 155 | } |
152 | 156 | deviceProfile.setName(name); |
153 | 157 | deviceProfile.setType(type); |
158 | + deviceProfile.setImage(image); | |
154 | 159 | deviceProfile.setTransportType(transportType); |
155 | 160 | deviceProfile.setProvisionType(provisionType); |
156 | 161 | deviceProfile.setDescription(description); | ... | ... |
... | ... | @@ -94,6 +94,70 @@ public interface AlarmRepository extends CrudRepository<AlarmEntity, UUID> { |
94 | 94 | @Param("searchText") String searchText, |
95 | 95 | Pageable pageable); |
96 | 96 | |
97 | + @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " + | |
98 | + "WHERE a.tenantId = :tenantId " + | |
99 | + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + | |
100 | + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + | |
101 | + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + | |
102 | + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
103 | + " OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
104 | + " OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) ", | |
105 | + countQuery = "" + | |
106 | + "SELECT count(a) " + | |
107 | + "FROM AlarmEntity a " + | |
108 | + "WHERE a.tenantId = :tenantId " + | |
109 | + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + | |
110 | + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + | |
111 | + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + | |
112 | + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
113 | + " OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
114 | + " OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) ") | |
115 | + Page<AlarmInfoEntity> findAllAlarms(@Param("tenantId") UUID tenantId, | |
116 | + @Param("startTime") Long startTime, | |
117 | + @Param("endTime") Long endTime, | |
118 | + @Param("alarmStatuses") Set<AlarmStatus> alarmStatuses, | |
119 | + @Param("searchText") String searchText, | |
120 | + Pageable pageable); | |
121 | + | |
122 | + @Query(value = "SELECT new org.thingsboard.server.dao.model.sql.AlarmInfoEntity(a) FROM AlarmEntity a " + | |
123 | + "WHERE a.tenantId = :tenantId " + | |
124 | + "AND (" + | |
125 | + "a.originatorId IN (SELECT d.id from DeviceEntity d WHERE d.customerId = :customerId) " + | |
126 | + "OR a.originatorId IN (SELECT asset.id from AssetEntity asset WHERE asset.customerId = :customerId) " + | |
127 | + "OR a.originatorId IN (SELECT u.id from UserEntity u WHERE u.customerId = :customerId) " + | |
128 | + "OR a.originatorId = :customerId" + | |
129 | + ") " + | |
130 | + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + | |
131 | + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + | |
132 | + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + | |
133 | + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
134 | + " OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
135 | + " OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) " | |
136 | + , | |
137 | + countQuery = "" + | |
138 | + "SELECT count(a) " + | |
139 | + "FROM AlarmEntity a " + | |
140 | + "WHERE a.tenantId = :tenantId " + | |
141 | + "AND (" + | |
142 | + "a.originatorId IN (SELECT d.id from DeviceEntity d WHERE d.customerId = :customerId) " + | |
143 | + "OR a.originatorId IN (SELECT asset.id from AssetEntity asset WHERE asset.customerId = :customerId) " + | |
144 | + "OR a.originatorId IN (SELECT u.id from UserEntity u WHERE u.customerId = :customerId) " + | |
145 | + "OR a.originatorId = :customerId" + | |
146 | + ") " + | |
147 | + "AND (:startTime IS NULL OR a.createdTime >= :startTime) " + | |
148 | + "AND (:endTime IS NULL OR a.createdTime <= :endTime) " + | |
149 | + "AND ((:alarmStatuses) IS NULL OR a.status in (:alarmStatuses)) " + | |
150 | + "AND (LOWER(a.type) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
151 | + " OR LOWER(a.severity) LIKE LOWER(CONCAT(:searchText, '%')) " + | |
152 | + " OR LOWER(a.status) LIKE LOWER(CONCAT(:searchText, '%'))) ") | |
153 | + Page<AlarmInfoEntity> findCustomerAlarms(@Param("tenantId") UUID tenantId, | |
154 | + @Param("customerId") UUID customerId, | |
155 | + @Param("startTime") Long startTime, | |
156 | + @Param("endTime") Long endTime, | |
157 | + @Param("alarmStatuses") Set<AlarmStatus> alarmStatuses, | |
158 | + @Param("searchText") String searchText, | |
159 | + Pageable pageable); | |
160 | + | |
97 | 161 | @Query(value = "SELECT a.severity FROM AlarmEntity a " + |
98 | 162 | "LEFT JOIN RelationEntity re ON a.id = re.toId " + |
99 | 163 | "AND re.relationTypeGroup = 'ALARM' " + | ... | ... |
... | ... | @@ -103,11 +103,46 @@ public class JpaAlarmDao extends JpaAbstractDao<AlarmEntity, Alarm> implements A |
103 | 103 | } else if (query.getStatus() != null) { |
104 | 104 | statusSet = Collections.singleton(query.getStatus()); |
105 | 105 | } |
106 | + if (affectedEntity != null) { | |
107 | + return DaoUtil.toPageData( | |
108 | + alarmRepository.findAlarms( | |
109 | + tenantId.getId(), | |
110 | + affectedEntity.getId(), | |
111 | + affectedEntity.getEntityType().name(), | |
112 | + query.getPageLink().getStartTime(), | |
113 | + query.getPageLink().getEndTime(), | |
114 | + statusSet, | |
115 | + Objects.toString(query.getPageLink().getTextSearch(), ""), | |
116 | + DaoUtil.toPageable(query.getPageLink()) | |
117 | + ) | |
118 | + ); | |
119 | + } else { | |
120 | + return DaoUtil.toPageData( | |
121 | + alarmRepository.findAllAlarms( | |
122 | + tenantId.getId(), | |
123 | + query.getPageLink().getStartTime(), | |
124 | + query.getPageLink().getEndTime(), | |
125 | + statusSet, | |
126 | + Objects.toString(query.getPageLink().getTextSearch(), ""), | |
127 | + DaoUtil.toPageable(query.getPageLink()) | |
128 | + ) | |
129 | + ); | |
130 | + } | |
131 | + } | |
132 | + | |
133 | + @Override | |
134 | + public PageData<AlarmInfo> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query) { | |
135 | + log.trace("Try to find customer alarms by status [{}] and pageLink [{}]", query.getStatus(), query.getPageLink()); | |
136 | + Set<AlarmStatus> statusSet = null; | |
137 | + if (query.getSearchStatus() != null) { | |
138 | + statusSet = query.getSearchStatus().getStatuses(); | |
139 | + } else if (query.getStatus() != null) { | |
140 | + statusSet = Collections.singleton(query.getStatus()); | |
141 | + } | |
106 | 142 | return DaoUtil.toPageData( |
107 | - alarmRepository.findAlarms( | |
143 | + alarmRepository.findCustomerAlarms( | |
108 | 144 | tenantId.getId(), |
109 | - affectedEntity.getId(), | |
110 | - affectedEntity.getEntityType().name(), | |
145 | + customerId.getId(), | |
111 | 146 | query.getPageLink().getStartTime(), |
112 | 147 | query.getPageLink().getEndTime(), |
113 | 148 | statusSet, | ... | ... |
... | ... | @@ -28,7 +28,7 @@ import java.util.UUID; |
28 | 28 | |
29 | 29 | public interface DeviceProfileRepository extends PagingAndSortingRepository<DeviceProfileEntity, UUID> { |
30 | 30 | |
31 | - @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + | |
31 | + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.type, d.transportType) " + | |
32 | 32 | "FROM DeviceProfileEntity d " + |
33 | 33 | "WHERE d.id = :deviceProfileId") |
34 | 34 | DeviceProfileInfo findDeviceProfileInfoById(@Param("deviceProfileId") UUID deviceProfileId); |
... | ... | @@ -39,14 +39,14 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi |
39 | 39 | @Param("textSearch") String textSearch, |
40 | 40 | Pageable pageable); |
41 | 41 | |
42 | - @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + | |
42 | + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.type, d.transportType) " + | |
43 | 43 | "FROM DeviceProfileEntity d WHERE " + |
44 | 44 | "d.tenantId = :tenantId AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") |
45 | 45 | Page<DeviceProfileInfo> findDeviceProfileInfos(@Param("tenantId") UUID tenantId, |
46 | 46 | @Param("textSearch") String textSearch, |
47 | 47 | Pageable pageable); |
48 | 48 | |
49 | - @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + | |
49 | + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.type, d.transportType) " + | |
50 | 50 | "FROM DeviceProfileEntity d WHERE " + |
51 | 51 | "d.tenantId = :tenantId AND d.transportType = :transportType AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") |
52 | 52 | Page<DeviceProfileInfo> findDeviceProfileInfos(@Param("tenantId") UUID tenantId, |
... | ... | @@ -58,7 +58,7 @@ public interface DeviceProfileRepository extends PagingAndSortingRepository<Devi |
58 | 58 | "WHERE d.tenantId = :tenantId AND d.isDefault = true") |
59 | 59 | DeviceProfileEntity findByDefaultTrueAndTenantId(@Param("tenantId") UUID tenantId); |
60 | 60 | |
61 | - @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.type, d.transportType) " + | |
61 | + @Query("SELECT new org.thingsboard.server.common.data.DeviceProfileInfo(d.id, d.name, d.image, d.type, d.transportType) " + | |
62 | 62 | "FROM DeviceProfileEntity d " + |
63 | 63 | "WHERE d.tenantId = :tenantId AND d.isDefault = true") |
64 | 64 | DeviceProfileInfo findDefaultDeviceProfileInfo(@Param("tenantId") UUID tenantId); | ... | ... |
... | ... | @@ -118,7 +118,8 @@ CREATE TABLE IF NOT EXISTS dashboard ( |
118 | 118 | assigned_customers varchar(1000000), |
119 | 119 | search_text varchar(255), |
120 | 120 | tenant_id uuid, |
121 | - title varchar(255) | |
121 | + title varchar(255), | |
122 | + image varchar(1000000) | |
122 | 123 | ); |
123 | 124 | |
124 | 125 | CREATE TABLE IF NOT EXISTS rule_chain ( |
... | ... | @@ -182,6 +183,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( |
182 | 183 | created_time bigint NOT NULL, |
183 | 184 | name varchar(255), |
184 | 185 | type varchar(255), |
186 | + image varchar(1000000), | |
185 | 187 | transport_type varchar(255), |
186 | 188 | provision_type varchar(255), |
187 | 189 | profile_data jsonb, | ... | ... |
... | ... | @@ -136,7 +136,8 @@ CREATE TABLE IF NOT EXISTS dashboard ( |
136 | 136 | assigned_customers varchar(1000000), |
137 | 137 | search_text varchar(255), |
138 | 138 | tenant_id uuid, |
139 | - title varchar(255) | |
139 | + title varchar(255), | |
140 | + image varchar(1000000) | |
140 | 141 | ); |
141 | 142 | |
142 | 143 | CREATE TABLE IF NOT EXISTS rule_chain ( |
... | ... | @@ -201,6 +202,7 @@ CREATE TABLE IF NOT EXISTS device_profile ( |
201 | 202 | created_time bigint NOT NULL, |
202 | 203 | name varchar(255), |
203 | 204 | type varchar(255), |
205 | + image varchar(1000000), | |
204 | 206 | transport_type varchar(255), |
205 | 207 | provision_type varchar(255), |
206 | 208 | profile_data jsonb, | ... | ... |
... | ... | @@ -333,7 +333,7 @@ public class BaseDeviceProfileServiceTest extends AbstractServiceTest { |
333 | 333 | |
334 | 334 | List<DeviceProfileInfo> deviceProfileInfos = deviceProfiles.stream() |
335 | 335 | .map(deviceProfile -> new DeviceProfileInfo(deviceProfile.getId(), |
336 | - deviceProfile.getName(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); | |
336 | + deviceProfile.getName(), deviceProfile.getImage(), deviceProfile.getType(), deviceProfile.getTransportType())).collect(Collectors.toList()); | |
337 | 337 | |
338 | 338 | Assert.assertEquals(deviceProfileInfos, loadedDeviceProfileInfos); |
339 | 339 | ... | ... |
... | ... | @@ -57,6 +57,8 @@ public interface RuleEngineAlarmService { |
57 | 57 | |
58 | 58 | ListenableFuture<PageData<AlarmInfo>> findAlarms(TenantId tenantId, AlarmQuery query); |
59 | 59 | |
60 | + ListenableFuture<PageData<AlarmInfo>> findCustomerAlarms(TenantId tenantId, CustomerId customerId, AlarmQuery query); | |
61 | + | |
60 | 62 | AlarmSeverity findHighestAlarmSeverity(TenantId tenantId, EntityId entityId, AlarmSearchStatus alarmSearchStatus, AlarmStatus alarmStatus); |
61 | 63 | |
62 | 64 | PageData<AlarmData> findAlarmDataByQueryForEntities(TenantId tenantId, CustomerId customerId, AlarmDataQuery query, Collection<EntityId> orderedEntityIds); | ... | ... |
... | ... | @@ -60,6 +60,11 @@ |
60 | 60 | {{ 'device-profile.type-required' | translate }} |
61 | 61 | </mat-error> |
62 | 62 | </mat-form-field> |
63 | + <tb-image-input fxFlex | |
64 | + label="{{'device-profile.image' | translate}}" | |
65 | + maxSizeByte="524288" | |
66 | + formControlName="image"> | |
67 | + </tb-image-input> | |
63 | 68 | <mat-form-field class="mat-block"> |
64 | 69 | <mat-label translate>device-profile.description</mat-label> |
65 | 70 | <textarea matInput formControlName="description" rows="2"></textarea> | ... | ... |
... | ... | @@ -106,6 +106,7 @@ export class AddDeviceProfileDialogComponent extends |
106 | 106 | { |
107 | 107 | name: [data.deviceProfileName, [Validators.required]], |
108 | 108 | type: [DeviceProfileType.DEFAULT, [Validators.required]], |
109 | + image: [null, []], | |
109 | 110 | defaultRuleChainId: [null, []], |
110 | 111 | defaultQueueName: ['', []], |
111 | 112 | description: ['', []] |
... | ... | @@ -183,6 +184,7 @@ export class AddDeviceProfileDialogComponent extends |
183 | 184 | const deviceProfile: DeviceProfile = { |
184 | 185 | name: this.deviceProfileDetailsFormGroup.get('name').value, |
185 | 186 | type: this.deviceProfileDetailsFormGroup.get('type').value, |
187 | + image: this.deviceProfileDetailsFormGroup.get('image').value, | |
186 | 188 | transportType: this.transportConfigFormGroup.get('transportType').value, |
187 | 189 | provisionType: deviceProvisionConfiguration.type, |
188 | 190 | provisionDeviceKey, | ... | ... |
... | ... | @@ -86,6 +86,11 @@ |
86 | 86 | {{ 'device-profile.type-required' | translate }} |
87 | 87 | </mat-error> |
88 | 88 | </mat-form-field> |
89 | + <tb-image-input fxFlex | |
90 | + label="{{'device-profile.image' | translate}}" | |
91 | + maxSizeByte="524288" | |
92 | + formControlName="image"> | |
93 | + </tb-image-input> | |
89 | 94 | <mat-form-field class="mat-block"> |
90 | 95 | <mat-label translate>device-profile.description</mat-label> |
91 | 96 | <textarea matInput formControlName="description" rows="2"></textarea> | ... | ... |
... | ... | @@ -103,6 +103,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { |
103 | 103 | { |
104 | 104 | name: [entity ? entity.name : '', [Validators.required]], |
105 | 105 | type: [entity ? entity.type : null, [Validators.required]], |
106 | + image: [entity ? entity.image : null], | |
106 | 107 | transportType: [entity ? entity.transportType : null, [Validators.required]], |
107 | 108 | profileData: this.fb.group({ |
108 | 109 | configuration: [entity && !this.isAdd ? entity.profileData?.configuration : {}, Validators.required], |
... | ... | @@ -178,6 +179,7 @@ export class DeviceProfileComponent extends EntityComponent<DeviceProfile> { |
178 | 179 | }; |
179 | 180 | this.entityForm.patchValue({name: entity.name}); |
180 | 181 | this.entityForm.patchValue({type: entity.type}, {emitEvent: false}); |
182 | + this.entityForm.patchValue({image: entity.image}, {emitEvent: false}); | |
181 | 183 | this.entityForm.patchValue({transportType: entity.transportType}, {emitEvent: false}); |
182 | 184 | this.entityForm.patchValue({provisionType: entity.provisionType}, {emitEvent: false}); |
183 | 185 | this.entityForm.patchValue({provisionDeviceKey: entity.provisionDeviceKey}, {emitEvent: false}); | ... | ... |
... | ... | @@ -105,6 +105,11 @@ |
105 | 105 | {{ 'dashboard.title-required' | translate }} |
106 | 106 | </mat-error> |
107 | 107 | </mat-form-field> |
108 | + <tb-image-input fxFlex | |
109 | + label="{{'dashboard.image' | translate}}" | |
110 | + maxSizeByte="524288" | |
111 | + formControlName="image"> | |
112 | + </tb-image-input> | |
108 | 113 | <div formGroupName="configuration" fxLayout="column"> |
109 | 114 | <mat-form-field class="mat-block"> |
110 | 115 | <mat-label translate>dashboard.description</mat-label> | ... | ... |
... | ... | @@ -80,6 +80,7 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> { |
80 | 80 | return this.fb.group( |
81 | 81 | { |
82 | 82 | title: [entity ? entity.title : '', [Validators.required]], |
83 | + image: [entity ? entity.image : null], | |
83 | 84 | configuration: this.fb.group( |
84 | 85 | { |
85 | 86 | description: [entity && entity.configuration ? entity.configuration.description : ''], |
... | ... | @@ -92,6 +93,7 @@ export class DashboardFormComponent extends EntityComponent<Dashboard> { |
92 | 93 | updateForm(entity: Dashboard) { |
93 | 94 | this.updateFields(entity); |
94 | 95 | this.entityForm.patchValue({title: entity.title}); |
96 | + this.entityForm.patchValue({image: entity.image}); | |
95 | 97 | this.entityForm.patchValue({configuration: {description: entity.configuration ? entity.configuration.description : ''}}); |
96 | 98 | } |
97 | 99 | ... | ... |
... | ... | @@ -21,10 +21,10 @@ |
21 | 21 | [flowConfig]="{singleFile: true, allowDuplicateUploads: true}"> |
22 | 22 | <div class="tb-image-select-container"> |
23 | 23 | <div *ngIf="showPreview" class="tb-image-preview-container"> |
24 | - <div *ngIf="!safeImageUrl;else elseBlock" translate>dashboard.no-image</div> | |
24 | + <div *ngIf="!safeImageUrl; else elseBlock">{{ (disabled ? 'dashboard.empty-image' : 'dashboard.no-image') | translate }}</div> | |
25 | 25 | <ng-template #elseBlock><img class="tb-image-preview" [src]="safeImageUrl" /></ng-template> |
26 | 26 | </div> |
27 | - <div *ngIf="showClearButton" class="tb-image-clear-container"> | |
27 | + <div *ngIf="showClearButton && !disabled" class="tb-image-clear-container"> | |
28 | 28 | <button mat-button mat-icon-button color="primary" |
29 | 29 | type="button" |
30 | 30 | (click)="clearImage()" |
... | ... | @@ -34,7 +34,7 @@ |
34 | 34 | <mat-icon>close</mat-icon> |
35 | 35 | </button> |
36 | 36 | </div> |
37 | - <div class="drop-area tb-flow-drop" | |
37 | + <div *ngIf="!disabled" class="drop-area tb-flow-drop" | |
38 | 38 | flowDrop |
39 | 39 | [flow]="flow.flowJs"> |
40 | 40 | <label for="{{inputId}}" translate>dashboard.drop-image</label> |
... | ... | @@ -42,5 +42,5 @@ |
42 | 42 | </div> |
43 | 43 | </div> |
44 | 44 | </ng-container> |
45 | - <div class="tb-hint" *ngIf="maxSizeByte" translate [translateParams]="{ size: maxSizeByte | fileSize}">dashboard.maximum-upload-file-size</div> | |
45 | + <div class="tb-hint" *ngIf="maxSizeByte && !disabled" translate [translateParams]="{ size: maxSizeByte | fileSize}">dashboard.maximum-upload-file-size</div> | |
46 | 46 | </div> | ... | ... |
... | ... | @@ -492,6 +492,7 @@ export interface DeviceProfile extends BaseData<DeviceProfileId> { |
492 | 492 | description?: string; |
493 | 493 | default?: boolean; |
494 | 494 | type: DeviceProfileType; |
495 | + image?: string; | |
495 | 496 | transportType: DeviceTransportType; |
496 | 497 | provisionType: DeviceProvisionType; |
497 | 498 | provisionDeviceKey?: string; | ... | ... |
... | ... | @@ -666,6 +666,7 @@ |
666 | 666 | "no-widgets": "No widgets configured", |
667 | 667 | "add-widget": "Add new widget", |
668 | 668 | "title": "Title", |
669 | + "image": "Dashboard image", | |
669 | 670 | "select-widget-title": "Select widget", |
670 | 671 | "select-widget-value": "{{title}}: select widget", |
671 | 672 | "select-widget-subtitle": "List of available widget types", |
... | ... | @@ -712,6 +713,7 @@ |
712 | 713 | "background-image": "Background image", |
713 | 714 | "background-size-mode": "Background size mode", |
714 | 715 | "no-image": "No image selected", |
716 | + "empty-image": "No image", | |
715 | 717 | "drop-image": "Drop an image or click to select a file to upload.", |
716 | 718 | "maximum-upload-file-size": "Maximum upload file size: {{ size }}", |
717 | 719 | "cannot-upload-file": "Cannot upload file", |
... | ... | @@ -1028,6 +1030,7 @@ |
1028 | 1030 | "type": "Profile type", |
1029 | 1031 | "type-required": "Profile type is required.", |
1030 | 1032 | "type-default": "Default", |
1033 | + "image": "Device profile image", | |
1031 | 1034 | "transport-type": "Transport type", |
1032 | 1035 | "transport-type-required": "Transport type is required.", |
1033 | 1036 | "transport-type-default": "Default", | ... | ... |