Showing
43 changed files
with
1968 additions
and
143 deletions
@@ -28,16 +28,7 @@ import org.springframework.security.core.Authentication; | @@ -28,16 +28,7 @@ import org.springframework.security.core.Authentication; | ||
28 | import org.springframework.security.core.context.SecurityContextHolder; | 28 | import org.springframework.security.core.context.SecurityContextHolder; |
29 | import org.springframework.web.bind.annotation.ExceptionHandler; | 29 | import org.springframework.web.bind.annotation.ExceptionHandler; |
30 | import org.thingsboard.server.actors.service.ActorService; | 30 | import org.thingsboard.server.actors.service.ActorService; |
31 | -import org.thingsboard.server.common.data.Customer; | ||
32 | -import org.thingsboard.server.common.data.Dashboard; | ||
33 | -import org.thingsboard.server.common.data.DashboardInfo; | ||
34 | -import org.thingsboard.server.common.data.DataConstants; | ||
35 | -import org.thingsboard.server.common.data.Device; | ||
36 | -import org.thingsboard.server.common.data.EntityType; | ||
37 | -import org.thingsboard.server.common.data.EntityView; | ||
38 | -import org.thingsboard.server.common.data.HasName; | ||
39 | -import org.thingsboard.server.common.data.Tenant; | ||
40 | -import org.thingsboard.server.common.data.User; | 31 | +import org.thingsboard.server.common.data.*; |
41 | import org.thingsboard.server.common.data.alarm.Alarm; | 32 | import org.thingsboard.server.common.data.alarm.Alarm; |
42 | import org.thingsboard.server.common.data.alarm.AlarmId; | 33 | import org.thingsboard.server.common.data.alarm.AlarmId; |
43 | import org.thingsboard.server.common.data.alarm.AlarmInfo; | 34 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
@@ -381,6 +372,18 @@ public abstract class BaseController { | @@ -381,6 +372,18 @@ public abstract class BaseController { | ||
381 | } | 372 | } |
382 | } | 373 | } |
383 | 374 | ||
375 | + DeviceInfo checkDeviceInfoId(DeviceId deviceId, Operation operation) throws ThingsboardException { | ||
376 | + try { | ||
377 | + validateId(deviceId, "Incorrect deviceId " + deviceId); | ||
378 | + DeviceInfo device = deviceService.findDeviceInfoById(getCurrentUser().getTenantId(), deviceId); | ||
379 | + checkNotNull(device); | ||
380 | + accessControlService.checkPermission(getCurrentUser(), Resource.DEVICE, operation, deviceId, device); | ||
381 | + return device; | ||
382 | + } catch (Exception e) { | ||
383 | + throw handleException(e, false); | ||
384 | + } | ||
385 | + } | ||
386 | + | ||
384 | protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { | 387 | protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { |
385 | try { | 388 | try { |
386 | validateId(entityViewId, "Incorrect entityViewId " + entityViewId); | 389 | validateId(entityViewId, "Incorrect entityViewId " + entityViewId); |
@@ -30,11 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody; | @@ -30,11 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody; | ||
30 | import org.springframework.web.bind.annotation.ResponseStatus; | 30 | import org.springframework.web.bind.annotation.ResponseStatus; |
31 | import org.springframework.web.bind.annotation.RestController; | 31 | import org.springframework.web.bind.annotation.RestController; |
32 | import org.springframework.web.context.request.async.DeferredResult; | 32 | import org.springframework.web.context.request.async.DeferredResult; |
33 | -import org.thingsboard.server.common.data.Customer; | ||
34 | -import org.thingsboard.server.common.data.DataConstants; | ||
35 | -import org.thingsboard.server.common.data.Device; | ||
36 | -import org.thingsboard.server.common.data.EntitySubtype; | ||
37 | -import org.thingsboard.server.common.data.EntityType; | 33 | +import org.thingsboard.server.common.data.*; |
38 | import org.thingsboard.server.common.data.audit.ActionType; | 34 | import org.thingsboard.server.common.data.audit.ActionType; |
39 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; | 35 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
40 | import org.thingsboard.server.common.data.exception.ThingsboardException; | 36 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
@@ -80,6 +76,19 @@ public class DeviceController extends BaseController { | @@ -80,6 +76,19 @@ public class DeviceController extends BaseController { | ||
80 | } | 76 | } |
81 | 77 | ||
82 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | 78 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
79 | + @RequestMapping(value = "/device/info/{deviceId}", method = RequestMethod.GET) | ||
80 | + @ResponseBody | ||
81 | + public DeviceInfo getDeviceInfoById(@PathVariable(DEVICE_ID) String strDeviceId) throws ThingsboardException { | ||
82 | + checkParameter(DEVICE_ID, strDeviceId); | ||
83 | + try { | ||
84 | + DeviceId deviceId = new DeviceId(toUUID(strDeviceId)); | ||
85 | + return checkDeviceInfoId(deviceId, Operation.READ); | ||
86 | + } catch (Exception e) { | ||
87 | + throw handleException(e); | ||
88 | + } | ||
89 | + } | ||
90 | + | ||
91 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | ||
83 | @RequestMapping(value = "/device", method = RequestMethod.POST) | 92 | @RequestMapping(value = "/device", method = RequestMethod.POST) |
84 | @ResponseBody | 93 | @ResponseBody |
85 | public Device saveDevice(@RequestBody Device device) throws ThingsboardException { | 94 | public Device saveDevice(@RequestBody Device device) throws ThingsboardException { |
@@ -288,6 +297,29 @@ public class DeviceController extends BaseController { | @@ -288,6 +297,29 @@ public class DeviceController extends BaseController { | ||
288 | } | 297 | } |
289 | 298 | ||
290 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") | 299 | @PreAuthorize("hasAuthority('TENANT_ADMIN')") |
300 | + @RequestMapping(value = "/tenant/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) | ||
301 | + @ResponseBody | ||
302 | + public PageData<DeviceInfo> getTenantDeviceInfos( | ||
303 | + @RequestParam int pageSize, | ||
304 | + @RequestParam int page, | ||
305 | + @RequestParam(required = false) String type, | ||
306 | + @RequestParam(required = false) String textSearch, | ||
307 | + @RequestParam(required = false) String sortProperty, | ||
308 | + @RequestParam(required = false) String sortOrder) throws ThingsboardException { | ||
309 | + try { | ||
310 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
311 | + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | ||
312 | + if (type != null && type.trim().length() > 0) { | ||
313 | + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndType(tenantId, type, pageLink)); | ||
314 | + } else { | ||
315 | + return checkNotNull(deviceService.findDeviceInfosByTenantId(tenantId, pageLink)); | ||
316 | + } | ||
317 | + } catch (Exception e) { | ||
318 | + throw handleException(e); | ||
319 | + } | ||
320 | + } | ||
321 | + | ||
322 | + @PreAuthorize("hasAuthority('TENANT_ADMIN')") | ||
291 | @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) | 323 | @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) |
292 | @ResponseBody | 324 | @ResponseBody |
293 | public Device getTenantDevice( | 325 | public Device getTenantDevice( |
@@ -328,6 +360,33 @@ public class DeviceController extends BaseController { | @@ -328,6 +360,33 @@ public class DeviceController extends BaseController { | ||
328 | } | 360 | } |
329 | 361 | ||
330 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | 362 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
363 | + @RequestMapping(value = "/customer/{customerId}/deviceInfos", params = {"pageSize", "page"}, method = RequestMethod.GET) | ||
364 | + @ResponseBody | ||
365 | + public PageData<DeviceInfo> getCustomerDeviceInfos( | ||
366 | + @PathVariable("customerId") String strCustomerId, | ||
367 | + @RequestParam int pageSize, | ||
368 | + @RequestParam int page, | ||
369 | + @RequestParam(required = false) String type, | ||
370 | + @RequestParam(required = false) String textSearch, | ||
371 | + @RequestParam(required = false) String sortProperty, | ||
372 | + @RequestParam(required = false) String sortOrder) throws ThingsboardException { | ||
373 | + checkParameter("customerId", strCustomerId); | ||
374 | + try { | ||
375 | + TenantId tenantId = getCurrentUser().getTenantId(); | ||
376 | + CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | ||
377 | + checkCustomerId(customerId, Operation.READ); | ||
378 | + PageLink pageLink = createPageLink(pageSize, page, textSearch, sortProperty, sortOrder); | ||
379 | + if (type != null && type.trim().length() > 0) { | ||
380 | + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink)); | ||
381 | + } else { | ||
382 | + return checkNotNull(deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink)); | ||
383 | + } | ||
384 | + } catch (Exception e) { | ||
385 | + throw handleException(e); | ||
386 | + } | ||
387 | + } | ||
388 | + | ||
389 | + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | ||
331 | @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) | 390 | @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) |
332 | @ResponseBody | 391 | @ResponseBody |
333 | public List<Device> getDevicesByIds( | 392 | public List<Device> getDevicesByIds( |
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; | @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; | ||
17 | 17 | ||
18 | import com.google.common.util.concurrent.ListenableFuture; | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | import org.thingsboard.server.common.data.Device; | 19 | import org.thingsboard.server.common.data.Device; |
20 | +import org.thingsboard.server.common.data.DeviceInfo; | ||
20 | import org.thingsboard.server.common.data.EntitySubtype; | 21 | import org.thingsboard.server.common.data.EntitySubtype; |
21 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; | 22 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
22 | import org.thingsboard.server.common.data.id.CustomerId; | 23 | import org.thingsboard.server.common.data.id.CustomerId; |
@@ -28,7 +29,9 @@ import org.thingsboard.server.common.data.page.PageLink; | @@ -28,7 +29,9 @@ import org.thingsboard.server.common.data.page.PageLink; | ||
28 | import java.util.List; | 29 | import java.util.List; |
29 | 30 | ||
30 | public interface DeviceService { | 31 | public interface DeviceService { |
31 | - | 32 | + |
33 | + DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); | ||
34 | + | ||
32 | Device findDeviceById(TenantId tenantId, DeviceId deviceId); | 35 | Device findDeviceById(TenantId tenantId, DeviceId deviceId); |
33 | 36 | ||
34 | ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); | 37 | ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); |
@@ -45,16 +48,24 @@ public interface DeviceService { | @@ -45,16 +48,24 @@ public interface DeviceService { | ||
45 | 48 | ||
46 | PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink); | 49 | PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink); |
47 | 50 | ||
51 | + PageData<DeviceInfo> findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink); | ||
52 | + | ||
48 | PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); | 53 | PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); |
49 | 54 | ||
55 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); | ||
56 | + | ||
50 | ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds); | 57 | ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds); |
51 | 58 | ||
52 | void deleteDevicesByTenantId(TenantId tenantId); | 59 | void deleteDevicesByTenantId(TenantId tenantId); |
53 | 60 | ||
54 | PageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); | 61 | PageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); |
55 | 62 | ||
63 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); | ||
64 | + | ||
56 | PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); | 65 | PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); |
57 | 66 | ||
67 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink); | ||
68 | + | ||
58 | ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds); | 69 | ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds); |
59 | 70 | ||
60 | void unassignCustomerDevices(TenantId tenantId, CustomerId customerId); | 71 | void unassignCustomerDevices(TenantId tenantId, CustomerId customerId); |
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.common.data; | ||
17 | + | ||
18 | +import lombok.Data; | ||
19 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
20 | + | ||
21 | +@Data | ||
22 | +public class DeviceInfo extends Device { | ||
23 | + | ||
24 | + private String customerTitle; | ||
25 | + private boolean customerIsPublic; | ||
26 | + | ||
27 | + public DeviceInfo() { | ||
28 | + super(); | ||
29 | + } | ||
30 | + | ||
31 | + public DeviceInfo(DeviceId deviceId) { | ||
32 | + super(deviceId); | ||
33 | + } | ||
34 | + | ||
35 | + public DeviceInfo(Device device, String customerTitle, boolean customerIsPublic) { | ||
36 | + super(device); | ||
37 | + this.customerTitle = customerTitle; | ||
38 | + this.customerIsPublic = customerIsPublic; | ||
39 | + } | ||
40 | +} |
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; | @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; | ||
17 | 17 | ||
18 | import com.google.common.util.concurrent.ListenableFuture; | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | import org.thingsboard.server.common.data.Device; | 19 | import org.thingsboard.server.common.data.Device; |
20 | +import org.thingsboard.server.common.data.DeviceInfo; | ||
20 | import org.thingsboard.server.common.data.EntitySubtype; | 21 | import org.thingsboard.server.common.data.EntitySubtype; |
21 | import org.thingsboard.server.common.data.id.TenantId; | 22 | import org.thingsboard.server.common.data.id.TenantId; |
22 | import org.thingsboard.server.common.data.page.PageData; | 23 | import org.thingsboard.server.common.data.page.PageData; |
@@ -34,6 +35,15 @@ import java.util.UUID; | @@ -34,6 +35,15 @@ import java.util.UUID; | ||
34 | public interface DeviceDao extends Dao<Device> { | 35 | public interface DeviceDao extends Dao<Device> { |
35 | 36 | ||
36 | /** | 37 | /** |
38 | + * Find device info by id. | ||
39 | + * | ||
40 | + * @param tenantId the tenant id | ||
41 | + * @param deviceId the device id | ||
42 | + * @return the device info object | ||
43 | + */ | ||
44 | + DeviceInfo findDeviceInfoById(TenantId tenantId, UUID deviceId); | ||
45 | + | ||
46 | + /** | ||
37 | * Save or update device object | 47 | * Save or update device object |
38 | * | 48 | * |
39 | * @param device the device object | 49 | * @param device the device object |
@@ -51,6 +61,15 @@ public interface DeviceDao extends Dao<Device> { | @@ -51,6 +61,15 @@ public interface DeviceDao extends Dao<Device> { | ||
51 | PageData<Device> findDevicesByTenantId(UUID tenantId, PageLink pageLink); | 61 | PageData<Device> findDevicesByTenantId(UUID tenantId, PageLink pageLink); |
52 | 62 | ||
53 | /** | 63 | /** |
64 | + * Find device infos by tenantId and page link. | ||
65 | + * | ||
66 | + * @param tenantId the tenantId | ||
67 | + * @param pageLink the page link | ||
68 | + * @return the list of device info objects | ||
69 | + */ | ||
70 | + PageData<DeviceInfo> findDeviceInfosByTenantId(UUID tenantId, PageLink pageLink); | ||
71 | + | ||
72 | + /** | ||
54 | * Find devices by tenantId, type and page link. | 73 | * Find devices by tenantId, type and page link. |
55 | * | 74 | * |
56 | * @param tenantId the tenantId | 75 | * @param tenantId the tenantId |
@@ -61,6 +80,16 @@ public interface DeviceDao extends Dao<Device> { | @@ -61,6 +80,16 @@ public interface DeviceDao extends Dao<Device> { | ||
61 | PageData<Device> findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); | 80 | PageData<Device> findDevicesByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); |
62 | 81 | ||
63 | /** | 82 | /** |
83 | + * Find device infos by tenantId, type and page link. | ||
84 | + * | ||
85 | + * @param tenantId the tenantId | ||
86 | + * @param type the type | ||
87 | + * @param pageLink the page link | ||
88 | + * @return the list of device onfo objects | ||
89 | + */ | ||
90 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink); | ||
91 | + | ||
92 | + /** | ||
64 | * Find devices by tenantId and devices Ids. | 93 | * Find devices by tenantId and devices Ids. |
65 | * | 94 | * |
66 | * @param tenantId the tenantId | 95 | * @param tenantId the tenantId |
@@ -80,6 +109,16 @@ public interface DeviceDao extends Dao<Device> { | @@ -80,6 +109,16 @@ public interface DeviceDao extends Dao<Device> { | ||
80 | PageData<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); | 109 | PageData<Device> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); |
81 | 110 | ||
82 | /** | 111 | /** |
112 | + * Find device infos by tenantId, customerId and page link. | ||
113 | + * | ||
114 | + * @param tenantId the tenantId | ||
115 | + * @param customerId the customerId | ||
116 | + * @param pageLink the page link | ||
117 | + * @return the list of device info objects | ||
118 | + */ | ||
119 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink); | ||
120 | + | ||
121 | + /** | ||
83 | * Find devices by tenantId, customerId, type and page link. | 122 | * Find devices by tenantId, customerId, type and page link. |
84 | * | 123 | * |
85 | * @param tenantId the tenantId | 124 | * @param tenantId the tenantId |
@@ -90,6 +129,17 @@ public interface DeviceDao extends Dao<Device> { | @@ -90,6 +129,17 @@ public interface DeviceDao extends Dao<Device> { | ||
90 | */ | 129 | */ |
91 | PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); | 130 | PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); |
92 | 131 | ||
132 | + /** | ||
133 | + * Find device infos by tenantId, customerId, type and page link. | ||
134 | + * | ||
135 | + * @param tenantId the tenantId | ||
136 | + * @param customerId the customerId | ||
137 | + * @param type the type | ||
138 | + * @param pageLink the page link | ||
139 | + * @return the list of device info objects | ||
140 | + */ | ||
141 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink); | ||
142 | + | ||
93 | 143 | ||
94 | /** | 144 | /** |
95 | * Find devices by tenantId, customerId and devices Ids. | 145 | * Find devices by tenantId, customerId and devices Ids. |
@@ -27,12 +27,7 @@ import org.springframework.cache.annotation.CacheEvict; | @@ -27,12 +27,7 @@ import org.springframework.cache.annotation.CacheEvict; | ||
27 | import org.springframework.cache.annotation.Cacheable; | 27 | import org.springframework.cache.annotation.Cacheable; |
28 | import org.springframework.stereotype.Service; | 28 | import org.springframework.stereotype.Service; |
29 | import org.springframework.util.StringUtils; | 29 | import org.springframework.util.StringUtils; |
30 | -import org.thingsboard.server.common.data.Customer; | ||
31 | -import org.thingsboard.server.common.data.Device; | ||
32 | -import org.thingsboard.server.common.data.EntitySubtype; | ||
33 | -import org.thingsboard.server.common.data.EntityType; | ||
34 | -import org.thingsboard.server.common.data.EntityView; | ||
35 | -import org.thingsboard.server.common.data.Tenant; | 30 | +import org.thingsboard.server.common.data.*; |
36 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; | 31 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
37 | import org.thingsboard.server.common.data.id.CustomerId; | 32 | import org.thingsboard.server.common.data.id.CustomerId; |
38 | import org.thingsboard.server.common.data.id.DeviceId; | 33 | import org.thingsboard.server.common.data.id.DeviceId; |
@@ -96,6 +91,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | @@ -96,6 +91,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | ||
96 | private CacheManager cacheManager; | 91 | private CacheManager cacheManager; |
97 | 92 | ||
98 | @Override | 93 | @Override |
94 | + public DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId) { | ||
95 | + log.trace("Executing findDeviceInfoById [{}]", deviceId); | ||
96 | + validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); | ||
97 | + return deviceDao.findDeviceInfoById(tenantId, deviceId.getId()); | ||
98 | + } | ||
99 | + | ||
100 | + @Override | ||
99 | public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { | 101 | public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { |
100 | log.trace("Executing findDeviceById [{}]", deviceId); | 102 | log.trace("Executing findDeviceById [{}]", deviceId); |
101 | validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); | 103 | validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); |
@@ -188,6 +190,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | @@ -188,6 +190,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | ||
188 | } | 190 | } |
189 | 191 | ||
190 | @Override | 192 | @Override |
193 | + public PageData<DeviceInfo> findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink) { | ||
194 | + log.trace("Executing findDeviceInfosByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); | ||
195 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
196 | + validatePageLink(pageLink); | ||
197 | + return deviceDao.findDeviceInfosByTenantId(tenantId.getId(), pageLink); | ||
198 | + } | ||
199 | + | ||
200 | + @Override | ||
191 | public PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { | 201 | public PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { |
192 | log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); | 202 | log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); |
193 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | 203 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
@@ -197,6 +207,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | @@ -197,6 +207,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | ||
197 | } | 207 | } |
198 | 208 | ||
199 | @Override | 209 | @Override |
210 | + public PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { | ||
211 | + log.trace("Executing findDeviceInfosByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); | ||
212 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
213 | + validateString(type, "Incorrect type " + type); | ||
214 | + validatePageLink(pageLink); | ||
215 | + return deviceDao.findDeviceInfosByTenantIdAndType(tenantId.getId(), type, pageLink); | ||
216 | + } | ||
217 | + | ||
218 | + @Override | ||
200 | public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds) { | 219 | public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds) { |
201 | log.trace("Executing findDevicesByTenantIdAndIdsAsync, tenantId [{}], deviceIds [{}]", tenantId, deviceIds); | 220 | log.trace("Executing findDevicesByTenantIdAndIdsAsync, tenantId [{}], deviceIds [{}]", tenantId, deviceIds); |
202 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | 221 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
@@ -222,6 +241,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | @@ -222,6 +241,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | ||
222 | } | 241 | } |
223 | 242 | ||
224 | @Override | 243 | @Override |
244 | + public PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink) { | ||
245 | + log.trace("Executing findDeviceInfosByTenantIdAndCustomerId, tenantId [{}], customerId [{}], pageLink [{}]", tenantId, customerId, pageLink); | ||
246 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
247 | + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); | ||
248 | + validatePageLink(pageLink); | ||
249 | + return deviceDao.findDeviceInfosByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink); | ||
250 | + } | ||
251 | + | ||
252 | + @Override | ||
225 | public PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { | 253 | public PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { |
226 | log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); | 254 | log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); |
227 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | 255 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
@@ -232,6 +260,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | @@ -232,6 +260,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe | ||
232 | } | 260 | } |
233 | 261 | ||
234 | @Override | 262 | @Override |
263 | + public PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { | ||
264 | + log.trace("Executing findDeviceInfosByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); | ||
265 | + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | ||
266 | + validateId(customerId, INCORRECT_CUSTOMER_ID + customerId); | ||
267 | + validateString(type, "Incorrect type " + type); | ||
268 | + validatePageLink(pageLink); | ||
269 | + return deviceDao.findDeviceInfosByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink); | ||
270 | + } | ||
271 | + | ||
272 | + @Override | ||
235 | public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds) { | 273 | public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds) { |
236 | log.trace("Executing findDevicesByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], deviceIds [{}]", tenantId, customerId, deviceIds); | 274 | log.trace("Executing findDevicesByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], deviceIds [{}]", tenantId, customerId, deviceIds); |
237 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); | 275 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
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.Device; | ||
25 | +import org.thingsboard.server.common.data.id.CustomerId; | ||
26 | +import org.thingsboard.server.common.data.id.DeviceId; | ||
27 | +import org.thingsboard.server.common.data.id.TenantId; | ||
28 | +import org.thingsboard.server.dao.model.BaseSqlEntity; | ||
29 | +import org.thingsboard.server.dao.model.ModelConstants; | ||
30 | +import org.thingsboard.server.dao.model.SearchTextEntity; | ||
31 | +import org.thingsboard.server.dao.util.mapping.JsonStringType; | ||
32 | + | ||
33 | +import javax.persistence.Column; | ||
34 | +import javax.persistence.Entity; | ||
35 | +import javax.persistence.MappedSuperclass; | ||
36 | + | ||
37 | +@Data | ||
38 | +@EqualsAndHashCode(callSuper = true) | ||
39 | +@TypeDef(name = "json", typeClass = JsonStringType.class) | ||
40 | +@MappedSuperclass | ||
41 | +public abstract class AbstractDeviceEntity<T extends Device> extends BaseSqlEntity<T> implements SearchTextEntity<T> { | ||
42 | + | ||
43 | + @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY) | ||
44 | + private String tenantId; | ||
45 | + | ||
46 | + @Column(name = ModelConstants.DEVICE_CUSTOMER_ID_PROPERTY) | ||
47 | + private String customerId; | ||
48 | + | ||
49 | + @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY) | ||
50 | + private String type; | ||
51 | + | ||
52 | + @Column(name = ModelConstants.DEVICE_NAME_PROPERTY) | ||
53 | + private String name; | ||
54 | + | ||
55 | + @Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) | ||
56 | + private String label; | ||
57 | + | ||
58 | + @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) | ||
59 | + private String searchText; | ||
60 | + | ||
61 | + @Type(type = "json") | ||
62 | + @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) | ||
63 | + private JsonNode additionalInfo; | ||
64 | + | ||
65 | + public AbstractDeviceEntity() { | ||
66 | + super(); | ||
67 | + } | ||
68 | + | ||
69 | + public AbstractDeviceEntity(Device device) { | ||
70 | + if (device.getId() != null) { | ||
71 | + this.setId(device.getId().getId()); | ||
72 | + } | ||
73 | + if (device.getTenantId() != null) { | ||
74 | + this.tenantId = toString(device.getTenantId().getId()); | ||
75 | + } | ||
76 | + if (device.getCustomerId() != null) { | ||
77 | + this.customerId = toString(device.getCustomerId().getId()); | ||
78 | + } | ||
79 | + this.name = device.getName(); | ||
80 | + this.type = device.getType(); | ||
81 | + this.label = device.getLabel(); | ||
82 | + this.additionalInfo = device.getAdditionalInfo(); | ||
83 | + } | ||
84 | + | ||
85 | + public AbstractDeviceEntity(DeviceEntity deviceEntity) { | ||
86 | + this.setId(deviceEntity.getId());; | ||
87 | + this.tenantId = deviceEntity.getTenantId(); | ||
88 | + this.customerId = deviceEntity.getCustomerId(); | ||
89 | + this.type = deviceEntity.getType(); | ||
90 | + this.name = deviceEntity.getName(); | ||
91 | + this.label = deviceEntity.getLabel(); | ||
92 | + this.searchText = deviceEntity.getSearchText(); | ||
93 | + this.additionalInfo = deviceEntity.getAdditionalInfo(); | ||
94 | + } | ||
95 | + | ||
96 | + @Override | ||
97 | + public String getSearchTextSource() { | ||
98 | + return name; | ||
99 | + } | ||
100 | + | ||
101 | + @Override | ||
102 | + public void setSearchText(String searchText) { | ||
103 | + this.searchText = searchText; | ||
104 | + } | ||
105 | + | ||
106 | + protected Device toDevice() { | ||
107 | + Device device = new Device(new DeviceId(getId())); | ||
108 | + device.setCreatedTime(UUIDs.unixTimestamp(getId())); | ||
109 | + if (tenantId != null) { | ||
110 | + device.setTenantId(new TenantId(toUUID(tenantId))); | ||
111 | + } | ||
112 | + if (customerId != null) { | ||
113 | + device.setCustomerId(new CustomerId(toUUID(customerId))); | ||
114 | + } | ||
115 | + device.setName(name); | ||
116 | + device.setType(type); | ||
117 | + device.setLabel(label); | ||
118 | + device.setAdditionalInfo(additionalInfo); | ||
119 | + return device; | ||
120 | + } | ||
121 | + | ||
122 | +} |
@@ -39,74 +39,18 @@ import javax.persistence.Table; | @@ -39,74 +39,18 @@ import javax.persistence.Table; | ||
39 | @Entity | 39 | @Entity |
40 | @TypeDef(name = "json", typeClass = JsonStringType.class) | 40 | @TypeDef(name = "json", typeClass = JsonStringType.class) |
41 | @Table(name = ModelConstants.DEVICE_COLUMN_FAMILY_NAME) | 41 | @Table(name = ModelConstants.DEVICE_COLUMN_FAMILY_NAME) |
42 | -public final class DeviceEntity extends BaseSqlEntity<Device> implements SearchTextEntity<Device> { | ||
43 | - | ||
44 | - @Column(name = ModelConstants.DEVICE_TENANT_ID_PROPERTY) | ||
45 | - private String tenantId; | ||
46 | - | ||
47 | - @Column(name = ModelConstants.DEVICE_CUSTOMER_ID_PROPERTY) | ||
48 | - private String customerId; | ||
49 | - | ||
50 | - @Column(name = ModelConstants.DEVICE_TYPE_PROPERTY) | ||
51 | - private String type; | ||
52 | - | ||
53 | - @Column(name = ModelConstants.DEVICE_NAME_PROPERTY) | ||
54 | - private String name; | ||
55 | - | ||
56 | - @Column(name = ModelConstants.DEVICE_LABEL_PROPERTY) | ||
57 | - private String label; | ||
58 | - | ||
59 | - @Column(name = ModelConstants.SEARCH_TEXT_PROPERTY) | ||
60 | - private String searchText; | ||
61 | - | ||
62 | - @Type(type = "json") | ||
63 | - @Column(name = ModelConstants.DEVICE_ADDITIONAL_INFO_PROPERTY) | ||
64 | - private JsonNode additionalInfo; | 42 | +public final class DeviceEntity extends AbstractDeviceEntity<Device> { |
65 | 43 | ||
66 | public DeviceEntity() { | 44 | public DeviceEntity() { |
67 | super(); | 45 | super(); |
68 | } | 46 | } |
69 | 47 | ||
70 | public DeviceEntity(Device device) { | 48 | public DeviceEntity(Device device) { |
71 | - if (device.getId() != null) { | ||
72 | - this.setId(device.getId().getId()); | ||
73 | - } | ||
74 | - if (device.getTenantId() != null) { | ||
75 | - this.tenantId = toString(device.getTenantId().getId()); | ||
76 | - } | ||
77 | - if (device.getCustomerId() != null) { | ||
78 | - this.customerId = toString(device.getCustomerId().getId()); | ||
79 | - } | ||
80 | - this.name = device.getName(); | ||
81 | - this.type = device.getType(); | ||
82 | - this.label = device.getLabel(); | ||
83 | - this.additionalInfo = device.getAdditionalInfo(); | ||
84 | - } | ||
85 | - | ||
86 | - @Override | ||
87 | - public String getSearchTextSource() { | ||
88 | - return name; | ||
89 | - } | ||
90 | - | ||
91 | - @Override | ||
92 | - public void setSearchText(String searchText) { | ||
93 | - this.searchText = searchText; | 49 | + super(device); |
94 | } | 50 | } |
95 | 51 | ||
96 | @Override | 52 | @Override |
97 | public Device toData() { | 53 | public Device toData() { |
98 | - Device device = new Device(new DeviceId(getId())); | ||
99 | - device.setCreatedTime(UUIDs.unixTimestamp(getId())); | ||
100 | - if (tenantId != null) { | ||
101 | - device.setTenantId(new TenantId(toUUID(tenantId))); | ||
102 | - } | ||
103 | - if (customerId != null) { | ||
104 | - device.setCustomerId(new CustomerId(toUUID(customerId))); | ||
105 | - } | ||
106 | - device.setName(name); | ||
107 | - device.setType(type); | ||
108 | - device.setLabel(label); | ||
109 | - device.setAdditionalInfo(additionalInfo); | ||
110 | - return device; | 54 | + return super.toDevice(); |
111 | } | 55 | } |
112 | -} | ||
56 | +} |
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 com.fasterxml.jackson.databind.JsonNode; | ||
21 | +import org.thingsboard.server.common.data.DeviceInfo; | ||
22 | + | ||
23 | +import java.util.HashMap; | ||
24 | +import java.util.Map; | ||
25 | + | ||
26 | +@Data | ||
27 | +@EqualsAndHashCode(callSuper = true) | ||
28 | +public class DeviceInfoEntity extends AbstractDeviceEntity<DeviceInfo> { | ||
29 | + | ||
30 | + public static final Map<String,String> deviceInfoColumnMap = new HashMap<>(); | ||
31 | + static { | ||
32 | + deviceInfoColumnMap.put("customerTitle", "c.title"); | ||
33 | + } | ||
34 | + | ||
35 | + private String customerTitle; | ||
36 | + private boolean customerIsPublic; | ||
37 | + | ||
38 | + public DeviceInfoEntity() { | ||
39 | + super(); | ||
40 | + } | ||
41 | + | ||
42 | + public DeviceInfoEntity(DeviceEntity deviceEntity, | ||
43 | + String customerTitle, | ||
44 | + Object customerAdditionalInfo) { | ||
45 | + super(deviceEntity); | ||
46 | + this.customerTitle = customerTitle; | ||
47 | + if (customerAdditionalInfo != null && ((JsonNode)customerAdditionalInfo).has("isPublic")) { | ||
48 | + this.customerIsPublic = ((JsonNode)customerAdditionalInfo).get("isPublic").asBoolean(); | ||
49 | + } else { | ||
50 | + this.customerIsPublic = false; | ||
51 | + } | ||
52 | + } | ||
53 | + | ||
54 | + @Override | ||
55 | + public DeviceInfo toData() { | ||
56 | + return new DeviceInfo(super.toDevice(), customerTitle, customerIsPublic); | ||
57 | + } | ||
58 | +} |
@@ -22,6 +22,7 @@ import org.springframework.data.repository.CrudRepository; | @@ -22,6 +22,7 @@ import org.springframework.data.repository.CrudRepository; | ||
22 | import org.springframework.data.repository.PagingAndSortingRepository; | 22 | import org.springframework.data.repository.PagingAndSortingRepository; |
23 | import org.springframework.data.repository.query.Param; | 23 | import org.springframework.data.repository.query.Param; |
24 | import org.thingsboard.server.dao.model.sql.DeviceEntity; | 24 | import org.thingsboard.server.dao.model.sql.DeviceEntity; |
25 | +import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; | ||
25 | import org.thingsboard.server.dao.util.SqlDao; | 26 | import org.thingsboard.server.dao.util.SqlDao; |
26 | 27 | ||
27 | import java.util.List; | 28 | import java.util.List; |
@@ -32,6 +33,11 @@ import java.util.List; | @@ -32,6 +33,11 @@ import java.util.List; | ||
32 | @SqlDao | 33 | @SqlDao |
33 | public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntity, String> { | 34 | public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntity, String> { |
34 | 35 | ||
36 | + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " + | ||
37 | + "FROM DeviceEntity d " + | ||
38 | + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + | ||
39 | + "WHERE d.id = :deviceId") | ||
40 | + DeviceInfoEntity findDeviceInfoById(@Param("deviceId") String deviceId); | ||
35 | 41 | ||
36 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + | 42 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
37 | "AND d.customerId = :customerId " + | 43 | "AND d.customerId = :customerId " + |
@@ -41,12 +47,32 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit | @@ -41,12 +47,32 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit | ||
41 | @Param("searchText") String searchText, | 47 | @Param("searchText") String searchText, |
42 | Pageable pageable); | 48 | Pageable pageable); |
43 | 49 | ||
50 | + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " + | ||
51 | + "FROM DeviceEntity d " + | ||
52 | + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + | ||
53 | + "WHERE d.tenantId = :tenantId " + | ||
54 | + "AND d.customerId = :customerId " + | ||
55 | + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:searchText, '%'))") | ||
56 | + Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerId(@Param("tenantId") String tenantId, | ||
57 | + @Param("customerId") String customerId, | ||
58 | + @Param("searchText") String searchText, | ||
59 | + Pageable pageable); | ||
60 | + | ||
44 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + | 61 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
45 | "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") | 62 | "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") |
46 | Page<DeviceEntity> findByTenantId(@Param("tenantId") String tenantId, | 63 | Page<DeviceEntity> findByTenantId(@Param("tenantId") String tenantId, |
47 | @Param("textSearch") String textSearch, | 64 | @Param("textSearch") String textSearch, |
48 | Pageable pageable); | 65 | Pageable pageable); |
49 | 66 | ||
67 | + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " + | ||
68 | + "FROM DeviceEntity d " + | ||
69 | + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + | ||
70 | + "WHERE d.tenantId = :tenantId " + | ||
71 | + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") | ||
72 | + Page<DeviceInfoEntity> findDeviceInfosByTenantId(@Param("tenantId") String tenantId, | ||
73 | + @Param("textSearch") String textSearch, | ||
74 | + Pageable pageable); | ||
75 | + | ||
50 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + | 76 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
51 | "AND d.type = :type " + | 77 | "AND d.type = :type " + |
52 | "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") | 78 | "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") |
@@ -55,6 +81,17 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit | @@ -55,6 +81,17 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit | ||
55 | @Param("textSearch") String textSearch, | 81 | @Param("textSearch") String textSearch, |
56 | Pageable pageable); | 82 | Pageable pageable); |
57 | 83 | ||
84 | + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " + | ||
85 | + "FROM DeviceEntity d " + | ||
86 | + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + | ||
87 | + "WHERE d.tenantId = :tenantId " + | ||
88 | + "AND d.type = :type " + | ||
89 | + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") | ||
90 | + Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndType(@Param("tenantId") String tenantId, | ||
91 | + @Param("type") String type, | ||
92 | + @Param("textSearch") String textSearch, | ||
93 | + Pageable pageable); | ||
94 | + | ||
58 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + | 95 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
59 | "AND d.customerId = :customerId " + | 96 | "AND d.customerId = :customerId " + |
60 | "AND d.type = :type " + | 97 | "AND d.type = :type " + |
@@ -65,6 +102,19 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit | @@ -65,6 +102,19 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit | ||
65 | @Param("textSearch") String textSearch, | 102 | @Param("textSearch") String textSearch, |
66 | Pageable pageable); | 103 | Pageable pageable); |
67 | 104 | ||
105 | + @Query("SELECT new org.thingsboard.server.dao.model.sql.DeviceInfoEntity(d, c.title, c.additionalInfo) " + | ||
106 | + "FROM DeviceEntity d " + | ||
107 | + "LEFT JOIN CustomerEntity c on c.id = d.customerId " + | ||
108 | + "WHERE d.tenantId = :tenantId " + | ||
109 | + "AND d.customerId = :customerId " + | ||
110 | + "AND d.type = :type " + | ||
111 | + "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") | ||
112 | + Page<DeviceInfoEntity> findDeviceInfosByTenantIdAndCustomerIdAndType(@Param("tenantId") String tenantId, | ||
113 | + @Param("customerId") String customerId, | ||
114 | + @Param("type") String type, | ||
115 | + @Param("textSearch") String textSearch, | ||
116 | + Pageable pageable); | ||
117 | + | ||
68 | @Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId") | 118 | @Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId") |
69 | List<String> findTenantDeviceTypes(@Param("tenantId") String tenantId); | 119 | List<String> findTenantDeviceTypes(@Param("tenantId") String tenantId); |
70 | 120 |
@@ -20,16 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired; | @@ -20,16 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired; | ||
20 | import org.springframework.data.domain.PageRequest; | 20 | import org.springframework.data.domain.PageRequest; |
21 | import org.springframework.data.repository.CrudRepository; | 21 | import org.springframework.data.repository.CrudRepository; |
22 | import org.springframework.stereotype.Component; | 22 | import org.springframework.stereotype.Component; |
23 | -import org.thingsboard.server.common.data.Device; | ||
24 | -import org.thingsboard.server.common.data.EntitySubtype; | ||
25 | -import org.thingsboard.server.common.data.EntityType; | ||
26 | -import org.thingsboard.server.common.data.UUIDConverter; | 23 | +import org.thingsboard.server.common.data.*; |
27 | import org.thingsboard.server.common.data.id.TenantId; | 24 | import org.thingsboard.server.common.data.id.TenantId; |
28 | import org.thingsboard.server.common.data.page.PageData; | 25 | import org.thingsboard.server.common.data.page.PageData; |
29 | import org.thingsboard.server.common.data.page.PageLink; | 26 | import org.thingsboard.server.common.data.page.PageLink; |
30 | import org.thingsboard.server.dao.DaoUtil; | 27 | import org.thingsboard.server.dao.DaoUtil; |
31 | import org.thingsboard.server.dao.device.DeviceDao; | 28 | import org.thingsboard.server.dao.device.DeviceDao; |
32 | import org.thingsboard.server.dao.model.sql.DeviceEntity; | 29 | import org.thingsboard.server.dao.model.sql.DeviceEntity; |
30 | +import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; | ||
33 | import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; | 31 | import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; |
34 | import org.thingsboard.server.dao.util.SqlDao; | 32 | import org.thingsboard.server.dao.util.SqlDao; |
35 | 33 | ||
@@ -65,6 +63,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | @@ -65,6 +63,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | ||
65 | } | 63 | } |
66 | 64 | ||
67 | @Override | 65 | @Override |
66 | + public DeviceInfo findDeviceInfoById(TenantId tenantId, UUID deviceId) { | ||
67 | + return DaoUtil.getData(deviceRepository.findDeviceInfoById(fromTimeUUID(deviceId))); | ||
68 | + } | ||
69 | + | ||
70 | + @Override | ||
68 | public PageData<Device> findDevicesByTenantId(UUID tenantId, PageLink pageLink) { | 71 | public PageData<Device> findDevicesByTenantId(UUID tenantId, PageLink pageLink) { |
69 | return DaoUtil.toPageData( | 72 | return DaoUtil.toPageData( |
70 | deviceRepository.findByTenantId( | 73 | deviceRepository.findByTenantId( |
@@ -74,6 +77,15 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | @@ -74,6 +77,15 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | ||
74 | } | 77 | } |
75 | 78 | ||
76 | @Override | 79 | @Override |
80 | + public PageData<DeviceInfo> findDeviceInfosByTenantId(UUID tenantId, PageLink pageLink) { | ||
81 | + return DaoUtil.toPageData( | ||
82 | + deviceRepository.findDeviceInfosByTenantId( | ||
83 | + fromTimeUUID(tenantId), | ||
84 | + Objects.toString(pageLink.getTextSearch(), ""), | ||
85 | + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); | ||
86 | + } | ||
87 | + | ||
88 | + @Override | ||
77 | public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> deviceIds) { | 89 | public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> deviceIds) { |
78 | return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findDevicesByTenantIdAndIdIn(UUIDConverter.fromTimeUUID(tenantId), fromTimeUUIDs(deviceIds)))); | 90 | return service.submit(() -> DaoUtil.convertDataList(deviceRepository.findDevicesByTenantIdAndIdIn(UUIDConverter.fromTimeUUID(tenantId), fromTimeUUIDs(deviceIds)))); |
79 | } | 91 | } |
@@ -89,6 +101,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | @@ -89,6 +101,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | ||
89 | } | 101 | } |
90 | 102 | ||
91 | @Override | 103 | @Override |
104 | + public PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerId(UUID tenantId, UUID customerId, PageLink pageLink) { | ||
105 | + return DaoUtil.toPageData( | ||
106 | + deviceRepository.findDeviceInfosByTenantIdAndCustomerId( | ||
107 | + fromTimeUUID(tenantId), | ||
108 | + fromTimeUUID(customerId), | ||
109 | + Objects.toString(pageLink.getTextSearch(), ""), | ||
110 | + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); | ||
111 | + } | ||
112 | + | ||
113 | + @Override | ||
92 | public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds) { | 114 | public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds) { |
93 | return service.submit(() -> DaoUtil.convertDataList( | 115 | return service.submit(() -> DaoUtil.convertDataList( |
94 | deviceRepository.findDevicesByTenantIdAndCustomerIdAndIdIn(fromTimeUUID(tenantId), fromTimeUUID(customerId), fromTimeUUIDs(deviceIds)))); | 116 | deviceRepository.findDevicesByTenantIdAndCustomerIdAndIdIn(fromTimeUUID(tenantId), fromTimeUUID(customerId), fromTimeUUIDs(deviceIds)))); |
@@ -111,6 +133,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | @@ -111,6 +133,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | ||
111 | } | 133 | } |
112 | 134 | ||
113 | @Override | 135 | @Override |
136 | + public PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(UUID tenantId, String type, PageLink pageLink) { | ||
137 | + return DaoUtil.toPageData( | ||
138 | + deviceRepository.findDeviceInfosByTenantIdAndType( | ||
139 | + fromTimeUUID(tenantId), | ||
140 | + type, | ||
141 | + Objects.toString(pageLink.getTextSearch(), ""), | ||
142 | + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); | ||
143 | + } | ||
144 | + | ||
145 | + @Override | ||
114 | public PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { | 146 | public PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { |
115 | return DaoUtil.toPageData( | 147 | return DaoUtil.toPageData( |
116 | deviceRepository.findByTenantIdAndCustomerIdAndType( | 148 | deviceRepository.findByTenantIdAndCustomerIdAndType( |
@@ -122,6 +154,17 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | @@ -122,6 +154,17 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> | ||
122 | } | 154 | } |
123 | 155 | ||
124 | @Override | 156 | @Override |
157 | + public PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { | ||
158 | + return DaoUtil.toPageData( | ||
159 | + deviceRepository.findDeviceInfosByTenantIdAndCustomerIdAndType( | ||
160 | + fromTimeUUID(tenantId), | ||
161 | + fromTimeUUID(customerId), | ||
162 | + type, | ||
163 | + Objects.toString(pageLink.getTextSearch(), ""), | ||
164 | + DaoUtil.toPageable(pageLink, DeviceInfoEntity.deviceInfoColumnMap))); | ||
165 | + } | ||
166 | + | ||
167 | + @Override | ||
125 | public ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId) { | 168 | public ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId) { |
126 | return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); | 169 | return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); |
127 | } | 170 | } |
@@ -24,7 +24,7 @@ import java.util.Arrays; | @@ -24,7 +24,7 @@ import java.util.Arrays; | ||
24 | 24 | ||
25 | @RunWith(ClasspathSuite.class) | 25 | @RunWith(ClasspathSuite.class) |
26 | @ClassnameFilters({ | 26 | @ClassnameFilters({ |
27 | - "org.thingsboard.server.dao.service.*ServiceSqlTest" | 27 | + "org.thingsboard.server.dao.service.*DeviceServiceSqlTest" |
28 | }) | 28 | }) |
29 | public class SqlDaoServiceTestSuite { | 29 | public class SqlDaoServiceTestSuite { |
30 | 30 |
@@ -21,10 +21,7 @@ import org.junit.After; | @@ -21,10 +21,7 @@ import org.junit.After; | ||
21 | import org.junit.Assert; | 21 | import org.junit.Assert; |
22 | import org.junit.Before; | 22 | import org.junit.Before; |
23 | import org.junit.Test; | 23 | import org.junit.Test; |
24 | -import org.thingsboard.server.common.data.Customer; | ||
25 | -import org.thingsboard.server.common.data.Device; | ||
26 | -import org.thingsboard.server.common.data.EntitySubtype; | ||
27 | -import org.thingsboard.server.common.data.Tenant; | 24 | +import org.thingsboard.server.common.data.*; |
28 | import org.thingsboard.server.common.data.id.CustomerId; | 25 | import org.thingsboard.server.common.data.id.CustomerId; |
29 | import org.thingsboard.server.common.data.id.TenantId; | 26 | import org.thingsboard.server.common.data.id.TenantId; |
30 | import org.thingsboard.server.common.data.page.PageData; | 27 | import org.thingsboard.server.common.data.page.PageData; |
@@ -264,7 +261,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -264,7 +261,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
264 | @Test | 261 | @Test |
265 | public void testFindDevicesByTenantIdAndName() { | 262 | public void testFindDevicesByTenantIdAndName() { |
266 | String title1 = "Device title 1"; | 263 | String title1 = "Device title 1"; |
267 | - List<Device> devicesTitle1 = new ArrayList<>(); | 264 | + List<DeviceInfo> devicesTitle1 = new ArrayList<>(); |
268 | for (int i=0;i<143;i++) { | 265 | for (int i=0;i<143;i++) { |
269 | Device device = new Device(); | 266 | Device device = new Device(); |
270 | device.setTenantId(tenantId); | 267 | device.setTenantId(tenantId); |
@@ -273,10 +270,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -273,10 +270,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
273 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); | 270 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
274 | device.setName(name); | 271 | device.setName(name); |
275 | device.setType("default"); | 272 | device.setType("default"); |
276 | - devicesTitle1.add(deviceService.saveDevice(device)); | 273 | + devicesTitle1.add(new DeviceInfo(deviceService.saveDevice(device), null, false)); |
277 | } | 274 | } |
278 | String title2 = "Device title 2"; | 275 | String title2 = "Device title 2"; |
279 | - List<Device> devicesTitle2 = new ArrayList<>(); | 276 | + List<DeviceInfo> devicesTitle2 = new ArrayList<>(); |
280 | for (int i=0;i<175;i++) { | 277 | for (int i=0;i<175;i++) { |
281 | Device device = new Device(); | 278 | Device device = new Device(); |
282 | device.setTenantId(tenantId); | 279 | device.setTenantId(tenantId); |
@@ -285,14 +282,14 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -285,14 +282,14 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
285 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); | 282 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
286 | device.setName(name); | 283 | device.setName(name); |
287 | device.setType("default"); | 284 | device.setType("default"); |
288 | - devicesTitle2.add(deviceService.saveDevice(device)); | 285 | + devicesTitle2.add(new DeviceInfo(deviceService.saveDevice(device), null, false)); |
289 | } | 286 | } |
290 | 287 | ||
291 | - List<Device> loadedDevicesTitle1 = new ArrayList<>(); | 288 | + List<DeviceInfo> loadedDevicesTitle1 = new ArrayList<>(); |
292 | PageLink pageLink = new PageLink(15, 0, title1); | 289 | PageLink pageLink = new PageLink(15, 0, title1); |
293 | - PageData<Device> pageData = null; | 290 | + PageData<DeviceInfo> pageData = null; |
294 | do { | 291 | do { |
295 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | 292 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); |
296 | loadedDevicesTitle1.addAll(pageData.getData()); | 293 | loadedDevicesTitle1.addAll(pageData.getData()); |
297 | if (pageData.hasNext()) { | 294 | if (pageData.hasNext()) { |
298 | pageLink = pageLink.nextPageLink(); | 295 | pageLink = pageLink.nextPageLink(); |
@@ -304,10 +301,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -304,10 +301,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
304 | 301 | ||
305 | Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); | 302 | Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); |
306 | 303 | ||
307 | - List<Device> loadedDevicesTitle2 = new ArrayList<>(); | 304 | + List<DeviceInfo> loadedDevicesTitle2 = new ArrayList<>(); |
308 | pageLink = new PageLink(4, 0, title2); | 305 | pageLink = new PageLink(4, 0, title2); |
309 | do { | 306 | do { |
310 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | 307 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); |
311 | loadedDevicesTitle2.addAll(pageData.getData()); | 308 | loadedDevicesTitle2.addAll(pageData.getData()); |
312 | if (pageData.hasNext()) { | 309 | if (pageData.hasNext()) { |
313 | pageLink = pageLink.nextPageLink(); | 310 | pageLink = pageLink.nextPageLink(); |
@@ -324,7 +321,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -324,7 +321,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
324 | } | 321 | } |
325 | 322 | ||
326 | pageLink = new PageLink(4, 0, title1); | 323 | pageLink = new PageLink(4, 0, title1); |
327 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | 324 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); |
328 | Assert.assertFalse(pageData.hasNext()); | 325 | Assert.assertFalse(pageData.hasNext()); |
329 | Assert.assertEquals(0, pageData.getData().size()); | 326 | Assert.assertEquals(0, pageData.getData().size()); |
330 | 327 | ||
@@ -333,7 +330,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -333,7 +330,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
333 | } | 330 | } |
334 | 331 | ||
335 | pageLink = new PageLink(4, 0, title2); | 332 | pageLink = new PageLink(4, 0, title2); |
336 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | 333 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); |
337 | Assert.assertFalse(pageData.hasNext()); | 334 | Assert.assertFalse(pageData.hasNext()); |
338 | Assert.assertEquals(0, pageData.getData().size()); | 335 | Assert.assertEquals(0, pageData.getData().size()); |
339 | } | 336 | } |
@@ -431,21 +428,21 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -431,21 +428,21 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
431 | customer = customerService.saveCustomer(customer); | 428 | customer = customerService.saveCustomer(customer); |
432 | CustomerId customerId = customer.getId(); | 429 | CustomerId customerId = customer.getId(); |
433 | 430 | ||
434 | - List<Device> devices = new ArrayList<>(); | 431 | + List<DeviceInfo> devices = new ArrayList<>(); |
435 | for (int i=0;i<278;i++) { | 432 | for (int i=0;i<278;i++) { |
436 | Device device = new Device(); | 433 | Device device = new Device(); |
437 | device.setTenantId(tenantId); | 434 | device.setTenantId(tenantId); |
438 | device.setName("Device"+i); | 435 | device.setName("Device"+i); |
439 | device.setType("default"); | 436 | device.setType("default"); |
440 | device = deviceService.saveDevice(device); | 437 | device = deviceService.saveDevice(device); |
441 | - devices.add(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId)); | 438 | + devices.add(new DeviceInfo(deviceService.assignDeviceToCustomer(tenantId, device.getId(), customerId), customer.getTitle(), customer.isPublic())); |
442 | } | 439 | } |
443 | 440 | ||
444 | - List<Device> loadedDevices = new ArrayList<>(); | 441 | + List<DeviceInfo> loadedDevices = new ArrayList<>(); |
445 | PageLink pageLink = new PageLink(23); | 442 | PageLink pageLink = new PageLink(23); |
446 | - PageData<Device> pageData = null; | 443 | + PageData<DeviceInfo> pageData = null; |
447 | do { | 444 | do { |
448 | - pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); | 445 | + pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); |
449 | loadedDevices.addAll(pageData.getData()); | 446 | loadedDevices.addAll(pageData.getData()); |
450 | if (pageData.hasNext()) { | 447 | if (pageData.hasNext()) { |
451 | pageLink = pageLink.nextPageLink(); | 448 | pageLink = pageLink.nextPageLink(); |
@@ -460,7 +457,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | @@ -460,7 +457,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { | ||
460 | deviceService.unassignCustomerDevices(tenantId, customerId); | 457 | deviceService.unassignCustomerDevices(tenantId, customerId); |
461 | 458 | ||
462 | pageLink = new PageLink(33); | 459 | pageLink = new PageLink(33); |
463 | - pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); | 460 | + pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); |
464 | Assert.assertFalse(pageData.hasNext()); | 461 | Assert.assertFalse(pageData.hasNext()); |
465 | Assert.assertTrue(pageData.getData().isEmpty()); | 462 | Assert.assertTrue(pageData.getData().isEmpty()); |
466 | 463 |
ui-ngx/src/app/core/http/device.service.ts
0 → 100644
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 { PageLink } from '@shared/models/page/page-link'; | ||
22 | +import { PageData } from '@shared/models/page/page-data'; | ||
23 | +import { Tenant } from '@shared/models/tenant.model'; | ||
24 | +import {DashboardInfo, Dashboard} from '@shared/models/dashboard.models'; | ||
25 | +import {map} from 'rxjs/operators'; | ||
26 | +import {DeviceInfo, Device} from '@app/shared/models/device.models'; | ||
27 | +import {EntitySubtype} from '@app/shared/models/entity-type.models'; | ||
28 | + | ||
29 | +@Injectable({ | ||
30 | + providedIn: 'root' | ||
31 | +}) | ||
32 | +export class DeviceService { | ||
33 | + | ||
34 | + constructor( | ||
35 | + private http: HttpClient | ||
36 | + ) { } | ||
37 | + | ||
38 | + public getTenantDeviceInfos(pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, | ||
39 | + ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { | ||
40 | + return this.http.get<PageData<DeviceInfo>>(`/api/tenant/deviceInfos${pageLink.toQuery()}&type=${type}`, | ||
41 | + defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
42 | + } | ||
43 | + | ||
44 | + public getCustomerDeviceInfos(customerId: string, pageLink: PageLink, type: string = '', ignoreErrors: boolean = false, | ||
45 | + ignoreLoading: boolean = false): Observable<PageData<DeviceInfo>> { | ||
46 | + return this.http.get<PageData<DeviceInfo>>(`/api/customer/${customerId}/deviceInfos${pageLink.toQuery()}&type=${type}`, | ||
47 | + defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
48 | + } | ||
49 | + | ||
50 | + public getDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { | ||
51 | + return this.http.get<Device>(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
52 | + } | ||
53 | + | ||
54 | + public getDeviceInfo(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<DeviceInfo> { | ||
55 | + return this.http.get<DeviceInfo>(`/api/device/info/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
56 | + } | ||
57 | + | ||
58 | + public saveDevice(device: Device, ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Device> { | ||
59 | + return this.http.post<Device>('/api/device', device, defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
60 | + } | ||
61 | + | ||
62 | + public deleteDevice(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { | ||
63 | + return this.http.delete(`/api/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
64 | + } | ||
65 | + | ||
66 | + public getDeviceTypes(ignoreErrors: boolean = false, ignoreLoading: boolean = false): Observable<Array<EntitySubtype>> { | ||
67 | + return this.http.get<Array<EntitySubtype>>('/api/device/types', defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
68 | + } | ||
69 | + | ||
70 | + public unassignDeviceFromCustomer(deviceId: string, ignoreErrors: boolean = false, ignoreLoading: boolean = false) { | ||
71 | + return this.http.delete(`/api/customer/device/${deviceId}`, defaultHttpOptions(ignoreLoading, ignoreErrors)); | ||
72 | + } | ||
73 | + | ||
74 | +} |
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 | + | ||
18 | +export interface BroadcastMessage { | ||
19 | + name: string; | ||
20 | + args?: Array<any>; | ||
21 | +} | ||
22 | + | ||
23 | +export interface BroadcastEvent { | ||
24 | + name: string; | ||
25 | +} | ||
26 | + | ||
27 | +export type BroadcastListener = (event: BroadcastEvent, ...args: Array<any>) => void; |
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 {Subject, Subscription} from 'rxjs'; | ||
19 | +import {NotificationMessage} from '@core/notification/notification.models'; | ||
20 | +import {BroadcastEvent, BroadcastListener, BroadcastMessage} from '@core/services/broadcast.models'; | ||
21 | +import {filter} from 'rxjs/operators'; | ||
22 | + | ||
23 | +@Injectable({ | ||
24 | + providedIn: 'root' | ||
25 | +}) | ||
26 | +export class BroadcastService { | ||
27 | + | ||
28 | + private broadcastSubject: Subject<BroadcastMessage> = new Subject(); | ||
29 | + | ||
30 | + broadcast(name: string, ...args: Array<any>) { | ||
31 | + const message = { | ||
32 | + name, | ||
33 | + args | ||
34 | + } as BroadcastMessage; | ||
35 | + this.broadcastSubject.next(message); | ||
36 | + } | ||
37 | + | ||
38 | + on(name: string, listener: BroadcastListener): Subscription { | ||
39 | + return this.broadcastSubject.asObservable().pipe( | ||
40 | + filter((message) => message.name === name) | ||
41 | + ).subscribe( | ||
42 | + (message) => { | ||
43 | + const event = { | ||
44 | + name: message.name | ||
45 | + } as BroadcastEvent; | ||
46 | + listener(event, message.args); | ||
47 | + } | ||
48 | + ); | ||
49 | + } | ||
50 | + | ||
51 | +} |
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 {NgModule} from '@angular/core'; | ||
18 | +import {RouterModule, Routes} from '@angular/router'; | ||
19 | + | ||
20 | +import {EntitiesTableComponent} from '@shared/components/entity/entities-table.component'; | ||
21 | +import {Authority} from '@shared/models/authority.enum'; | ||
22 | +import {DevicesTableConfigResolver} from '@modules/home/pages/device/devices-table-config.resolver'; | ||
23 | + | ||
24 | +const routes: Routes = [ | ||
25 | + { | ||
26 | + path: 'devices', | ||
27 | + component: EntitiesTableComponent, | ||
28 | + data: { | ||
29 | + auth: [Authority.TENANT_ADMIN, Authority.CUSTOMER_USER], | ||
30 | + title: 'device.devices', | ||
31 | + devicesType: 'tenant', | ||
32 | + breadcrumb: { | ||
33 | + label: 'device.devices', | ||
34 | + icon: 'devices_other' | ||
35 | + } | ||
36 | + }, | ||
37 | + resolve: { | ||
38 | + entitiesTableConfig: DevicesTableConfigResolver | ||
39 | + } | ||
40 | + } | ||
41 | +]; | ||
42 | + | ||
43 | +@NgModule({ | ||
44 | + imports: [RouterModule.forChild(routes)], | ||
45 | + exports: [RouterModule], | ||
46 | + providers: [ | ||
47 | + DevicesTableConfigResolver | ||
48 | + ] | ||
49 | +}) | ||
50 | +export class DeviceRoutingModule { } |
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-entity-subtype-select | ||
19 | + [showLabel]="true" | ||
20 | + [entityType]="entityType.DEVICE" | ||
21 | + [ngModel]="entitiesTableConfig.componentsData.deviceType" | ||
22 | + (ngModelChange)="deviceTypeChanged($event)"> | ||
23 | +</tb-entity-subtype-select> |
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 | +} | ||
21 | + | ||
22 | +:host ::ng-deep { | ||
23 | + tb-entity-subtype-select { | ||
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 | + } | ||
36 | +} |
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 '@shared/components/entity/entity-table-header.component'; | ||
21 | +import {DeviceInfo} from '@app/shared/models/device.models'; | ||
22 | +import {EntityType} from '@shared/models/entity-type.models'; | ||
23 | + | ||
24 | +@Component({ | ||
25 | + selector: 'tb-device-table-header', | ||
26 | + templateUrl: './device-table-header.component.html', | ||
27 | + styleUrls: ['./device-table-header.component.scss'] | ||
28 | +}) | ||
29 | +export class DeviceTableHeaderComponent extends EntityTableHeaderComponent<DeviceInfo> { | ||
30 | + | ||
31 | + entityType = EntityType; | ||
32 | + | ||
33 | + constructor(protected store: Store<AppState>) { | ||
34 | + super(store); | ||
35 | + } | ||
36 | + | ||
37 | + deviceTypeChanged(deviceType: string) { | ||
38 | + this.entitiesTableConfig.componentsData.deviceType = deviceType; | ||
39 | + this.entitiesTableConfig.table.updateData(); | ||
40 | + } | ||
41 | + | ||
42 | +} |
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 | +<div class="tb-details-buttons"> | ||
19 | + <button mat-raised-button color="primary" | ||
20 | + [disabled]="(isLoading$ | async)" | ||
21 | + (click)="onEntityAction($event, 'makePublic')" | ||
22 | + [fxShow]="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer(entity) && !entity?.customerIsPublic"> | ||
23 | + {{'device.make-public' | translate }} | ||
24 | + </button> | ||
25 | + <button mat-raised-button color="primary" | ||
26 | + [disabled]="(isLoading$ | async)" | ||
27 | + (click)="onEntityAction($event, 'assignToCustomer')" | ||
28 | + [fxShow]="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer(entity)"> | ||
29 | + {{'device.assign-to-customer' | translate }} | ||
30 | + </button> | ||
31 | + <button mat-raised-button color="primary" | ||
32 | + [disabled]="(isLoading$ | async)" | ||
33 | + (click)="onEntityAction($event, 'unassignFromCustomer')" | ||
34 | + [fxShow]="!isEdit && (deviceScope === 'customer' || deviceScope === 'tenant') && isAssignedToCustomer(entity)"> | ||
35 | + {{ (entity?.customerIsPublic ? 'device.make-private' : 'device.unassign-from-customer') | translate }} | ||
36 | + </button> | ||
37 | + <button mat-raised-button color="primary" | ||
38 | + [disabled]="(isLoading$ | async)" | ||
39 | + (click)="onEntityAction($event, 'manageCredentials')" | ||
40 | + [fxShow]="!isEdit"> | ||
41 | + {{ (deviceScope === 'customer_user' ? 'device.view-credentials' : 'device.manage-credentials') | translate }} | ||
42 | + </button> | ||
43 | + <button mat-raised-button color="primary" | ||
44 | + [disabled]="(isLoading$ | async)" | ||
45 | + (click)="onEntityAction($event, 'delete')" | ||
46 | + [fxShow]="!hideDelete() && !isEdit"> | ||
47 | + {{'device.delete' | translate }} | ||
48 | + </button> | ||
49 | + <div fxLayout="row"> | ||
50 | + <button mat-raised-button | ||
51 | + ngxClipboard | ||
52 | + (cbOnSuccess)="onDeviceIdCopied($event)" | ||
53 | + [cbContent]="entity?.id?.id" | ||
54 | + [fxShow]="!isEdit"> | ||
55 | + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> | ||
56 | + <span translate>device.copyId</span> | ||
57 | + </button> | ||
58 | + <button mat-raised-button | ||
59 | + (click)="copyAccessToken($event)" | ||
60 | + [fxShow]="!isEdit"> | ||
61 | + <mat-icon svgIcon="mdi:clipboard-arrow-left"></mat-icon> | ||
62 | + <span translate>device.copyAccessToken</span> | ||
63 | + </button> | ||
64 | + </div> | ||
65 | +</div> | ||
66 | +<div class="mat-padding" fxLayout="column"> | ||
67 | + <mat-form-field class="mat-block" | ||
68 | + [fxShow]="!isEdit && isAssignedToCustomer(entity) | ||
69 | + && !entity?.customerIsPublic && deviceScope === 'tenant'"> | ||
70 | + <mat-label translate>device.assignedToCustomer</mat-label> | ||
71 | + <input matInput disabled [ngModel]="entity?.customerTitle"> | ||
72 | + </mat-form-field> | ||
73 | + <div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;" | ||
74 | + [fxShow]="!isEdit && entity?.customerIsPublic && (deviceScope === 'customer' || deviceScope === 'tenant')"> | ||
75 | + {{ 'device.device-public' | translate }} | ||
76 | + </div> | ||
77 | + <form #entityNgForm="ngForm" [formGroup]="entityForm"> | ||
78 | + <fieldset [disabled]="(isLoading$ | async) || !isEdit"> | ||
79 | + <mat-form-field class="mat-block"> | ||
80 | + <mat-label translate>device.name</mat-label> | ||
81 | + <input matInput formControlName="name" required> | ||
82 | + <mat-error *ngIf="entityForm.get('name').hasError('required')"> | ||
83 | + {{ 'device.name-required' | translate }} | ||
84 | + </mat-error> | ||
85 | + </mat-form-field> | ||
86 | + <tb-entity-subtype-autocomplete | ||
87 | + formControlName="type" | ||
88 | + [required]="true" | ||
89 | + [entityType]="entityType.DEVICE" | ||
90 | + > | ||
91 | + </tb-entity-subtype-autocomplete> | ||
92 | + <mat-form-field class="mat-block"> | ||
93 | + <mat-label translate>device.label</mat-label> | ||
94 | + <input matInput formControlName="label"> | ||
95 | + </mat-form-field> | ||
96 | + <div formGroupName="additionalInfo" fxLayout="column"> | ||
97 | + <mat-checkbox fxFlex formControlName="gateway"> | ||
98 | + {{ 'device.is-gateway' | translate }} | ||
99 | + </mat-checkbox> | ||
100 | + <mat-form-field class="mat-block"> | ||
101 | + <mat-label translate>device.description</mat-label> | ||
102 | + <textarea matInput formControlName="description" rows="2"></textarea> | ||
103 | + </mat-form-field> | ||
104 | + </div> | ||
105 | + </fieldset> | ||
106 | + </form> | ||
107 | +</div> |
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 | +:host { | ||
18 | + | ||
19 | +} |
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, OnInit } from '@angular/core'; | ||
18 | +import { select, Store } from '@ngrx/store'; | ||
19 | +import { AppState } from '@core/core.state'; | ||
20 | +import { EntityComponent } from '@shared/components/entity/entity.component'; | ||
21 | +import { FormBuilder, FormGroup, Validators } from '@angular/forms'; | ||
22 | +import { User } from '@shared/models/user.model'; | ||
23 | +import { selectAuth, selectUserDetails } from '@core/auth/auth.selectors'; | ||
24 | +import { map } from 'rxjs/operators'; | ||
25 | +import { Authority } from '@shared/models/authority.enum'; | ||
26 | +import {DeviceInfo} from '@shared/models/device.models'; | ||
27 | +import {EntityType} from '@shared/models/entity-type.models'; | ||
28 | +import {NULL_UUID} from '@shared/models/id/has-uuid'; | ||
29 | + | ||
30 | +@Component({ | ||
31 | + selector: 'tb-device', | ||
32 | + templateUrl: './device.component.html', | ||
33 | + styleUrls: ['./device.component.scss'] | ||
34 | +}) | ||
35 | +export class DeviceComponent extends EntityComponent<DeviceInfo> { | ||
36 | + | ||
37 | + entityType = EntityType; | ||
38 | + | ||
39 | + deviceScope: 'tenant' | 'customer' | 'customer_user'; | ||
40 | + | ||
41 | + constructor(protected store: Store<AppState>, | ||
42 | + public fb: FormBuilder) { | ||
43 | + super(store); | ||
44 | + } | ||
45 | + | ||
46 | + ngOnInit() { | ||
47 | + this.deviceScope = this.entitiesTableConfig.componentsData.deviceScope; | ||
48 | + super.ngOnInit(); | ||
49 | + } | ||
50 | + | ||
51 | + hideDelete() { | ||
52 | + if (this.entitiesTableConfig) { | ||
53 | + return !this.entitiesTableConfig.deleteEnabled(this.entity); | ||
54 | + } else { | ||
55 | + return false; | ||
56 | + } | ||
57 | + } | ||
58 | + | ||
59 | + isAssignedToCustomer(entity: DeviceInfo): boolean { | ||
60 | + return entity && entity.customerId && entity.customerId.id !== NULL_UUID; | ||
61 | + } | ||
62 | + | ||
63 | + buildForm(entity: DeviceInfo): FormGroup { | ||
64 | + return this.fb.group( | ||
65 | + { | ||
66 | + name: [entity ? entity.name : '', [Validators.required]], | ||
67 | + type: [entity ? entity.type : null, [Validators.required]], | ||
68 | + label: [entity ? entity.label : ''], | ||
69 | + additionalInfo: this.fb.group( | ||
70 | + { | ||
71 | + gateway: [entity && entity.additionalInfo ? entity.additionalInfo.gateway : false], | ||
72 | + description: [entity && entity.additionalInfo ? entity.additionalInfo.description : ''], | ||
73 | + } | ||
74 | + ) | ||
75 | + } | ||
76 | + ); | ||
77 | + } | ||
78 | + | ||
79 | + updateForm(entity: DeviceInfo) { | ||
80 | + this.entityForm.patchValue({name: entity.name}); | ||
81 | + this.entityForm.patchValue({type: entity.type}); | ||
82 | + this.entityForm.patchValue({label: entity.label}); | ||
83 | + this.entityForm.patchValue({additionalInfo: | ||
84 | + {gateway: entity.additionalInfo ? entity.additionalInfo.gateway : false}}); | ||
85 | + this.entityForm.patchValue({additionalInfo: {description: entity.additionalInfo ? entity.additionalInfo.description : ''}}); | ||
86 | + } | ||
87 | + | ||
88 | +} |
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 { NgModule } from '@angular/core'; | ||
18 | +import { CommonModule } from '@angular/common'; | ||
19 | +import { SharedModule } from '@shared/shared.module'; | ||
20 | +import {DeviceComponent} from '@modules/home/pages/device/device.component'; | ||
21 | +import {DeviceRoutingModule} from './device-routing.module'; | ||
22 | +import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; | ||
23 | + | ||
24 | +@NgModule({ | ||
25 | + entryComponents: [ | ||
26 | + DeviceComponent, | ||
27 | + DeviceTableHeaderComponent | ||
28 | + ], | ||
29 | + declarations: [ | ||
30 | + DeviceComponent, | ||
31 | + DeviceTableHeaderComponent | ||
32 | + ], | ||
33 | + imports: [ | ||
34 | + CommonModule, | ||
35 | + SharedModule, | ||
36 | + DeviceRoutingModule | ||
37 | + ] | ||
38 | +}) | ||
39 | +export class DeviceModule { } |
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 | + | ||
19 | +import {ActivatedRouteSnapshot, Resolve, Router} from '@angular/router'; | ||
20 | + | ||
21 | +import { Tenant } from '@shared/models/tenant.model'; | ||
22 | +import { | ||
23 | + CellActionDescriptor, | ||
24 | + checkBoxCell, | ||
25 | + DateEntityTableColumn, | ||
26 | + EntityTableColumn, | ||
27 | + EntityTableConfig | ||
28 | +} from '@shared/components/entity/entities-table-config.models'; | ||
29 | +import { TenantService } from '@core/http/tenant.service'; | ||
30 | +import { TranslateService } from '@ngx-translate/core'; | ||
31 | +import { DatePipe } from '@angular/common'; | ||
32 | +import { | ||
33 | + EntityType, | ||
34 | + entityTypeResources, | ||
35 | + entityTypeTranslations | ||
36 | +} from '@shared/models/entity-type.models'; | ||
37 | +import { TenantComponent } from '@modules/home/pages/tenant/tenant.component'; | ||
38 | +import { EntityAction } from '@shared/components/entity/entity-component.models'; | ||
39 | +import { User } from '@shared/models/user.model'; | ||
40 | +import {Device, DeviceInfo} from '@app/shared/models/device.models'; | ||
41 | +import {DeviceComponent} from '@modules/home/pages/device/device.component'; | ||
42 | +import {Observable, of} from 'rxjs'; | ||
43 | +import {select, Store} from '@ngrx/store'; | ||
44 | +import {selectAuth, selectAuthUser} from '@core/auth/auth.selectors'; | ||
45 | +import {map, mergeMap, take, tap} from 'rxjs/operators'; | ||
46 | +import {AppState} from '@core/core.state'; | ||
47 | +import {DeviceService} from '@app/core/http/device.service'; | ||
48 | +import {Authority} from '@app/shared/models/authority.enum'; | ||
49 | +import {CustomerService} from '@core/http/customer.service'; | ||
50 | +import {Customer} from '@app/shared/models/customer.model'; | ||
51 | +import {NULL_UUID} from '@shared/models/id/has-uuid'; | ||
52 | +import {BroadcastService} from '@core/services/broadcast.service'; | ||
53 | +import {DeviceTableHeaderComponent} from '@modules/home/pages/device/device-table-header.component'; | ||
54 | + | ||
55 | +@Injectable() | ||
56 | +export class DevicesTableConfigResolver implements Resolve<EntityTableConfig<DeviceInfo>> { | ||
57 | + | ||
58 | + private readonly config: EntityTableConfig<DeviceInfo> = new EntityTableConfig<DeviceInfo>(); | ||
59 | + | ||
60 | + private customerId: string; | ||
61 | + | ||
62 | + constructor(private store: Store<AppState>, | ||
63 | + private broadcast: BroadcastService, | ||
64 | + private deviceService: DeviceService, | ||
65 | + private customerService: CustomerService, | ||
66 | + private translate: TranslateService, | ||
67 | + private datePipe: DatePipe, | ||
68 | + private router: Router) { | ||
69 | + | ||
70 | + this.config.entityType = EntityType.CUSTOMER; | ||
71 | + this.config.entityComponent = DeviceComponent; | ||
72 | + this.config.entityTranslations = entityTypeTranslations.get(EntityType.DEVICE); | ||
73 | + this.config.entityResources = entityTypeResources.get(EntityType.DEVICE); | ||
74 | + | ||
75 | + this.config.deleteEntityTitle = device => this.translate.instant('device.delete-device-title', { deviceName: device.name }); | ||
76 | + this.config.deleteEntityContent = () => this.translate.instant('device.delete-device-text'); | ||
77 | + this.config.deleteEntitiesTitle = count => this.translate.instant('device.delete-devices-title', {count}); | ||
78 | + this.config.deleteEntitiesContent = () => this.translate.instant('device.delete-devices-text'); | ||
79 | + | ||
80 | + this.config.loadEntity = id => this.deviceService.getDeviceInfo(id.id); | ||
81 | + this.config.saveEntity = device => { | ||
82 | + return this.deviceService.saveDevice(device).pipe( | ||
83 | + tap(() => { | ||
84 | + this.broadcast.broadcast('deviceSaved'); | ||
85 | + }), | ||
86 | + mergeMap((savedDevice) => this.deviceService.getDeviceInfo(savedDevice.id.id) | ||
87 | + )); | ||
88 | + }; | ||
89 | + this.config.onEntityAction = action => this.onDeviceAction(action); | ||
90 | + | ||
91 | + this.config.headerComponent = DeviceTableHeaderComponent; | ||
92 | + | ||
93 | + } | ||
94 | + | ||
95 | + resolve(route: ActivatedRouteSnapshot): Observable<EntityTableConfig<DeviceInfo>> { | ||
96 | + const routeParams = route.params; | ||
97 | + this.config.componentsData = { | ||
98 | + deviceScope: route.data.devicesType, | ||
99 | + deviceType: '' | ||
100 | + }; | ||
101 | + this.customerId = routeParams.customerId; | ||
102 | + return this.store.pipe(select(selectAuthUser), take(1)).pipe( | ||
103 | + tap((authUser) => { | ||
104 | + if (authUser.authority === Authority.CUSTOMER_USER) { | ||
105 | + this.config.componentsData.deviceScope = 'customer_user'; | ||
106 | + this.customerId = authUser.customerId; | ||
107 | + } | ||
108 | + }), | ||
109 | + mergeMap(() => | ||
110 | + this.customerId ? this.customerService.getCustomer(this.customerId) : of(null as Customer) | ||
111 | + ), | ||
112 | + map((parentCustomer) => { | ||
113 | + if (parentCustomer) { | ||
114 | + if (parentCustomer.additionalInfo && parentCustomer.additionalInfo.isPublic) { | ||
115 | + this.config.tableTitle = this.translate.instant('customer.public-devices'); | ||
116 | + } else { | ||
117 | + this.config.tableTitle = parentCustomer.title + ': ' + this.translate.instant('device.devices'); | ||
118 | + } | ||
119 | + } else { | ||
120 | + this.config.tableTitle = this.translate.instant('device.devices'); | ||
121 | + } | ||
122 | + this.config.columns = this.configureColumns(this.config.componentsData.deviceScope); | ||
123 | + this.configureEntityFunctions(this.config.componentsData.deviceScope); | ||
124 | + this.config.cellActionDescriptors = this.configureCellActions(this.config.componentsData.deviceScope); | ||
125 | + return this.config; | ||
126 | + }) | ||
127 | + ); | ||
128 | + } | ||
129 | + | ||
130 | + configureColumns(deviceScope: string): Array<EntityTableColumn<Device | DeviceInfo>> { | ||
131 | + const columns: Array<EntityTableColumn<Device | DeviceInfo>> = [ | ||
132 | + new DateEntityTableColumn<DeviceInfo>('createdTime', 'device.created-time', this.datePipe, '150px'), | ||
133 | + new EntityTableColumn<DeviceInfo>('name', 'device.name'), | ||
134 | + new EntityTableColumn<DeviceInfo>('type', 'device.device-type'), | ||
135 | + new EntityTableColumn<DeviceInfo>('label', 'device.label') | ||
136 | + ]; | ||
137 | + if (deviceScope === 'tenant') { | ||
138 | + columns.push( | ||
139 | + new EntityTableColumn<DeviceInfo>('customerTitle', 'customer.customer'), | ||
140 | + new EntityTableColumn<DeviceInfo>('customerIsPublic', 'device.public', '60px', | ||
141 | + entity => { | ||
142 | + return checkBoxCell(entity.customerIsPublic); | ||
143 | + }, () => ({}), false), | ||
144 | + ); | ||
145 | + } | ||
146 | + columns.push( | ||
147 | + new EntityTableColumn<DeviceInfo>('gateway', 'device.is-gateway', '60px', | ||
148 | + entity => { | ||
149 | + return checkBoxCell(entity.additionalInfo && entity.additionalInfo.gateway); | ||
150 | + }, () => ({}), false) | ||
151 | + ); | ||
152 | + return columns; | ||
153 | + } | ||
154 | + | ||
155 | + configureEntityFunctions(deviceScope: string): void { | ||
156 | + if (deviceScope === 'tenant') { | ||
157 | + this.config.entitiesFetchFunction = pageLink => this.deviceService.getTenantDeviceInfos(pageLink, this.config.componentsData.deviceType); | ||
158 | + this.config.deleteEntity = id => this.deviceService.deleteDevice(id.id); | ||
159 | + } else { | ||
160 | + this.config.entitiesFetchFunction = pageLink => this.deviceService.getCustomerDeviceInfos(this.customerId, pageLink, this.config.componentsData.deviceType); | ||
161 | + this.config.deleteEntity = id => this.deviceService.unassignDeviceFromCustomer(id.id); | ||
162 | + } | ||
163 | + } | ||
164 | + | ||
165 | + configureCellActions(deviceScope: string): Array<CellActionDescriptor<Device | DeviceInfo>> { | ||
166 | + const actions: Array<CellActionDescriptor<Device | DeviceInfo>> = []; | ||
167 | + if (deviceScope === 'tenant') { | ||
168 | + actions.push( | ||
169 | + { | ||
170 | + name: this.translate.instant('device.make-public'), | ||
171 | + icon: 'share', | ||
172 | + isEnabled: (entity) => (!entity.customerId || entity.customerId.id === NULL_UUID), | ||
173 | + onAction: ($event, entity) => this.makePublic($event, entity) | ||
174 | + } | ||
175 | + ); | ||
176 | + } | ||
177 | + return actions; | ||
178 | + } | ||
179 | + | ||
180 | + makePublic($event: Event, device: Device) { | ||
181 | + if ($event) { | ||
182 | + $event.stopPropagation(); | ||
183 | + } | ||
184 | + // TODO: | ||
185 | + } | ||
186 | + | ||
187 | + onDeviceAction(action: EntityAction<Device | DeviceInfo>): boolean { | ||
188 | + switch (action.action) { | ||
189 | + case 'makePublic': | ||
190 | + this.makePublic(action.event, action.entity); | ||
191 | + return true; | ||
192 | + } | ||
193 | + return false; | ||
194 | + } | ||
195 | + | ||
196 | +} |
@@ -23,6 +23,7 @@ import { TenantModule } from '@modules/home/pages/tenant/tenant.module'; | @@ -23,6 +23,7 @@ import { TenantModule } from '@modules/home/pages/tenant/tenant.module'; | ||
23 | // import { CustomerModule } from '@modules/home/pages/customer/customer.module'; | 23 | // import { CustomerModule } from '@modules/home/pages/customer/customer.module'; |
24 | // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; | 24 | // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; |
25 | import { UserModule } from '@modules/home/pages/user/user.module'; | 25 | import { UserModule } from '@modules/home/pages/user/user.module'; |
26 | +import {DeviceModule} from '@modules/home/pages/device/device.module'; | ||
26 | 27 | ||
27 | @NgModule({ | 28 | @NgModule({ |
28 | exports: [ | 29 | exports: [ |
@@ -30,6 +31,7 @@ import { UserModule } from '@modules/home/pages/user/user.module'; | @@ -30,6 +31,7 @@ import { UserModule } from '@modules/home/pages/user/user.module'; | ||
30 | HomeLinksModule, | 31 | HomeLinksModule, |
31 | ProfileModule, | 32 | ProfileModule, |
32 | TenantModule, | 33 | TenantModule, |
34 | + DeviceModule, | ||
33 | // CustomerModule, | 35 | // CustomerModule, |
34 | // AuditLogModule, | 36 | // AuditLogModule, |
35 | UserModule | 37 | UserModule |
@@ -74,8 +74,6 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI | @@ -74,8 +74,6 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI | ||
74 | 74 | ||
75 | filteredDashboards: Observable<Array<DashboardInfo>>; | 75 | filteredDashboards: Observable<Array<DashboardInfo>>; |
76 | 76 | ||
77 | - private valueLoaded = false; | ||
78 | - | ||
79 | private searchText = ''; | 77 | private searchText = ''; |
80 | 78 | ||
81 | private propagateChange = (v: any) => { }; | 79 | private propagateChange = (v: any) => { }; |
@@ -97,7 +95,21 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI | @@ -97,7 +95,21 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI | ||
97 | } | 95 | } |
98 | 96 | ||
99 | ngOnInit() { | 97 | ngOnInit() { |
100 | - | 98 | + this.filteredDashboards = this.selectDashboardFormGroup.get('dashboard').valueChanges |
99 | + .pipe( | ||
100 | + tap(value => { | ||
101 | + let modelValue; | ||
102 | + if (typeof value === 'string' || !value) { | ||
103 | + modelValue = null; | ||
104 | + } else { | ||
105 | + modelValue = this.useIdValue ? value.id.id : value; | ||
106 | + } | ||
107 | + this.updateView(modelValue); | ||
108 | + }), | ||
109 | + startWith<string | DashboardInfo>(''), | ||
110 | + map(value => value ? (typeof value === 'string' ? value : value.name) : ''), | ||
111 | + mergeMap(name => this.fetchDashboards(name) ) | ||
112 | + ); | ||
101 | } | 113 | } |
102 | 114 | ||
103 | ngAfterViewInit(): void { | 115 | ngAfterViewInit(): void { |
@@ -123,48 +135,23 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI | @@ -123,48 +135,23 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI | ||
123 | this.disabled = isDisabled; | 135 | this.disabled = isDisabled; |
124 | } | 136 | } |
125 | 137 | ||
126 | - initFilteredResults(): void { | ||
127 | - this.filteredDashboards = this.selectDashboardFormGroup.get('dashboard').valueChanges | ||
128 | - .pipe( | ||
129 | - startWith<string | DashboardInfo>(''), | ||
130 | - tap(value => { | ||
131 | - if (this.valueLoaded) { | ||
132 | - let modelValue; | ||
133 | - if (typeof value === 'string' || !value) { | ||
134 | - modelValue = null; | ||
135 | - } else { | ||
136 | - modelValue = this.useIdValue ? value.id.id : value; | ||
137 | - } | ||
138 | - this.updateView(modelValue); | ||
139 | - } | ||
140 | - }), | ||
141 | - map(value => value ? (typeof value === 'string' ? value : value.name) : ''), | ||
142 | - mergeMap(name => this.fetchDashboards(name) ) | ||
143 | - ); | ||
144 | - } | ||
145 | - | ||
146 | writeValue(value: DashboardInfo | string | null): void { | 138 | writeValue(value: DashboardInfo | string | null): void { |
147 | - this.valueLoaded = false; | ||
148 | this.searchText = ''; | 139 | this.searchText = ''; |
149 | - this.initFilteredResults(); | ||
150 | if (value != null) { | 140 | if (value != null) { |
151 | if (typeof value === 'string') { | 141 | if (typeof value === 'string') { |
152 | this.dashboardService.getDashboardInfo(value).subscribe( | 142 | this.dashboardService.getDashboardInfo(value).subscribe( |
153 | (dashboard) => { | 143 | (dashboard) => { |
154 | this.modelValue = this.useIdValue ? dashboard.id.id : dashboard; | 144 | this.modelValue = this.useIdValue ? dashboard.id.id : dashboard; |
155 | this.selectDashboardFormGroup.get('dashboard').patchValue(dashboard, {emitEvent: true}); | 145 | this.selectDashboardFormGroup.get('dashboard').patchValue(dashboard, {emitEvent: true}); |
156 | - this.valueLoaded = true; | ||
157 | } | 146 | } |
158 | ); | 147 | ); |
159 | } else { | 148 | } else { |
160 | this.modelValue = this.useIdValue ? value.id.id : value; | 149 | this.modelValue = this.useIdValue ? value.id.id : value; |
161 | - this.selectDashboardFormGroup.get('dashboard').patchValue(value, {emitEvent: false}); | ||
162 | - this.valueLoaded = true; | 150 | + this.selectDashboardFormGroup.get('dashboard').patchValue(value, {emitEvent: true}); |
163 | } | 151 | } |
164 | } else { | 152 | } else { |
165 | this.modelValue = null; | 153 | this.modelValue = null; |
166 | - this.selectDashboardFormGroup.get('dashboard').patchValue(null, {emitEvent: false}); | ||
167 | - this.valueLoaded = true; | 154 | + this.selectDashboardFormGroup.get('dashboard').patchValue(null, {emitEvent: true}); |
168 | } | 155 | } |
169 | } | 156 | } |
170 | 157 |
@@ -76,7 +76,8 @@ export class EntityTableColumn<T extends BaseData<HasId>> { | @@ -76,7 +76,8 @@ export class EntityTableColumn<T extends BaseData<HasId>> { | ||
76 | public title: string, | 76 | public title: string, |
77 | public maxWidth: string = '100%', | 77 | public maxWidth: string = '100%', |
78 | public cellContentFunction: CellContentFunction<T> = (entity, property) => entity[property], | 78 | public cellContentFunction: CellContentFunction<T> = (entity, property) => entity[property], |
79 | - public cellStyleFunction: CellStyleFunction<T> = () => ({})) { | 79 | + public cellStyleFunction: CellStyleFunction<T> = () => ({}), |
80 | + public sortable: boolean = true) { | ||
80 | } | 81 | } |
81 | } | 82 | } |
82 | 83 | ||
@@ -135,3 +136,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | @@ -135,3 +136,7 @@ export class EntityTableConfig<T extends BaseData<HasId>, P extends PageLink = P | ||
135 | entitiesFetchFunction: EntitiesFetchFunction<T, P> = () => of(emptyPageData<T>()); | 136 | entitiesFetchFunction: EntitiesFetchFunction<T, P> = () => of(emptyPageData<T>()); |
136 | onEntityAction: EntityActionFunction<T> = () => false; | 137 | onEntityAction: EntityActionFunction<T> = () => false; |
137 | } | 138 | } |
139 | + | ||
140 | +export function checkBoxCell(value: boolean): string { | ||
141 | + return `<mat-icon class="material-icons mat-icon">${value ? 'check_box' : 'check_box_outline_blank'}</mat-icon>`; | ||
142 | +} |
@@ -120,7 +120,7 @@ | @@ -120,7 +120,7 @@ | ||
120 | </mat-cell> | 120 | </mat-cell> |
121 | </ng-container> | 121 | </ng-container> |
122 | <ng-container [matColumnDef]="column.key" *ngFor="let column of columns"> | 122 | <ng-container [matColumnDef]="column.key" *ngFor="let column of columns"> |
123 | - <mat-header-cell *matHeaderCellDef [ngStyle]="{maxWidth: column.maxWidth}" mat-sort-header> {{ column.title | translate }} </mat-header-cell> | 123 | + <mat-header-cell *matHeaderCellDef [ngStyle]="{maxWidth: column.maxWidth}" mat-sort-header [disabled]="!column.sortable"> {{ column.title | translate }} </mat-header-cell> |
124 | <mat-cell *matCellDef="let entity" [ngStyle]="cellStyle(entity, column)" [innerHTML]="cellContent(entity, column)"></mat-cell> | 124 | <mat-cell *matCellDef="let entity" [ngStyle]="cellStyle(entity, column)" [innerHTML]="cellContent(entity, column)"></mat-cell> |
125 | </ng-container> | 125 | </ng-container> |
126 | <ng-container matColumnDef="actions" stickyEnd> | 126 | <ng-container matColumnDef="actions" stickyEnd> |
@@ -82,7 +82,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | @@ -82,7 +82,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn | ||
82 | 82 | ||
83 | isDetailsOpen = false; | 83 | isDetailsOpen = false; |
84 | 84 | ||
85 | - @ViewChild('entityTableHeader', {static: false}) entityTableHeaderAnchor: TbAnchorComponent; | 85 | + @ViewChild('entityTableHeader', {static: true}) entityTableHeaderAnchor: TbAnchorComponent; |
86 | 86 | ||
87 | @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; | 87 | @ViewChild('searchInput', {static: false}) searchInputField: ElementRef; |
88 | 88 |
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 | +<mat-form-field [formGroup]="subTypeFormGroup" class="mat-block"> | ||
19 | + <mat-label>{{ entitySubtypeText | translate }}</mat-label> | ||
20 | + <input matInput type="text" placeholder="{{ selectEntitySubtypeText | translate }}" | ||
21 | + #subTypeInput | ||
22 | + formControlName="subType" | ||
23 | + [required]="required" | ||
24 | + [matAutocomplete]="subTypeAutocomplete"> | ||
25 | + <button *ngIf="subTypeFormGroup.get('subType').value && !disabled" | ||
26 | + type="button" | ||
27 | + matSuffix mat-button mat-icon-button aria-label="Clear" | ||
28 | + (click)="clear()"> | ||
29 | + <mat-icon class="material-icons">close</mat-icon> | ||
30 | + </button> | ||
31 | + <mat-autocomplete #subTypeAutocomplete="matAutocomplete" [displayWith]="displaySubTypeFn"> | ||
32 | + <mat-option *ngFor="let subType of filteredSubTypes | async" [value]="subType"> | ||
33 | + <span [innerHTML]="subType.type | highlight:searchText"></span> | ||
34 | + </mat-option> | ||
35 | + </mat-autocomplete> | ||
36 | + <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')"> | ||
37 | + {{ entitySubtypeRequiredText | translate }} | ||
38 | + </mat-error> | ||
39 | +</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 | + | ||
17 | +import {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core'; | ||
18 | +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; | ||
19 | +import {Observable, of, throwError, Subscription} from 'rxjs'; | ||
20 | +import {PageLink} from '@shared/models/page/page-link'; | ||
21 | +import {Direction} from '@shared/models/page/sort-order'; | ||
22 | +import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators'; | ||
23 | +import {PageData, emptyPageData} from '@shared/models/page/page-data'; | ||
24 | +import {DashboardInfo} from '@app/shared/models/dashboard.models'; | ||
25 | +import {DashboardId} from '@app/shared/models/id/dashboard-id'; | ||
26 | +import {DashboardService} from '@core/http/dashboard.service'; | ||
27 | +import {Store} from '@ngrx/store'; | ||
28 | +import {AppState} from '@app/core/core.state'; | ||
29 | +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; | ||
30 | +import {Authority} from '@shared/models/authority.enum'; | ||
31 | +import {TranslateService} from '@ngx-translate/core'; | ||
32 | +import {DeviceService} from '@core/http/device.service'; | ||
33 | +import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; | ||
34 | +import {BroadcastService} from '@app/core/services/broadcast.service'; | ||
35 | + | ||
36 | +@Component({ | ||
37 | + selector: 'tb-entity-subtype-autocomplete', | ||
38 | + templateUrl: './entity-subtype-autocomplete.component.html', | ||
39 | + styleUrls: [], | ||
40 | + providers: [{ | ||
41 | + provide: NG_VALUE_ACCESSOR, | ||
42 | + useExisting: forwardRef(() => EntitySubTypeAutocompleteComponent), | ||
43 | + multi: true | ||
44 | + }] | ||
45 | +}) | ||
46 | +export class EntitySubTypeAutocompleteComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { | ||
47 | + | ||
48 | + subTypeFormGroup: FormGroup; | ||
49 | + | ||
50 | + modelValue: string | null; | ||
51 | + | ||
52 | + @Input() | ||
53 | + entityType: EntityType; | ||
54 | + | ||
55 | + @Input() | ||
56 | + required: boolean; | ||
57 | + | ||
58 | + @Input() | ||
59 | + disabled: boolean; | ||
60 | + | ||
61 | + @ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; | ||
62 | + | ||
63 | + selectEntitySubtypeText: string; | ||
64 | + entitySubtypeText: string; | ||
65 | + entitySubtypeRequiredText: string; | ||
66 | + | ||
67 | + filteredSubTypes: Observable<Array<EntitySubtype>>; | ||
68 | + | ||
69 | + subTypes: Observable<Array<EntitySubtype>>; | ||
70 | + | ||
71 | + private broadcastSubscription: Subscription; | ||
72 | + | ||
73 | + private searchText = ''; | ||
74 | + | ||
75 | + private propagateChange = (v: any) => { }; | ||
76 | + | ||
77 | + constructor(private store: Store<AppState>, | ||
78 | + private broadcast: BroadcastService, | ||
79 | + public translate: TranslateService, | ||
80 | + private deviceService: DeviceService, | ||
81 | + private fb: FormBuilder) { | ||
82 | + this.subTypeFormGroup = this.fb.group({ | ||
83 | + subType: [null] | ||
84 | + }); | ||
85 | + } | ||
86 | + | ||
87 | + registerOnChange(fn: any): void { | ||
88 | + this.propagateChange = fn; | ||
89 | + } | ||
90 | + | ||
91 | + registerOnTouched(fn: any): void { | ||
92 | + } | ||
93 | + | ||
94 | + ngOnInit() { | ||
95 | + | ||
96 | + switch (this.entityType) { | ||
97 | + case EntityType.ASSET: | ||
98 | + this.selectEntitySubtypeText = 'asset.select-asset-type'; | ||
99 | + this.entitySubtypeText = 'asset.asset-type'; | ||
100 | + this.entitySubtypeRequiredText = 'asset.asset-type-required'; | ||
101 | + this.broadcastSubscription = this.broadcast.on('assetSaved', () => { | ||
102 | + this.subTypes = null; | ||
103 | + }); | ||
104 | + break; | ||
105 | + case EntityType.DEVICE: | ||
106 | + this.selectEntitySubtypeText = 'device.select-device-type'; | ||
107 | + this.entitySubtypeText = 'device.device-type'; | ||
108 | + this.entitySubtypeRequiredText = 'device.device-type-required'; | ||
109 | + this.broadcastSubscription = this.broadcast.on('deviceSaved', () => { | ||
110 | + this.subTypes = null; | ||
111 | + }); | ||
112 | + break; | ||
113 | + case EntityType.ENTITY_VIEW: | ||
114 | + this.selectEntitySubtypeText = 'entity-view.select-entity-view-type'; | ||
115 | + this.entitySubtypeText = 'entity-view.entity-view-type'; | ||
116 | + this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required'; | ||
117 | + this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => { | ||
118 | + this.subTypes = null; | ||
119 | + }); | ||
120 | + break; | ||
121 | + } | ||
122 | + | ||
123 | + this.filteredSubTypes = this.subTypeFormGroup.get('subType').valueChanges | ||
124 | + .pipe( | ||
125 | + tap(value => { | ||
126 | + let modelValue; | ||
127 | + if (!value) { | ||
128 | + modelValue = null; | ||
129 | + } else if (typeof value === 'string') { | ||
130 | + modelValue = value; | ||
131 | + } else { | ||
132 | + modelValue = value.type; | ||
133 | + } | ||
134 | + this.updateView(modelValue); | ||
135 | + }), | ||
136 | + startWith<string | EntitySubtype>(''), | ||
137 | + map(value => value ? (typeof value === 'string' ? value : value.type) : ''), | ||
138 | + mergeMap(type => this.fetchSubTypes(type) ) | ||
139 | + ); | ||
140 | + } | ||
141 | + | ||
142 | + ngAfterViewInit(): void { | ||
143 | + } | ||
144 | + | ||
145 | + ngOnDestroy(): void { | ||
146 | + if (this.broadcastSubscription) { | ||
147 | + this.broadcastSubscription.unsubscribe(); | ||
148 | + } | ||
149 | + } | ||
150 | + | ||
151 | + setDisabledState(isDisabled: boolean): void { | ||
152 | + this.disabled = isDisabled; | ||
153 | + } | ||
154 | + | ||
155 | + writeValue(value: string | null): void { | ||
156 | + this.searchText = ''; | ||
157 | + if (value != null) { | ||
158 | + this.modelValue = value; | ||
159 | + this.fetchSubTypes(value, true).subscribe( | ||
160 | + (subTypes) => { | ||
161 | + const subType = subTypes && subTypes.length === 1 ? subTypes[0] : null; | ||
162 | + this.subTypeFormGroup.get('subType').patchValue(subType, {emitEvent: true}); | ||
163 | + } | ||
164 | + ); | ||
165 | + } else { | ||
166 | + this.modelValue = null; | ||
167 | + this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); | ||
168 | + } | ||
169 | + } | ||
170 | + | ||
171 | + updateView(value: string | null) { | ||
172 | + if (this.modelValue !== value) { | ||
173 | + this.modelValue = value; | ||
174 | + this.propagateChange(this.modelValue); | ||
175 | + } | ||
176 | + } | ||
177 | + | ||
178 | + displaySubTypeFn(subType?: EntitySubtype): string | undefined { | ||
179 | + return subType ? subType.type : undefined; | ||
180 | + } | ||
181 | + | ||
182 | + fetchSubTypes(searchText?: string, strictMatch: boolean = false): Observable<Array<EntitySubtype>> { | ||
183 | + this.searchText = searchText; | ||
184 | + return this.getSubTypes().pipe( | ||
185 | + map(subTypes => subTypes.filter( subType => { | ||
186 | + if (strictMatch) { | ||
187 | + return searchText ? subType.type === searchText : false; | ||
188 | + } else { | ||
189 | + return searchText ? subType.type.toUpperCase().startsWith(searchText.toUpperCase()) : true; | ||
190 | + } | ||
191 | + })) | ||
192 | + ); | ||
193 | + } | ||
194 | + | ||
195 | + getSubTypes(): Observable<Array<EntitySubtype>> { | ||
196 | + if (!this.subTypes) { | ||
197 | + switch (this.entityType) { | ||
198 | + case EntityType.ASSET: | ||
199 | + // TODO: | ||
200 | + break; | ||
201 | + case EntityType.DEVICE: | ||
202 | + this.subTypes = this.deviceService.getDeviceTypes(false, true); | ||
203 | + break; | ||
204 | + case EntityType.ENTITY_VIEW: | ||
205 | + // TODO: | ||
206 | + break; | ||
207 | + } | ||
208 | + if (this.subTypes) { | ||
209 | + this.subTypes = this.subTypes.pipe( | ||
210 | + publishReplay(1), | ||
211 | + refCount() | ||
212 | + ); | ||
213 | + } else { | ||
214 | + return throwError(null); | ||
215 | + } | ||
216 | + } | ||
217 | + return this.subTypes; | ||
218 | + } | ||
219 | + | ||
220 | + clear() { | ||
221 | + this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); | ||
222 | + setTimeout(() => { | ||
223 | + this.subTypeInput.nativeElement.blur(); | ||
224 | + this.subTypeInput.nativeElement.focus(); | ||
225 | + }, 0); | ||
226 | + } | ||
227 | + | ||
228 | +} |
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 | +<mat-form-field [formGroup]="subTypeFormGroup" class="mat-block"> | ||
19 | + <mat-label *ngIf="showLabel">{{ entitySubtypeTitle | translate }}</mat-label> | ||
20 | + <mat-select class="tb-entity-subtype-select" matInput formControlName="subType"> | ||
21 | + <mat-option *ngFor="let subType of subTypesOptions | async" [value]="subType"> | ||
22 | + {{ displaySubTypeFn(subType) }} | ||
23 | + </mat-option> | ||
24 | + </mat-select> | ||
25 | + <mat-error *ngIf="subTypeFormGroup.get('subType').hasError('required')"> | ||
26 | + {{ entitySubtypeRequiredText | translate }} | ||
27 | + </mat-error> | ||
28 | +</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 | + mat-select.tb-entity-subtype-select { | ||
18 | + min-width: 200px; | ||
19 | + } | ||
20 | +} |
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 {AfterViewInit, Component, ElementRef, forwardRef, Input, OnInit, ViewChild, OnDestroy} from '@angular/core'; | ||
18 | +import {ControlValueAccessor, FormBuilder, FormGroup, NG_VALUE_ACCESSOR} from '@angular/forms'; | ||
19 | +import {Observable, of, throwError, Subscription, Subject} from 'rxjs'; | ||
20 | +import {PageLink} from '@shared/models/page/page-link'; | ||
21 | +import {Direction} from '@shared/models/page/sort-order'; | ||
22 | +import {filter, map, mergeMap, publishReplay, refCount, startWith, tap, publish} from 'rxjs/operators'; | ||
23 | +import {PageData, emptyPageData} from '@shared/models/page/page-data'; | ||
24 | +import {DashboardInfo} from '@app/shared/models/dashboard.models'; | ||
25 | +import {DashboardId} from '@app/shared/models/id/dashboard-id'; | ||
26 | +import {DashboardService} from '@core/http/dashboard.service'; | ||
27 | +import {Store} from '@ngrx/store'; | ||
28 | +import {AppState} from '@app/core/core.state'; | ||
29 | +import {getCurrentAuthUser} from '@app/core/auth/auth.selectors'; | ||
30 | +import {Authority} from '@shared/models/authority.enum'; | ||
31 | +import {TranslateService} from '@ngx-translate/core'; | ||
32 | +import {DeviceService} from '@core/http/device.service'; | ||
33 | +import {EntitySubtype, EntityType} from '@app/shared/models/entity-type.models'; | ||
34 | +import {BroadcastService} from '@app/core/services/broadcast.service'; | ||
35 | + | ||
36 | +@Component({ | ||
37 | + selector: 'tb-entity-subtype-select', | ||
38 | + templateUrl: './entity-subtype-select.component.html', | ||
39 | + styleUrls: ['./entity-subtype-select.component.scss'], | ||
40 | + providers: [{ | ||
41 | + provide: NG_VALUE_ACCESSOR, | ||
42 | + useExisting: forwardRef(() => EntitySubTypeSelectComponent), | ||
43 | + multi: true | ||
44 | + }] | ||
45 | +}) | ||
46 | +export class EntitySubTypeSelectComponent implements ControlValueAccessor, OnInit, AfterViewInit, OnDestroy { | ||
47 | + | ||
48 | + subTypeFormGroup: FormGroup; | ||
49 | + | ||
50 | + modelValue: string | null; | ||
51 | + | ||
52 | + @Input() | ||
53 | + entityType: EntityType; | ||
54 | + | ||
55 | + @Input() | ||
56 | + showLabel: boolean; | ||
57 | + | ||
58 | + @Input() | ||
59 | + required: boolean; | ||
60 | + | ||
61 | + @Input() | ||
62 | + disabled: boolean; | ||
63 | + | ||
64 | + @Input() | ||
65 | + typeTranslatePrefix: string; | ||
66 | + | ||
67 | + @ViewChild('subTypeInput', {static: true}) subTypeInput: ElementRef; | ||
68 | + | ||
69 | + entitySubtypeTitle: string; | ||
70 | + entitySubtypeRequiredText: string; | ||
71 | + | ||
72 | + subTypesOptions: Observable<Array<EntitySubtype | string>>; | ||
73 | + | ||
74 | + private subTypesOptionsSubject: Subject<string> = new Subject(); | ||
75 | + | ||
76 | + subTypes: Observable<Array<EntitySubtype | string>>; | ||
77 | + | ||
78 | + private broadcastSubscription: Subscription; | ||
79 | + | ||
80 | + private propagateChange = (v: any) => { }; | ||
81 | + | ||
82 | + constructor(private store: Store<AppState>, | ||
83 | + private broadcast: BroadcastService, | ||
84 | + public translate: TranslateService, | ||
85 | + private deviceService: DeviceService, | ||
86 | + private fb: FormBuilder) { | ||
87 | + this.subTypeFormGroup = this.fb.group({ | ||
88 | + subType: [null] | ||
89 | + }); | ||
90 | + } | ||
91 | + | ||
92 | + registerOnChange(fn: any): void { | ||
93 | + this.propagateChange = fn; | ||
94 | + } | ||
95 | + | ||
96 | + registerOnTouched(fn: any): void { | ||
97 | + } | ||
98 | + | ||
99 | + ngOnInit() { | ||
100 | + | ||
101 | + switch (this.entityType) { | ||
102 | + case EntityType.ASSET: | ||
103 | + this.entitySubtypeTitle = 'asset.asset-type'; | ||
104 | + this.entitySubtypeRequiredText = 'asset.asset-type-required'; | ||
105 | + this.broadcastSubscription = this.broadcast.on('assetSaved', () => { | ||
106 | + this.subTypes = null; | ||
107 | + this.subTypesOptionsSubject.next(''); | ||
108 | + }); | ||
109 | + break; | ||
110 | + case EntityType.DEVICE: | ||
111 | + this.entitySubtypeTitle = 'device.device-type'; | ||
112 | + this.entitySubtypeRequiredText = 'device.device-type-required'; | ||
113 | + this.broadcastSubscription = this.broadcast.on('deviceSaved', () => { | ||
114 | + this.subTypes = null; | ||
115 | + this.subTypesOptionsSubject.next(''); | ||
116 | + }); | ||
117 | + break; | ||
118 | + case EntityType.ENTITY_VIEW: | ||
119 | + this.entitySubtypeTitle = 'entity-view.entity-view-type'; | ||
120 | + this.entitySubtypeRequiredText = 'entity-view.entity-view-type-required'; | ||
121 | + this.broadcastSubscription = this.broadcast.on('entityViewSaved', () => { | ||
122 | + this.subTypes = null; | ||
123 | + this.subTypesOptionsSubject.next(''); | ||
124 | + }); | ||
125 | + break; | ||
126 | + } | ||
127 | + | ||
128 | + this.subTypesOptions = this.subTypesOptionsSubject.asObservable().pipe( | ||
129 | + startWith<string | EntitySubtype>(''), | ||
130 | + mergeMap(() => this.getSubTypes()) | ||
131 | + ); | ||
132 | + | ||
133 | + this.subTypeFormGroup.get('subType').valueChanges.subscribe( | ||
134 | + (value) => { | ||
135 | + let modelValue; | ||
136 | + if (!value || value === '') { | ||
137 | + modelValue = ''; | ||
138 | + } else { | ||
139 | + modelValue = value.type; | ||
140 | + } | ||
141 | + this.updateView(modelValue); | ||
142 | + } | ||
143 | + ); | ||
144 | + } | ||
145 | + | ||
146 | + ngAfterViewInit(): void { | ||
147 | + } | ||
148 | + | ||
149 | + ngOnDestroy(): void { | ||
150 | + if (this.broadcastSubscription) { | ||
151 | + this.broadcastSubscription.unsubscribe(); | ||
152 | + } | ||
153 | + } | ||
154 | + | ||
155 | + setDisabledState(isDisabled: boolean): void { | ||
156 | + this.disabled = isDisabled; | ||
157 | + } | ||
158 | + | ||
159 | + writeValue(value: string | null): void { | ||
160 | + if (value != null && value !== '') { | ||
161 | + this.modelValue = value; | ||
162 | + this.findSubTypes(value).subscribe( | ||
163 | + (subTypes) => { | ||
164 | + const subType = subTypes && subTypes.length === 1 ? subTypes[0] : ''; | ||
165 | + this.subTypeFormGroup.get('subType').patchValue(subType, {emitEvent: true}); | ||
166 | + } | ||
167 | + ); | ||
168 | + } else { | ||
169 | + this.modelValue = ''; | ||
170 | + this.subTypeFormGroup.get('subType').patchValue('', {emitEvent: true}); | ||
171 | + } | ||
172 | + } | ||
173 | + | ||
174 | + updateView(value: string | null) { | ||
175 | + if (this.modelValue !== value) { | ||
176 | + this.modelValue = value; | ||
177 | + this.propagateChange(this.modelValue); | ||
178 | + } | ||
179 | + } | ||
180 | + | ||
181 | + displaySubTypeFn(subType?: EntitySubtype | string): string | undefined { | ||
182 | + if (subType && typeof subType !== 'string') { | ||
183 | + if (this.typeTranslatePrefix) { | ||
184 | + return this.translate.instant(this.typeTranslatePrefix + '.' + subType.type); | ||
185 | + } else { | ||
186 | + return subType.type; | ||
187 | + } | ||
188 | + } else { | ||
189 | + return this.translate.instant('entity.all-subtypes'); | ||
190 | + } | ||
191 | + } | ||
192 | + | ||
193 | + findSubTypes(searchText?: string): Observable<Array<EntitySubtype | string>> { | ||
194 | + return this.getSubTypes().pipe( | ||
195 | + map(subTypes => subTypes.filter( subType => { | ||
196 | + return searchText ? (typeof subType === 'string' ? false : subType.type === searchText) : false; | ||
197 | + })) | ||
198 | + ); | ||
199 | + } | ||
200 | + | ||
201 | + getSubTypes(): Observable<Array<EntitySubtype | string>> { | ||
202 | + if (!this.subTypes) { | ||
203 | + switch (this.entityType) { | ||
204 | + case EntityType.ASSET: | ||
205 | + // TODO: | ||
206 | + break; | ||
207 | + case EntityType.DEVICE: | ||
208 | + this.subTypes = this.deviceService.getDeviceTypes(false, true); | ||
209 | + break; | ||
210 | + case EntityType.ENTITY_VIEW: | ||
211 | + // TODO: | ||
212 | + break; | ||
213 | + } | ||
214 | + if (this.subTypes) { | ||
215 | + this.subTypes = this.subTypes.pipe( | ||
216 | + map((allSubtypes) => { | ||
217 | + allSubtypes.unshift(''); | ||
218 | + return allSubtypes; | ||
219 | + }), | ||
220 | + publishReplay(1), | ||
221 | + refCount() | ||
222 | + ); | ||
223 | + } else { | ||
224 | + return throwError(null); | ||
225 | + } | ||
226 | + } | ||
227 | + return this.subTypes; | ||
228 | + } | ||
229 | + | ||
230 | + clear() { | ||
231 | + this.subTypeFormGroup.get('subType').patchValue(null, {emitEvent: true}); | ||
232 | + setTimeout(() => { | ||
233 | + this.subTypeInput.nativeElement.blur(); | ||
234 | + this.subTypeInput.nativeElement.focus(); | ||
235 | + }, 0); | ||
236 | + } | ||
237 | + | ||
238 | +} |
@@ -59,7 +59,8 @@ export const HelpLinks = { | @@ -59,7 +59,8 @@ export const HelpLinks = { | ||
59 | securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', | 59 | securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', |
60 | tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', | 60 | tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', |
61 | customers: helpBaseUrl + '/docs/user-guide/customers', | 61 | customers: helpBaseUrl + '/docs/user-guide/customers', |
62 | - users: helpBaseUrl + '/docs/user-guide/ui/users' | 62 | + users: helpBaseUrl + '/docs/user-guide/ui/users', |
63 | + devices: helpBaseUrl + '/docs/user-guide/ui/devices' | ||
63 | } | 64 | } |
64 | }; | 65 | }; |
65 | 66 |
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 {BaseData} from '@shared/models/base-data'; | ||
18 | +import {DeviceId} from './id/device-id'; | ||
19 | +import {TenantId} from '@shared/models/id/tenant-id'; | ||
20 | +import {CustomerId} from '@shared/models/id/customer-id'; | ||
21 | + | ||
22 | +export interface Device extends BaseData<DeviceId> { | ||
23 | + tenantId: TenantId; | ||
24 | + customerId: CustomerId; | ||
25 | + name: string; | ||
26 | + type: string; | ||
27 | + label: string; | ||
28 | + additionalInfo?: any; | ||
29 | +} | ||
30 | + | ||
31 | +export interface DeviceInfo extends Device { | ||
32 | + customerTitle: string; | ||
33 | + customerIsPublic: boolean; | ||
34 | +} |
1 | +import {TenantId} from './id/tenant-id'; | ||
2 | + | ||
1 | /// | 3 | /// |
2 | /// Copyright © 2016-2019 The Thingsboard Authors | 4 | /// Copyright © 2016-2019 The Thingsboard Authors |
3 | /// | 5 | /// |
@@ -88,6 +90,20 @@ export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation> | @@ -88,6 +90,20 @@ export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation> | ||
88 | search: 'user.search', | 90 | search: 'user.search', |
89 | selectedEntities: 'user.selected-users' | 91 | selectedEntities: 'user.selected-users' |
90 | } | 92 | } |
93 | + ], | ||
94 | + [ | ||
95 | + EntityType.DEVICE, | ||
96 | + { | ||
97 | + type: 'entity.type-device', | ||
98 | + typePlural: 'entity.type-devices', | ||
99 | + list: 'entity.list-of-devices', | ||
100 | + nameStartsWith: 'entity.device-name-starts-with', | ||
101 | + details: 'device.device-details', | ||
102 | + add: 'device.add', | ||
103 | + noEntities: 'device.no-devices-text', | ||
104 | + search: 'device.search', | ||
105 | + selectedEntities: 'device.selected-devices' | ||
106 | + } | ||
91 | ] | 107 | ] |
92 | ] | 108 | ] |
93 | ); | 109 | ); |
@@ -111,6 +127,18 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource>( | @@ -111,6 +127,18 @@ export const entityTypeResources = new Map<EntityType, EntityTypeResource>( | ||
111 | { | 127 | { |
112 | helpLinkId: 'users' | 128 | helpLinkId: 'users' |
113 | } | 129 | } |
130 | + ], | ||
131 | + [ | ||
132 | + EntityType.DEVICE, | ||
133 | + { | ||
134 | + helpLinkId: 'devices' | ||
135 | + } | ||
114 | ] | 136 | ] |
115 | ] | 137 | ] |
116 | ); | 138 | ); |
139 | + | ||
140 | +export interface EntitySubtype { | ||
141 | + tenantId: TenantId; | ||
142 | + entityType: EntityType; | ||
143 | + type: string; | ||
144 | +} |
ui-ngx/src/app/shared/models/id/device-id.ts
0 → 100644
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 { EntityId } from './entity-id'; | ||
18 | +import { EntityType } from '@shared/models/entity-type.models'; | ||
19 | + | ||
20 | +export class DeviceId implements EntityId { | ||
21 | + entityType = EntityType.DEVICE; | ||
22 | + id: string; | ||
23 | + constructor(id: string) { | ||
24 | + this.id = id; | ||
25 | + } | ||
26 | +} |
@@ -78,6 +78,8 @@ import { ClipboardModule } from 'ngx-clipboard'; | @@ -78,6 +78,8 @@ import { ClipboardModule } from 'ngx-clipboard'; | ||
78 | import { FullscreenDirective } from '@shared/components/fullscreen.directive'; | 78 | import { FullscreenDirective } from '@shared/components/fullscreen.directive'; |
79 | import { HighlightPipe } from '@shared/pipe/highlight.pipe'; | 79 | import { HighlightPipe } from '@shared/pipe/highlight.pipe'; |
80 | import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; | 80 | import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autocomplete.component'; |
81 | +import {EntitySubTypeAutocompleteComponent} from '@shared/components/entity/entity-subtype-autocomplete.component'; | ||
82 | +import {EntitySubTypeSelectComponent} from './components/entity/entity-subtype-select.component'; | ||
81 | 83 | ||
82 | @NgModule({ | 84 | @NgModule({ |
83 | providers: [ | 85 | providers: [ |
@@ -118,6 +120,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc | @@ -118,6 +120,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc | ||
118 | DatetimePeriodComponent, | 120 | DatetimePeriodComponent, |
119 | // ValueInputComponent, | 121 | // ValueInputComponent, |
120 | DashboardAutocompleteComponent, | 122 | DashboardAutocompleteComponent, |
123 | + EntitySubTypeAutocompleteComponent, | ||
124 | + EntitySubTypeSelectComponent, | ||
121 | NospacePipe, | 125 | NospacePipe, |
122 | MillisecondsToTimeStringPipe, | 126 | MillisecondsToTimeStringPipe, |
123 | EnumToArrayPipe, | 127 | EnumToArrayPipe, |
@@ -183,6 +187,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc | @@ -183,6 +187,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc | ||
183 | TimeintervalComponent, | 187 | TimeintervalComponent, |
184 | DatetimePeriodComponent, | 188 | DatetimePeriodComponent, |
185 | DashboardAutocompleteComponent, | 189 | DashboardAutocompleteComponent, |
190 | + EntitySubTypeAutocompleteComponent, | ||
191 | + EntitySubTypeSelectComponent, | ||
186 | // ValueInputComponent, | 192 | // ValueInputComponent, |
187 | MatButtonModule, | 193 | MatButtonModule, |
188 | MatCheckboxModule, | 194 | MatCheckboxModule, |
@@ -673,6 +673,7 @@ | @@ -673,6 +673,7 @@ | ||
673 | "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", | 673 | "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", |
674 | "device-type-list-empty": "No device types selected.", | 674 | "device-type-list-empty": "No device types selected.", |
675 | "device-types": "Device types", | 675 | "device-types": "Device types", |
676 | + "created-time": "Created time", | ||
676 | "name": "Name", | 677 | "name": "Name", |
677 | "name-required": "Name is required.", | 678 | "name-required": "Name is required.", |
678 | "description": "Description", | 679 | "description": "Description", |
@@ -691,7 +692,9 @@ | @@ -691,7 +692,9 @@ | ||
691 | "device-public": "Device is public", | 692 | "device-public": "Device is public", |
692 | "select-device": "Select device", | 693 | "select-device": "Select device", |
693 | "import": "Import device", | 694 | "import": "Import device", |
694 | - "device-file": "Device file" | 695 | + "device-file": "Device file", |
696 | + "search": "Search devices", | ||
697 | + "selected-devices": "{ count, plural, 1 {1 device} other {# devices} } selected" | ||
695 | }, | 698 | }, |
696 | "dialog": { | 699 | "dialog": { |
697 | "close": "Close dialog" | 700 | "close": "Close dialog" |
@@ -209,6 +209,13 @@ label { | @@ -209,6 +209,13 @@ label { | ||
209 | } | 209 | } |
210 | } | 210 | } |
211 | 211 | ||
212 | +div { | ||
213 | + &.tb-small { | ||
214 | + font-size: 14px; | ||
215 | + color: rgba(0, 0, 0, .54); | ||
216 | + } | ||
217 | +} | ||
218 | + | ||
212 | pre.tb-highlight { | 219 | pre.tb-highlight { |
213 | display: block; | 220 | display: block; |
214 | padding: 15px; | 221 | padding: 15px; |
@@ -253,6 +253,12 @@ $tb-dark-theme: get-tb-dark-theme( | @@ -253,6 +253,12 @@ $tb-dark-theme: get-tb-dark-theme( | ||
253 | } | 253 | } |
254 | } | 254 | } |
255 | 255 | ||
256 | + .mat-cell { | ||
257 | + mat-icon { | ||
258 | + color: rgba(0, 0, 0, .54); | ||
259 | + } | ||
260 | + } | ||
261 | + | ||
256 | mat-toolbar.mat-primary { | 262 | mat-toolbar.mat-primary { |
257 | button.mat-icon-button { | 263 | button.mat-icon-button { |
258 | mat-icon { | 264 | mat-icon { |