Showing
43 changed files
with
1968 additions
and
143 deletions
... | ... | @@ -28,16 +28,7 @@ import org.springframework.security.core.Authentication; |
28 | 28 | import org.springframework.security.core.context.SecurityContextHolder; |
29 | 29 | import org.springframework.web.bind.annotation.ExceptionHandler; |
30 | 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 | 32 | import org.thingsboard.server.common.data.alarm.Alarm; |
42 | 33 | import org.thingsboard.server.common.data.alarm.AlarmId; |
43 | 34 | import org.thingsboard.server.common.data.alarm.AlarmInfo; |
... | ... | @@ -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 | 387 | protected EntityView checkEntityViewId(EntityViewId entityViewId, Operation operation) throws ThingsboardException { |
385 | 388 | try { |
386 | 389 | validateId(entityViewId, "Incorrect entityViewId " + entityViewId); | ... | ... |
... | ... | @@ -30,11 +30,7 @@ import org.springframework.web.bind.annotation.ResponseBody; |
30 | 30 | import org.springframework.web.bind.annotation.ResponseStatus; |
31 | 31 | import org.springframework.web.bind.annotation.RestController; |
32 | 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 | 34 | import org.thingsboard.server.common.data.audit.ActionType; |
39 | 35 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
40 | 36 | import org.thingsboard.server.common.data.exception.ThingsboardException; |
... | ... | @@ -80,6 +76,19 @@ public class DeviceController extends BaseController { |
80 | 76 | } |
81 | 77 | |
82 | 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 | 92 | @RequestMapping(value = "/device", method = RequestMethod.POST) |
84 | 93 | @ResponseBody |
85 | 94 | public Device saveDevice(@RequestBody Device device) throws ThingsboardException { |
... | ... | @@ -288,6 +297,29 @@ public class DeviceController extends BaseController { |
288 | 297 | } |
289 | 298 | |
290 | 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 | 323 | @RequestMapping(value = "/tenant/devices", params = {"deviceName"}, method = RequestMethod.GET) |
292 | 324 | @ResponseBody |
293 | 325 | public Device getTenantDevice( |
... | ... | @@ -328,6 +360,33 @@ public class DeviceController extends BaseController { |
328 | 360 | } |
329 | 361 | |
330 | 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 | 390 | @RequestMapping(value = "/devices", params = {"deviceIds"}, method = RequestMethod.GET) |
332 | 391 | @ResponseBody |
333 | 392 | public List<Device> getDevicesByIds( | ... | ... |
... | ... | @@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device; |
17 | 17 | |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import org.thingsboard.server.common.data.Device; |
20 | +import org.thingsboard.server.common.data.DeviceInfo; | |
20 | 21 | import org.thingsboard.server.common.data.EntitySubtype; |
21 | 22 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
22 | 23 | import org.thingsboard.server.common.data.id.CustomerId; |
... | ... | @@ -28,7 +29,9 @@ import org.thingsboard.server.common.data.page.PageLink; |
28 | 29 | import java.util.List; |
29 | 30 | |
30 | 31 | public interface DeviceService { |
31 | - | |
32 | + | |
33 | + DeviceInfo findDeviceInfoById(TenantId tenantId, DeviceId deviceId); | |
34 | + | |
32 | 35 | Device findDeviceById(TenantId tenantId, DeviceId deviceId); |
33 | 36 | |
34 | 37 | ListenableFuture<Device> findDeviceByIdAsync(TenantId tenantId, DeviceId deviceId); |
... | ... | @@ -45,16 +48,24 @@ public interface DeviceService { |
45 | 48 | |
46 | 49 | PageData<Device> findDevicesByTenantId(TenantId tenantId, PageLink pageLink); |
47 | 50 | |
51 | + PageData<DeviceInfo> findDeviceInfosByTenantId(TenantId tenantId, PageLink pageLink); | |
52 | + | |
48 | 53 | PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); |
49 | 54 | |
55 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink); | |
56 | + | |
50 | 57 | ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds); |
51 | 58 | |
52 | 59 | void deleteDevicesByTenantId(TenantId tenantId); |
53 | 60 | |
54 | 61 | PageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); |
55 | 62 | |
63 | + PageData<DeviceInfo> findDeviceInfosByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, PageLink pageLink); | |
64 | + | |
56 | 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 | 69 | ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds); |
59 | 70 | |
60 | 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 | 17 | |
18 | 18 | import com.google.common.util.concurrent.ListenableFuture; |
19 | 19 | import org.thingsboard.server.common.data.Device; |
20 | +import org.thingsboard.server.common.data.DeviceInfo; | |
20 | 21 | import org.thingsboard.server.common.data.EntitySubtype; |
21 | 22 | import org.thingsboard.server.common.data.id.TenantId; |
22 | 23 | import org.thingsboard.server.common.data.page.PageData; |
... | ... | @@ -34,6 +35,15 @@ import java.util.UUID; |
34 | 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 | 47 | * Save or update device object |
38 | 48 | * |
39 | 49 | * @param device the device object |
... | ... | @@ -51,6 +61,15 @@ public interface DeviceDao extends Dao<Device> { |
51 | 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 | 73 | * Find devices by tenantId, type and page link. |
55 | 74 | * |
56 | 75 | * @param tenantId the tenantId |
... | ... | @@ -61,6 +80,16 @@ public interface DeviceDao extends Dao<Device> { |
61 | 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 | 93 | * Find devices by tenantId and devices Ids. |
65 | 94 | * |
66 | 95 | * @param tenantId the tenantId |
... | ... | @@ -80,6 +109,16 @@ public interface DeviceDao extends Dao<Device> { |
80 | 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 | 122 | * Find devices by tenantId, customerId, type and page link. |
84 | 123 | * |
85 | 124 | * @param tenantId the tenantId |
... | ... | @@ -90,6 +129,17 @@ public interface DeviceDao extends Dao<Device> { |
90 | 129 | */ |
91 | 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 | 145 | * Find devices by tenantId, customerId and devices Ids. | ... | ... |
... | ... | @@ -27,12 +27,7 @@ import org.springframework.cache.annotation.CacheEvict; |
27 | 27 | import org.springframework.cache.annotation.Cacheable; |
28 | 28 | import org.springframework.stereotype.Service; |
29 | 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 | 31 | import org.thingsboard.server.common.data.device.DeviceSearchQuery; |
37 | 32 | import org.thingsboard.server.common.data.id.CustomerId; |
38 | 33 | import org.thingsboard.server.common.data.id.DeviceId; |
... | ... | @@ -96,6 +91,13 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
96 | 91 | private CacheManager cacheManager; |
97 | 92 | |
98 | 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 | 101 | public Device findDeviceById(TenantId tenantId, DeviceId deviceId) { |
100 | 102 | log.trace("Executing findDeviceById [{}]", deviceId); |
101 | 103 | validateId(deviceId, INCORRECT_DEVICE_ID + deviceId); |
... | ... | @@ -188,6 +190,14 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
188 | 190 | } |
189 | 191 | |
190 | 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 | 201 | public PageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, PageLink pageLink) { |
192 | 202 | log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink); |
193 | 203 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
... | ... | @@ -197,6 +207,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
197 | 207 | } |
198 | 208 | |
199 | 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 | 219 | public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds) { |
201 | 220 | log.trace("Executing findDevicesByTenantIdAndIdsAsync, tenantId [{}], deviceIds [{}]", tenantId, deviceIds); |
202 | 221 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
... | ... | @@ -222,6 +241,15 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
222 | 241 | } |
223 | 242 | |
224 | 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 | 253 | public PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, PageLink pageLink) { |
226 | 254 | log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink); |
227 | 255 | validateId(tenantId, INCORRECT_TENANT_ID + tenantId); |
... | ... | @@ -232,6 +260,16 @@ public class DeviceServiceImpl extends AbstractEntityService implements DeviceSe |
232 | 260 | } |
233 | 261 | |
234 | 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 | 273 | public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds) { |
236 | 274 | log.trace("Executing findDevicesByTenantIdCustomerIdAndIdsAsync, tenantId [{}], customerId [{}], deviceIds [{}]", tenantId, customerId, deviceIds); |
237 | 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 | 39 | @Entity |
40 | 40 | @TypeDef(name = "json", typeClass = JsonStringType.class) |
41 | 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 | 44 | public DeviceEntity() { |
67 | 45 | super(); |
68 | 46 | } |
69 | 47 | |
70 | 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 | 52 | @Override |
97 | 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 | -} | |
\ No newline at end of file | ||
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 | 22 | import org.springframework.data.repository.PagingAndSortingRepository; |
23 | 23 | import org.springframework.data.repository.query.Param; |
24 | 24 | import org.thingsboard.server.dao.model.sql.DeviceEntity; |
25 | +import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; | |
25 | 26 | import org.thingsboard.server.dao.util.SqlDao; |
26 | 27 | |
27 | 28 | import java.util.List; |
... | ... | @@ -32,6 +33,11 @@ import java.util.List; |
32 | 33 | @SqlDao |
33 | 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 | 42 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
37 | 43 | "AND d.customerId = :customerId " + |
... | ... | @@ -41,12 +47,32 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit |
41 | 47 | @Param("searchText") String searchText, |
42 | 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 | 61 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
45 | 62 | "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") |
46 | 63 | Page<DeviceEntity> findByTenantId(@Param("tenantId") String tenantId, |
47 | 64 | @Param("textSearch") String textSearch, |
48 | 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 | 76 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
51 | 77 | "AND d.type = :type " + |
52 | 78 | "AND LOWER(d.searchText) LIKE LOWER(CONCAT(:textSearch, '%'))") |
... | ... | @@ -55,6 +81,17 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit |
55 | 81 | @Param("textSearch") String textSearch, |
56 | 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 | 95 | @Query("SELECT d FROM DeviceEntity d WHERE d.tenantId = :tenantId " + |
59 | 96 | "AND d.customerId = :customerId " + |
60 | 97 | "AND d.type = :type " + |
... | ... | @@ -65,6 +102,19 @@ public interface DeviceRepository extends PagingAndSortingRepository<DeviceEntit |
65 | 102 | @Param("textSearch") String textSearch, |
66 | 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 | 118 | @Query("SELECT DISTINCT d.type FROM DeviceEntity d WHERE d.tenantId = :tenantId") |
69 | 119 | List<String> findTenantDeviceTypes(@Param("tenantId") String tenantId); |
70 | 120 | ... | ... |
... | ... | @@ -20,16 +20,14 @@ import org.springframework.beans.factory.annotation.Autowired; |
20 | 20 | import org.springframework.data.domain.PageRequest; |
21 | 21 | import org.springframework.data.repository.CrudRepository; |
22 | 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 | 24 | import org.thingsboard.server.common.data.id.TenantId; |
28 | 25 | import org.thingsboard.server.common.data.page.PageData; |
29 | 26 | import org.thingsboard.server.common.data.page.PageLink; |
30 | 27 | import org.thingsboard.server.dao.DaoUtil; |
31 | 28 | import org.thingsboard.server.dao.device.DeviceDao; |
32 | 29 | import org.thingsboard.server.dao.model.sql.DeviceEntity; |
30 | +import org.thingsboard.server.dao.model.sql.DeviceInfoEntity; | |
33 | 31 | import org.thingsboard.server.dao.sql.JpaAbstractSearchTextDao; |
34 | 32 | import org.thingsboard.server.dao.util.SqlDao; |
35 | 33 | |
... | ... | @@ -65,6 +63,11 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> |
65 | 63 | } |
66 | 64 | |
67 | 65 | @Override |
66 | + public DeviceInfo findDeviceInfoById(TenantId tenantId, UUID deviceId) { | |
67 | + return DaoUtil.getData(deviceRepository.findDeviceInfoById(fromTimeUUID(deviceId))); | |
68 | + } | |
69 | + | |
70 | + @Override | |
68 | 71 | public PageData<Device> findDevicesByTenantId(UUID tenantId, PageLink pageLink) { |
69 | 72 | return DaoUtil.toPageData( |
70 | 73 | deviceRepository.findByTenantId( |
... | ... | @@ -74,6 +77,15 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> |
74 | 77 | } |
75 | 78 | |
76 | 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 | 89 | public ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> deviceIds) { |
78 | 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 | 101 | } |
90 | 102 | |
91 | 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 | 114 | public ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds) { |
93 | 115 | return service.submit(() -> DaoUtil.convertDataList( |
94 | 116 | deviceRepository.findDevicesByTenantIdAndCustomerIdAndIdIn(fromTimeUUID(tenantId), fromTimeUUID(customerId), fromTimeUUIDs(deviceIds)))); |
... | ... | @@ -111,6 +133,16 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> |
111 | 133 | } |
112 | 134 | |
113 | 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 | 146 | public PageData<Device> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, PageLink pageLink) { |
115 | 147 | return DaoUtil.toPageData( |
116 | 148 | deviceRepository.findByTenantIdAndCustomerIdAndType( |
... | ... | @@ -122,6 +154,17 @@ public class JpaDeviceDao extends JpaAbstractSearchTextDao<DeviceEntity, Device> |
122 | 154 | } |
123 | 155 | |
124 | 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 | 168 | public ListenableFuture<List<EntitySubtype>> findTenantDeviceTypesAsync(UUID tenantId) { |
126 | 169 | return service.submit(() -> convertTenantDeviceTypesToDto(tenantId, deviceRepository.findTenantDeviceTypes(fromTimeUUID(tenantId)))); |
127 | 170 | } | ... | ... |
... | ... | @@ -24,7 +24,7 @@ import java.util.Arrays; |
24 | 24 | |
25 | 25 | @RunWith(ClasspathSuite.class) |
26 | 26 | @ClassnameFilters({ |
27 | - "org.thingsboard.server.dao.service.*ServiceSqlTest" | |
27 | + "org.thingsboard.server.dao.service.*DeviceServiceSqlTest" | |
28 | 28 | }) |
29 | 29 | public class SqlDaoServiceTestSuite { |
30 | 30 | ... | ... |
... | ... | @@ -21,10 +21,7 @@ import org.junit.After; |
21 | 21 | import org.junit.Assert; |
22 | 22 | import org.junit.Before; |
23 | 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 | 25 | import org.thingsboard.server.common.data.id.CustomerId; |
29 | 26 | import org.thingsboard.server.common.data.id.TenantId; |
30 | 27 | import org.thingsboard.server.common.data.page.PageData; |
... | ... | @@ -264,7 +261,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
264 | 261 | @Test |
265 | 262 | public void testFindDevicesByTenantIdAndName() { |
266 | 263 | String title1 = "Device title 1"; |
267 | - List<Device> devicesTitle1 = new ArrayList<>(); | |
264 | + List<DeviceInfo> devicesTitle1 = new ArrayList<>(); | |
268 | 265 | for (int i=0;i<143;i++) { |
269 | 266 | Device device = new Device(); |
270 | 267 | device.setTenantId(tenantId); |
... | ... | @@ -273,10 +270,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
273 | 270 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
274 | 271 | device.setName(name); |
275 | 272 | device.setType("default"); |
276 | - devicesTitle1.add(deviceService.saveDevice(device)); | |
273 | + devicesTitle1.add(new DeviceInfo(deviceService.saveDevice(device), null, false)); | |
277 | 274 | } |
278 | 275 | String title2 = "Device title 2"; |
279 | - List<Device> devicesTitle2 = new ArrayList<>(); | |
276 | + List<DeviceInfo> devicesTitle2 = new ArrayList<>(); | |
280 | 277 | for (int i=0;i<175;i++) { |
281 | 278 | Device device = new Device(); |
282 | 279 | device.setTenantId(tenantId); |
... | ... | @@ -285,14 +282,14 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
285 | 282 | name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase(); |
286 | 283 | device.setName(name); |
287 | 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 | 289 | PageLink pageLink = new PageLink(15, 0, title1); |
293 | - PageData<Device> pageData = null; | |
290 | + PageData<DeviceInfo> pageData = null; | |
294 | 291 | do { |
295 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | |
292 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); | |
296 | 293 | loadedDevicesTitle1.addAll(pageData.getData()); |
297 | 294 | if (pageData.hasNext()) { |
298 | 295 | pageLink = pageLink.nextPageLink(); |
... | ... | @@ -304,10 +301,10 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
304 | 301 | |
305 | 302 | Assert.assertEquals(devicesTitle1, loadedDevicesTitle1); |
306 | 303 | |
307 | - List<Device> loadedDevicesTitle2 = new ArrayList<>(); | |
304 | + List<DeviceInfo> loadedDevicesTitle2 = new ArrayList<>(); | |
308 | 305 | pageLink = new PageLink(4, 0, title2); |
309 | 306 | do { |
310 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | |
307 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); | |
311 | 308 | loadedDevicesTitle2.addAll(pageData.getData()); |
312 | 309 | if (pageData.hasNext()) { |
313 | 310 | pageLink = pageLink.nextPageLink(); |
... | ... | @@ -324,7 +321,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
324 | 321 | } |
325 | 322 | |
326 | 323 | pageLink = new PageLink(4, 0, title1); |
327 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | |
324 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); | |
328 | 325 | Assert.assertFalse(pageData.hasNext()); |
329 | 326 | Assert.assertEquals(0, pageData.getData().size()); |
330 | 327 | |
... | ... | @@ -333,7 +330,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
333 | 330 | } |
334 | 331 | |
335 | 332 | pageLink = new PageLink(4, 0, title2); |
336 | - pageData = deviceService.findDevicesByTenantId(tenantId, pageLink); | |
333 | + pageData = deviceService.findDeviceInfosByTenantId(tenantId, pageLink); | |
337 | 334 | Assert.assertFalse(pageData.hasNext()); |
338 | 335 | Assert.assertEquals(0, pageData.getData().size()); |
339 | 336 | } |
... | ... | @@ -431,21 +428,21 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
431 | 428 | customer = customerService.saveCustomer(customer); |
432 | 429 | CustomerId customerId = customer.getId(); |
433 | 430 | |
434 | - List<Device> devices = new ArrayList<>(); | |
431 | + List<DeviceInfo> devices = new ArrayList<>(); | |
435 | 432 | for (int i=0;i<278;i++) { |
436 | 433 | Device device = new Device(); |
437 | 434 | device.setTenantId(tenantId); |
438 | 435 | device.setName("Device"+i); |
439 | 436 | device.setType("default"); |
440 | 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 | 442 | PageLink pageLink = new PageLink(23); |
446 | - PageData<Device> pageData = null; | |
443 | + PageData<DeviceInfo> pageData = null; | |
447 | 444 | do { |
448 | - pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); | |
445 | + pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); | |
449 | 446 | loadedDevices.addAll(pageData.getData()); |
450 | 447 | if (pageData.hasNext()) { |
451 | 448 | pageLink = pageLink.nextPageLink(); |
... | ... | @@ -460,7 +457,7 @@ public abstract class BaseDeviceServiceTest extends AbstractServiceTest { |
460 | 457 | deviceService.unassignCustomerDevices(tenantId, customerId); |
461 | 458 | |
462 | 459 | pageLink = new PageLink(33); |
463 | - pageData = deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink); | |
460 | + pageData = deviceService.findDeviceInfosByTenantIdAndCustomerId(tenantId, customerId, pageLink); | |
464 | 461 | Assert.assertFalse(pageData.hasNext()); |
465 | 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 | 23 | // import { CustomerModule } from '@modules/home/pages/customer/customer.module'; |
24 | 24 | // import { AuditLogModule } from '@modules/home/pages/audit-log/audit-log.module'; |
25 | 25 | import { UserModule } from '@modules/home/pages/user/user.module'; |
26 | +import {DeviceModule} from '@modules/home/pages/device/device.module'; | |
26 | 27 | |
27 | 28 | @NgModule({ |
28 | 29 | exports: [ |
... | ... | @@ -30,6 +31,7 @@ import { UserModule } from '@modules/home/pages/user/user.module'; |
30 | 31 | HomeLinksModule, |
31 | 32 | ProfileModule, |
32 | 33 | TenantModule, |
34 | + DeviceModule, | |
33 | 35 | // CustomerModule, |
34 | 36 | // AuditLogModule, |
35 | 37 | UserModule | ... | ... |
... | ... | @@ -74,8 +74,6 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI |
74 | 74 | |
75 | 75 | filteredDashboards: Observable<Array<DashboardInfo>>; |
76 | 76 | |
77 | - private valueLoaded = false; | |
78 | - | |
79 | 77 | private searchText = ''; |
80 | 78 | |
81 | 79 | private propagateChange = (v: any) => { }; |
... | ... | @@ -97,7 +95,21 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI |
97 | 95 | } |
98 | 96 | |
99 | 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 | 115 | ngAfterViewInit(): void { |
... | ... | @@ -123,48 +135,23 @@ export class DashboardAutocompleteComponent implements ControlValueAccessor, OnI |
123 | 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 | 138 | writeValue(value: DashboardInfo | string | null): void { |
147 | - this.valueLoaded = false; | |
148 | 139 | this.searchText = ''; |
149 | - this.initFilteredResults(); | |
150 | 140 | if (value != null) { |
151 | 141 | if (typeof value === 'string') { |
152 | 142 | this.dashboardService.getDashboardInfo(value).subscribe( |
153 | 143 | (dashboard) => { |
154 | 144 | this.modelValue = this.useIdValue ? dashboard.id.id : dashboard; |
155 | 145 | this.selectDashboardFormGroup.get('dashboard').patchValue(dashboard, {emitEvent: true}); |
156 | - this.valueLoaded = true; | |
157 | 146 | } |
158 | 147 | ); |
159 | 148 | } else { |
160 | 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 | 152 | } else { |
165 | 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 | 76 | public title: string, |
77 | 77 | public maxWidth: string = '100%', |
78 | 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 | 136 | entitiesFetchFunction: EntitiesFetchFunction<T, P> = () => of(emptyPageData<T>()); |
136 | 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 | 120 | </mat-cell> |
121 | 121 | </ng-container> |
122 | 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 | 124 | <mat-cell *matCellDef="let entity" [ngStyle]="cellStyle(entity, column)" [innerHTML]="cellContent(entity, column)"></mat-cell> |
125 | 125 | </ng-container> |
126 | 126 | <ng-container matColumnDef="actions" stickyEnd> | ... | ... |
... | ... | @@ -82,7 +82,7 @@ export class EntitiesTableComponent extends PageComponent implements AfterViewIn |
82 | 82 | |
83 | 83 | isDetailsOpen = false; |
84 | 84 | |
85 | - @ViewChild('entityTableHeader', {static: false}) entityTableHeaderAnchor: TbAnchorComponent; | |
85 | + @ViewChild('entityTableHeader', {static: true}) entityTableHeaderAnchor: TbAnchorComponent; | |
86 | 86 | |
87 | 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 | 59 | securitySettings: helpBaseUrl + '/docs/user-guide/ui/security-settings', |
60 | 60 | tenants: helpBaseUrl + '/docs/user-guide/ui/tenants', |
61 | 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 | 4 | /// Copyright © 2016-2019 The Thingsboard Authors |
3 | 5 | /// |
... | ... | @@ -88,6 +90,20 @@ export const entityTypeTranslations = new Map<EntityType, EntityTypeTranslation> |
88 | 90 | search: 'user.search', |
89 | 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 | 127 | { |
112 | 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 | 78 | import { FullscreenDirective } from '@shared/components/fullscreen.directive'; |
79 | 79 | import { HighlightPipe } from '@shared/pipe/highlight.pipe'; |
80 | 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 | 84 | @NgModule({ |
83 | 85 | providers: [ |
... | ... | @@ -118,6 +120,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc |
118 | 120 | DatetimePeriodComponent, |
119 | 121 | // ValueInputComponent, |
120 | 122 | DashboardAutocompleteComponent, |
123 | + EntitySubTypeAutocompleteComponent, | |
124 | + EntitySubTypeSelectComponent, | |
121 | 125 | NospacePipe, |
122 | 126 | MillisecondsToTimeStringPipe, |
123 | 127 | EnumToArrayPipe, |
... | ... | @@ -183,6 +187,8 @@ import {DashboardAutocompleteComponent} from '@shared/components/dashboard-autoc |
183 | 187 | TimeintervalComponent, |
184 | 188 | DatetimePeriodComponent, |
185 | 189 | DashboardAutocompleteComponent, |
190 | + EntitySubTypeAutocompleteComponent, | |
191 | + EntitySubTypeSelectComponent, | |
186 | 192 | // ValueInputComponent, |
187 | 193 | MatButtonModule, |
188 | 194 | MatCheckboxModule, | ... | ... |
... | ... | @@ -673,6 +673,7 @@ |
673 | 673 | "no-device-types-matching": "No device types matching '{{entitySubtype}}' were found.", |
674 | 674 | "device-type-list-empty": "No device types selected.", |
675 | 675 | "device-types": "Device types", |
676 | + "created-time": "Created time", | |
676 | 677 | "name": "Name", |
677 | 678 | "name-required": "Name is required.", |
678 | 679 | "description": "Description", |
... | ... | @@ -691,7 +692,9 @@ |
691 | 692 | "device-public": "Device is public", |
692 | 693 | "select-device": "Select device", |
693 | 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 | 699 | "dialog": { |
697 | 700 | "close": "Close dialog" | ... | ... |