thingsboard-memoizeit
Changes
dao/src/main/resources/demo-data.cql 21(+14 -7)
dao/src/main/resources/schema.cql 135(+88 -47)
ui/src/app/api/asset.service.js 24(+21 -3)
ui/src/app/api/device.service.js 24(+21 -3)
ui/src/app/api/entity.service.js 29(+28 -1)
ui/src/app/app.config.js 4(+0 -4)
ui/src/app/asset/asset.controller.js 12(+7 -5)
ui/src/app/asset/asset.directive.js 1(+1 -0)
ui/src/app/asset/asset.routes.js 6(+5 -1)
ui/src/app/asset/asset-card.tpl.html 7(+5 -2)
ui/src/app/asset/asset-fieldset.tpl.html 14(+7 -7)
ui/src/app/components/grid.directive.js 28(+15 -13)
ui/src/app/components/grid.tpl.html 9(+5 -4)
ui/src/app/dashboard/dashboard.controller.js 17(+15 -2)
ui/src/app/dashboard/dashboard.scss 80(+8 -72)
ui/src/app/dashboard/dashboard.tpl.html 190(+89 -101)
ui/src/app/dashboard/dashboard-toolbar.scss 114(+114 -0)
ui/src/app/dashboard/index.js 2(+2 -0)
ui/src/app/device/device.controller.js 12(+7 -5)
ui/src/app/device/device.routes.js 6(+5 -1)
ui/src/app/entity/entity-subtype-select.directive.js 138(+138 -0)
ui/src/app/entity/entity-subtype-select.tpl.html 27(+11 -16)
ui/src/app/entity/index.js 4(+4 -0)
ui/src/app/layout/home.controller.js 37(+34 -3)
ui/src/app/layout/home.scss 8(+8 -0)
ui/src/app/layout/home.tpl.html 18(+13 -5)
ui/src/app/locale/locale.constant.js 8(+8 -0)
ui/src/scss/constants.scss 2(+2 -0)
Details
diff --git a/application/src/main/java/org/thingsboard/server/controller/AssetController.java b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
index 24497d4..dd43e1c 100644
--- a/application/src/main/java/org/thingsboard/server/controller/AssetController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/AssetController.java
@@ -21,6 +21,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -136,13 +137,18 @@ public class AssetController extends BaseController {
@ResponseBody
public TextPageData<Asset> getTenantAssets(
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
- return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(assetService.findAssetsByTenantIdAndType(tenantId, type, pageLink));
+ } else {
+ return checkNotNull(assetService.findAssetsByTenantId(tenantId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -167,6 +173,7 @@ public class AssetController extends BaseController {
public TextPageData<Asset> getCustomerAssets(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
@@ -176,7 +183,11 @@ public class AssetController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
- return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
+ } else {
+ return checkNotNull(assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -231,4 +242,18 @@ public class AssetController extends BaseController {
throw handleException(e);
}
}
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/asset/types", method = RequestMethod.GET)
+ @ResponseBody
+ public List<TenantAssetType> getAssetTypes() throws ThingsboardException {
+ try {
+ SecurityUser user = getCurrentUser();
+ TenantId tenantId = user.getTenantId();
+ ListenableFuture<List<TenantAssetType>> assetTypes = assetService.findAssetTypesByTenantId(tenantId);
+ return checkNotNull(assetTypes.get());
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
}
diff --git a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
index 7cd381c..8257767 100644
--- a/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
+++ b/application/src/main/java/org/thingsboard/server/controller/DeviceController.java
@@ -21,6 +21,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -166,13 +167,18 @@ public class DeviceController extends BaseController {
@ResponseBody
public TextPageData<Device> getTenantDevices(
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
try {
TenantId tenantId = getCurrentUser().getTenantId();
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
- return checkNotNull(deviceService.findDevicesByTenantId(tenantId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(deviceService.findDevicesByTenantIdAndType(tenantId, type, pageLink));
+ } else {
+ return checkNotNull(deviceService.findDevicesByTenantId(tenantId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -197,6 +203,7 @@ public class DeviceController extends BaseController {
public TextPageData<Device> getCustomerDevices(
@PathVariable("customerId") String strCustomerId,
@RequestParam int limit,
+ @RequestParam(required = false) String type,
@RequestParam(required = false) String textSearch,
@RequestParam(required = false) String idOffset,
@RequestParam(required = false) String textOffset) throws ThingsboardException {
@@ -206,7 +213,11 @@ public class DeviceController extends BaseController {
CustomerId customerId = new CustomerId(toUUID(strCustomerId));
checkCustomerId(customerId);
TextPageLink pageLink = createPageLink(limit, textSearch, idOffset, textOffset);
- return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ if (type != null && type.trim().length()>0) {
+ return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type, pageLink));
+ } else {
+ return checkNotNull(deviceService.findDevicesByTenantIdAndCustomerId(tenantId, customerId, pageLink));
+ }
} catch (Exception e) {
throw handleException(e);
}
@@ -261,4 +272,19 @@ public class DeviceController extends BaseController {
throw handleException(e);
}
}
+
+ @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
+ @RequestMapping(value = "/device/types", method = RequestMethod.GET)
+ @ResponseBody
+ public List<TenantDeviceType> getDeviceTypes() throws ThingsboardException {
+ try {
+ SecurityUser user = getCurrentUser();
+ TenantId tenantId = user.getTenantId();
+ ListenableFuture<List<TenantDeviceType>> deviceTypes = deviceService.findDeviceTypesByTenantId(tenantId);
+ return checkNotNull(deviceTypes.get());
+ } catch (Exception e) {
+ throw handleException(e);
+ }
+ }
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
index b9647e5..435f9a6 100644
--- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java
@@ -98,13 +98,15 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.webAppC
@IntegrationTest("server.port:0")
public abstract class AbstractControllerTest {
+ protected static final String TEST_TENANT_NAME = "TEST TENANT";
+
protected static final String SYS_ADMIN_EMAIL = "sysadmin@thingsboard.org";
private static final String SYS_ADMIN_PASSWORD = "sysadmin";
- protected static final String TENANT_ADMIN_EMAIL = "tenant@thingsboard.org";
+ protected static final String TENANT_ADMIN_EMAIL = "testtenant@thingsboard.org";
private static final String TENANT_ADMIN_PASSWORD = "tenant";
- protected static final String CUSTOMER_USER_EMAIL = "customer@thingsboard.org";
+ protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org";
private static final String CUSTOMER_USER_PASSWORD = "customer";
protected MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(),
@@ -147,7 +149,7 @@ public abstract class AbstractControllerTest {
loginSysAdmin();
Tenant tenant = new Tenant();
- tenant.setTitle("Tenant");
+ tenant.setTitle(TEST_TENANT_NAME);
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
tenantId = savedTenant.getId();
diff --git a/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
new file mode 100644
index 0000000..10a2ff7
--- /dev/null
+++ b/application/src/test/java/org/thingsboard/server/controller/AssetControllerTest.java
@@ -0,0 +1,659 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.controller;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import org.apache.commons.lang3.RandomStringUtils;
+import org.thingsboard.server.common.data.*;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.AssetId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.common.data.security.Authority;
+import org.thingsboard.server.dao.model.ModelConstants;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.datastax.driver.core.utils.UUIDs;
+import com.fasterxml.jackson.core.type.TypeReference;
+
+public class AssetControllerTest extends AbstractControllerTest {
+
+ private IdComparator<Asset> idComparator = new IdComparator<>();
+
+ private Tenant savedTenant;
+ private User tenantAdmin;
+
+ @Before
+ public void beforeTest() throws Exception {
+ loginSysAdmin();
+
+ Tenant tenant = new Tenant();
+ tenant.setTitle("My tenant");
+ savedTenant = doPost("/api/tenant", tenant, Tenant.class);
+ Assert.assertNotNull(savedTenant);
+
+ tenantAdmin = new User();
+ tenantAdmin.setAuthority(Authority.TENANT_ADMIN);
+ tenantAdmin.setTenantId(savedTenant.getId());
+ tenantAdmin.setEmail("tenant2@thingsboard.org");
+ tenantAdmin.setFirstName("Joe");
+ tenantAdmin.setLastName("Downs");
+
+ tenantAdmin = createUserAndLogin(tenantAdmin, "testPassword1");
+ }
+
+ @After
+ public void afterTest() throws Exception {
+ loginSysAdmin();
+
+ doDelete("/api/tenant/"+savedTenant.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void testSaveAsset() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ Assert.assertNotNull(savedAsset);
+ Assert.assertNotNull(savedAsset.getId());
+ Assert.assertTrue(savedAsset.getCreatedTime() > 0);
+ Assert.assertEquals(savedTenant.getId(), savedAsset.getTenantId());
+ Assert.assertNotNull(savedAsset.getCustomerId());
+ Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId());
+ Assert.assertEquals(asset.getName(), savedAsset.getName());
+
+ savedAsset.setName("My new asset");
+ doPost("/api/asset", savedAsset, Asset.class);
+
+ Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
+ }
+
+ @Test
+ public void testFindAssetById() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+ Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertNotNull(foundAsset);
+ Assert.assertEquals(savedAsset, foundAsset);
+ }
+
+ @Test
+ public void testFindAssetTypesByTenantId() throws Exception {
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<3;i++) {
+ Asset asset = new Asset();
+ asset.setName("My asset B"+i);
+ asset.setType("typeB");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ for (int i=0;i<7;i++) {
+ Asset asset = new Asset();
+ asset.setName("My asset C"+i);
+ asset.setType("typeC");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ for (int i=0;i<9;i++) {
+ Asset asset = new Asset();
+ asset.setName("My asset A"+i);
+ asset.setType("typeA");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ List<TenantAssetType> assetTypes = doGetTyped("/api/asset/types",
+ new TypeReference<List<TenantAssetType>>(){});
+
+ Assert.assertNotNull(assetTypes);
+ Assert.assertEquals(3, assetTypes.size());
+ Assert.assertEquals("typeA", assetTypes.get(0).getType());
+ Assert.assertEquals("typeB", assetTypes.get(1).getType());
+ Assert.assertEquals("typeC", assetTypes.get(2).getType());
+ }
+
+ @Test
+ public void testDeleteAsset() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ doDelete("/api/asset/"+savedAsset.getId().getId().toString())
+ .andExpect(status().isOk());
+
+ doGet("/api/asset/"+savedAsset.getId().getId().toString())
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void testSaveAssetWithEmptyType() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ doPost("/api/asset", asset)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Asset type should be specified")));
+ }
+
+ @Test
+ public void testSaveAssetWithEmptyName() throws Exception {
+ Asset asset = new Asset();
+ asset.setType("default");
+ doPost("/api/asset", asset)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Asset name should be specified")));
+ }
+
+ @Test
+ public void testAssignUnassignAssetToCustomer() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ Customer customer = new Customer();
+ customer.setTitle("My customer");
+ Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
+
+ Asset assignedAsset = doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ + "/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(savedCustomer.getId(), assignedAsset.getCustomerId());
+
+ Asset foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(savedCustomer.getId(), foundAsset.getCustomerId());
+
+ Asset unassignedAsset =
+ doDelete("/api/customer/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(ModelConstants.NULL_UUID, unassignedAsset.getCustomerId().getId());
+
+ foundAsset = doGet("/api/asset/" + savedAsset.getId().getId().toString(), Asset.class);
+ Assert.assertEquals(ModelConstants.NULL_UUID, foundAsset.getCustomerId().getId());
+ }
+
+ @Test
+ public void testAssignAssetToNonExistentCustomer() throws Exception {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ doPost("/api/customer/" + UUIDs.timeBased().toString()
+ + "/asset/" + savedAsset.getId().getId().toString())
+ .andExpect(status().isNotFound());
+ }
+
+ @Test
+ public void testAssignAssetToCustomerFromDifferentTenant() throws Exception {
+ loginSysAdmin();
+
+ Tenant tenant2 = new Tenant();
+ tenant2.setTitle("Different tenant");
+ Tenant savedTenant2 = doPost("/api/tenant", tenant2, Tenant.class);
+ Assert.assertNotNull(savedTenant2);
+
+ User tenantAdmin2 = new User();
+ tenantAdmin2.setAuthority(Authority.TENANT_ADMIN);
+ tenantAdmin2.setTenantId(savedTenant2.getId());
+ tenantAdmin2.setEmail("tenant3@thingsboard.org");
+ tenantAdmin2.setFirstName("Joe");
+ tenantAdmin2.setLastName("Downs");
+
+ tenantAdmin2 = createUserAndLogin(tenantAdmin2, "testPassword1");
+
+ Customer customer = new Customer();
+ customer.setTitle("Different customer");
+ Customer savedCustomer = doPost("/api/customer", customer, Customer.class);
+
+ login(tenantAdmin.getEmail(), "testPassword1");
+
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = doPost("/api/asset", asset, Asset.class);
+
+ doPost("/api/customer/" + savedCustomer.getId().getId().toString()
+ + "/asset/" + savedAsset.getId().getId().toString())
+ .andExpect(status().isForbidden());
+
+ loginSysAdmin();
+
+ doDelete("/api/tenant/"+savedTenant2.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ @Test
+ public void testFindTenantAssets() throws Exception {
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<178;i++) {
+ Asset asset = new Asset();
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ assets.add(doPost("/api/asset", asset, Asset.class));
+ }
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+ }
+
+ @Test
+ public void testFindTenantAssetsByName() throws Exception {
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle1.add(doPost("/api/asset", asset, Asset.class));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<75;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle2.add(doPost("/api/asset", asset, Asset.class));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindTenantAssetsByType() throws Exception {
+ String title1 = "Asset title 1";
+ String type1 = "typeA";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ assetsType1.add(doPost("/api/asset", asset, Asset.class));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeB";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<75;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ assetsType2.add(doPost("/api/asset", asset, Asset.class));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ doDelete("/api/asset/"+asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindCustomerAssets() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<128;i++) {
+ Asset asset = new Asset();
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ asset = doPost("/api/asset", asset, Asset.class);
+ assets.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+ }
+
+ @Test
+ public void testFindCustomerAssetsByName() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<125;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindCustomerAssetsByType() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ String type1 = "typeC";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<125;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsType1.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeD";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ asset = doPost("/api/asset", asset, Asset.class);
+ assetsType2.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/asset/" + asset.getId().getId().toString(), Asset.class));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ doDelete("/api/customer/asset/" + asset.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/assets?type={type}&",
+ new TypeReference<TextPageData<Asset>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+}
diff --git a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
index 69bb761..5d40a79 100644
--- a/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/DeviceControllerTest.java
@@ -24,10 +24,7 @@ import java.util.Collections;
import java.util.List;
import org.apache.commons.lang3.RandomStringUtils;
-import org.thingsboard.server.common.data.Customer;
-import org.thingsboard.server.common.data.Device;
-import org.thingsboard.server.common.data.Tenant;
-import org.thingsboard.server.common.data.User;
+import org.thingsboard.server.common.data.*;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceCredentialsId;
import org.thingsboard.server.common.data.id.DeviceId;
@@ -83,6 +80,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDevice() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Assert.assertNotNull(savedDevice);
@@ -114,16 +112,49 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testFindDeviceById() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Device foundDevice = doGet("/api/device/" + savedDevice.getId().getId().toString(), Device.class);
Assert.assertNotNull(foundDevice);
Assert.assertEquals(savedDevice, foundDevice);
}
+
+ @Test
+ public void testFindDeviceTypesByTenantId() throws Exception {
+ List<Device> devices = new ArrayList<>();
+ for (int i=0;i<3;i++) {
+ Device device = new Device();
+ device.setName("My device B"+i);
+ device.setType("typeB");
+ devices.add(doPost("/api/device", device, Device.class));
+ }
+ for (int i=0;i<7;i++) {
+ Device device = new Device();
+ device.setName("My device C"+i);
+ device.setType("typeC");
+ devices.add(doPost("/api/device", device, Device.class));
+ }
+ for (int i=0;i<9;i++) {
+ Device device = new Device();
+ device.setName("My device A"+i);
+ device.setType("typeA");
+ devices.add(doPost("/api/device", device, Device.class));
+ }
+ List<TenantDeviceType> deviceTypes = doGetTyped("/api/device/types",
+ new TypeReference<List<TenantDeviceType>>(){});
+
+ Assert.assertNotNull(deviceTypes);
+ Assert.assertEquals(3, deviceTypes.size());
+ Assert.assertEquals("typeA", deviceTypes.get(0).getType());
+ Assert.assertEquals("typeB", deviceTypes.get(1).getType());
+ Assert.assertEquals("typeC", deviceTypes.get(2).getType());
+ }
@Test
public void testDeleteDevice() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doDelete("/api/device/"+savedDevice.getId().getId().toString())
@@ -132,10 +163,20 @@ public class DeviceControllerTest extends AbstractControllerTest {
doGet("/api/device/"+savedDevice.getId().getId().toString())
.andExpect(status().isNotFound());
}
-
+
+ @Test
+ public void testSaveDeviceWithEmptyType() throws Exception {
+ Device device = new Device();
+ device.setName("My device");
+ doPost("/api/device", device)
+ .andExpect(status().isBadRequest())
+ .andExpect(statusReason(containsString("Device type should be specified")));
+ }
+
@Test
public void testSaveDeviceWithEmptyName() throws Exception {
Device device = new Device();
+ device.setType("default");
doPost("/api/device", device)
.andExpect(status().isBadRequest())
.andExpect(statusReason(containsString("Device name should be specified")));
@@ -145,6 +186,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testAssignUnassignDeviceToCustomer() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
Customer customer = new Customer();
@@ -170,6 +212,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testAssignDeviceToNonExistentCustomer() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + UUIDs.timeBased().toString()
@@ -203,6 +246,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
doPost("/api/customer/" + savedCustomer.getId().getId().toString()
@@ -219,6 +263,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testFindDeviceCredentialsByDeviceId() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -229,6 +274,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentials() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -255,6 +301,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsType() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -268,6 +315,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsId() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -281,6 +329,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveNonExistentDeviceCredentials() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -298,6 +347,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
public void testSaveDeviceCredentialsWithNonExistentDevice() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -307,9 +357,10 @@ public class DeviceControllerTest extends AbstractControllerTest {
}
@Test
- public void testSaveDeviceCredentialsWithInvalidCredemtialsIdLength() throws Exception {
+ public void testSaveDeviceCredentialsWithInvalidCredentialsIdLength() throws Exception {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
Device savedDevice = doPost("/api/device", device, Device.class);
DeviceCredentials deviceCredentials =
doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class);
@@ -325,6 +376,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
for (int i=0;i<178;i++) {
Device device = new Device();
device.setName("Device"+i);
+ device.setType("default");
devices.add(doPost("/api/device", device, Device.class));
}
List<Device> loadedDevices = new ArrayList<>();
@@ -355,6 +407,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle1.add(doPost("/api/device", device, Device.class));
}
String title2 = "Device title 2";
@@ -365,6 +418,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle2.add(doPost("/api/device", device, Device.class));
}
@@ -423,6 +477,89 @@ public class DeviceControllerTest extends AbstractControllerTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
+
+ @Test
+ public void testFindTenantDevicesByType() throws Exception {
+ String title1 = "Device title 1";
+ String type1 = "typeA";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ devicesType1.add(doPost("/api/device", device, Device.class));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeB";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<75;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ devicesType2.add(doPost("/api/device", device, Device.class));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ doDelete("/api/device/"+device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ doDelete("/api/device/"+device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/tenant/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
@Test
public void testFindCustomerDevices() throws Exception {
@@ -435,6 +572,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
for (int i=0;i<128;i++) {
Device device = new Device();
device.setName("Device"+i);
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
devices.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class));
@@ -473,6 +611,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
devicesTitle1.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class));
@@ -485,6 +624,7 @@ public class DeviceControllerTest extends AbstractControllerTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
devicesTitle2.add(doPost("/api/customer/" + customerId.getId().toString()
+ "/device/" + device.getId().getId().toString(), Device.class));
@@ -546,4 +686,96 @@ public class DeviceControllerTest extends AbstractControllerTest {
Assert.assertEquals(0, pageData.getData().size());
}
+ @Test
+ public void testFindCustomerDevicesByType() throws Exception {
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer = doPost("/api/customer", customer, Customer.class);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Device title 1";
+ String type1 = "typeC";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<125;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ device = doPost("/api/device", device, Device.class);
+ devicesType1.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/device/" + device.getId().getId().toString(), Device.class));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeD";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ device = doPost("/api/device", device, Device.class);
+ devicesType2.add(doPost("/api/customer/" + customerId.getId().toString()
+ + "/device/" + device.getId().getId().toString(), Device.class));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ doDelete("/api/customer/device/" + device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type1);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ doDelete("/api/customer/device/" + device.getId().getId().toString())
+ .andExpect(status().isOk());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = doGetTypedWithPageLink("/api/customer/" + customerId.getId().toString() + "/devices?type={type}&",
+ new TypeReference<TextPageData<Device>>(){}, pageLink, type2);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
index 5d72076..dc4a422 100644
--- a/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/TenantControllerTest.java
@@ -130,7 +130,7 @@ public class TenantControllerTest extends AbstractControllerTest {
Assert.assertEquals(tenants, loadedTenants);
for (Tenant tenant : loadedTenants) {
- if (!tenant.getTitle().equals("Tenant")) {
+ if (!tenant.getTitle().equals(TEST_TENANT_NAME)) {
doDelete("/api/tenant/"+tenant.getId().getId().toString())
.andExpect(status().isOk());
}
diff --git a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
index 7c00049..4fafd61 100644
--- a/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
+++ b/application/src/test/java/org/thingsboard/server/controller/UserControllerTest.java
@@ -182,7 +182,7 @@ public class UserControllerTest extends AbstractControllerTest {
Tenant savedTenant = doPost("/api/tenant", tenant, Tenant.class);
Assert.assertNotNull(savedTenant);
- String email = "tenant@thingsboard.org";
+ String email = TENANT_ADMIN_EMAIL;
User user = new User();
user.setAuthority(Authority.TENANT_ADMIN);
user.setTenantId(savedTenant.getId());
diff --git a/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java b/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java
index b65a5a6..af8b50f 100644
--- a/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java
+++ b/application/src/test/java/org/thingsboard/server/system/HttpDeviceApiTest.java
@@ -47,6 +47,7 @@ public class HttpDeviceApiTest extends AbstractControllerTest {
loginTenantAdmin();
device = new Device();
device.setName("My device");
+ device.setType("default");
device = doPost("/api/device", device, Device.class);
deviceCredentials =
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/asset/TenantAssetType.java b/common/data/src/main/java/org/thingsboard/server/common/data/asset/TenantAssetType.java
new file mode 100644
index 0000000..bd23f2a
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/asset/TenantAssetType.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data.asset;
+
+import org.thingsboard.server.common.data.id.TenantId;
+
+public class TenantAssetType {
+
+ private static final long serialVersionUID = 8057290243855622101L;
+
+ private String type;
+ private TenantId tenantId;
+
+ public TenantAssetType() {
+ super();
+ }
+
+ public TenantAssetType(String type, TenantId tenantId) {
+ this.type = type;
+ this.tenantId = tenantId;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public TenantId getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(TenantId tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantAssetType that = (TenantAssetType) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantAssetType{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/common/data/src/main/java/org/thingsboard/server/common/data/TenantDeviceType.java b/common/data/src/main/java/org/thingsboard/server/common/data/TenantDeviceType.java
new file mode 100644
index 0000000..d611a25
--- /dev/null
+++ b/common/data/src/main/java/org/thingsboard/server/common/data/TenantDeviceType.java
@@ -0,0 +1,79 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.common.data;
+
+import org.thingsboard.server.common.data.id.TenantId;
+
+public class TenantDeviceType {
+
+ private static final long serialVersionUID = 8057240243859922101L;
+
+ private String type;
+ private TenantId tenantId;
+
+ public TenantDeviceType() {
+ super();
+ }
+
+ public TenantDeviceType(String type, TenantId tenantId) {
+ this.type = type;
+ this.tenantId = tenantId;
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public TenantId getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(TenantId tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantDeviceType that = (TenantDeviceType) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantDeviceType{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
index 81f95b4..c2ffd1f 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDao.java
@@ -16,12 +16,11 @@
package org.thingsboard.server.dao.asset;
import com.google.common.util.concurrent.ListenableFuture;
-import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.model.AssetEntity;
-import org.thingsboard.server.dao.model.DeviceEntity;
+import org.thingsboard.server.dao.model.TenantAssetTypeEntity;
import java.util.List;
import java.util.Optional;
@@ -51,6 +50,16 @@ public interface AssetDao extends Dao<AssetEntity> {
List<AssetEntity> findAssetsByTenantId(UUID tenantId, TextPageLink pageLink);
/**
+ * Find assets by tenantId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of asset objects
+ */
+ List<AssetEntity> findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink);
+
+ /**
* Find assets by tenantId and assets Ids.
*
* @param tenantId the tenantId
@@ -70,6 +79,17 @@ public interface AssetDao extends Dao<AssetEntity> {
List<AssetEntity> findAssetsByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
/**
+ * Find assets by tenantId, customerId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param customerId the customerId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of asset objects
+ */
+ List<AssetEntity> findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink);
+
+ /**
* Find assets by tenantId, customerId and assets Ids.
*
* @param tenantId the tenantId
@@ -87,4 +107,12 @@ public interface AssetDao extends Dao<AssetEntity> {
* @return the optional asset object
*/
Optional<AssetEntity> findAssetsByTenantIdAndName(UUID tenantId, String name);
+
+ /**
+ * Find tenants asset types.
+ *
+ * @return the list of tenant asset type objects
+ */
+ ListenableFuture<List<TenantAssetTypeEntity>> findTenantAssetTypesAsync();
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDaoImpl.java
index ed08c6d..5dba18b 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDaoImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetDaoImpl.java
@@ -15,7 +15,12 @@
*/
package org.thingsboard.server.dao.asset;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.mapping.Result;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -23,7 +28,9 @@ import org.thingsboard.server.common.data.asset.Asset;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.AbstractSearchTextDao;
import org.thingsboard.server.dao.model.AssetEntity;
+import org.thingsboard.server.dao.model.TenantAssetTypeEntity;
+import javax.annotation.Nullable;
import java.util.*;
import static com.datastax.driver.core.querybuilder.QueryBuilder.*;
@@ -60,6 +67,16 @@ public class AssetDaoImpl extends AbstractSearchTextDao<AssetEntity> implements
}
@Override
+ public List<AssetEntity> findAssetsByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+ log.debug("Try to find assets by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
+ List<AssetEntity> assetEntities = findPageWithTextSearch(ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(ASSET_TYPE_PROPERTY, type),
+ eq(ASSET_TENANT_ID_PROPERTY, tenantId)), pageLink);
+ log.trace("Found assets [{}] by tenantId [{}], type [{}] and pageLink [{}]", assetEntities, tenantId, type, pageLink);
+ return assetEntities;
+ }
+
+ @Override
public ListenableFuture<List<AssetEntity>> findAssetsByTenantIdAndIdsAsync(UUID tenantId, List<UUID> assetIds) {
log.debug("Try to find assets by tenantId [{}] and asset Ids [{}]", tenantId, assetIds);
Select select = select().from(getColumnFamilyName());
@@ -82,6 +99,19 @@ public class AssetDaoImpl extends AbstractSearchTextDao<AssetEntity> implements
}
@Override
+ public List<AssetEntity> findAssetsByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+ log.debug("Try to find assets by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink);
+ List<AssetEntity> assetEntities = findPageWithTextSearch(ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(ASSET_TYPE_PROPERTY, type),
+ eq(ASSET_CUSTOMER_ID_PROPERTY, customerId),
+ eq(ASSET_TENANT_ID_PROPERTY, tenantId)),
+ pageLink);
+
+ log.trace("Found assets [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", assetEntities, tenantId, customerId, type, pageLink);
+ return assetEntities;
+ }
+
+ @Override
public ListenableFuture<List<AssetEntity>> findAssetsByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> assetIds) {
log.debug("Try to find assets by tenantId [{}], customerId [{}] and asset Ids [{}]", tenantId, customerId, assetIds);
Select select = select().from(getColumnFamilyName());
@@ -101,4 +131,24 @@ public class AssetDaoImpl extends AbstractSearchTextDao<AssetEntity> implements
return Optional.ofNullable(findOneByStatement(query));
}
+ @Override
+ public ListenableFuture<List<TenantAssetTypeEntity>> findTenantAssetTypesAsync() {
+ Select statement = select().distinct().column(ASSET_TYPE_PROPERTY).column(ASSET_TENANT_ID_PROPERTY).from(ASSET_TYPES_BY_TENANT_VIEW_NAME);
+ statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
+ ResultSetFuture resultSetFuture = getSession().executeAsync(statement);
+ ListenableFuture<List<TenantAssetTypeEntity>> result = Futures.transform(resultSetFuture, new Function<ResultSet, List<TenantAssetTypeEntity>>() {
+ @Nullable
+ @Override
+ public List<TenantAssetTypeEntity> apply(@Nullable ResultSet resultSet) {
+ Result<TenantAssetTypeEntity> result = cluster.getMapper(TenantAssetTypeEntity.class).map(resultSet);
+ if (result != null) {
+ return result.all();
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ });
+ return result;
+ }
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
index 7a61bd8..25baeda 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/AssetService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.asset;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -34,7 +35,7 @@ public interface AssetService {
Optional<Asset> findAssetByTenantIdAndName(TenantId tenantId, String name);
- Asset saveAsset(Asset device);
+ Asset saveAsset(Asset asset);
Asset assignAssetToCustomer(AssetId assetId, CustomerId customerId);
@@ -44,16 +45,21 @@ public interface AssetService {
TextPageData<Asset> findAssetsByTenantId(TenantId tenantId, TextPageLink pageLink);
+ TextPageData<Asset> findAssetsByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Asset>> findAssetsByTenantIdAndIdsAsync(TenantId tenantId, List<AssetId> assetIds);
void deleteAssetsByTenantId(TenantId tenantId);
TextPageData<Asset> findAssetsByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+ TextPageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Asset>> findAssetsByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<AssetId> assetIds);
void unassignCustomerAssets(TenantId tenantId, CustomerId customerId);
ListenableFuture<List<Asset>> findAssetsByQuery(AssetSearchQuery query);
+ ListenableFuture<List<TenantAssetType>> findAssetTypesByTenantId(TenantId tenantId);
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
index c63e5d7..fce0c5e 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/asset/BaseAssetService.java
@@ -26,6 +26,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.EntityType;
import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
import org.thingsboard.server.common.data.id.AssetId;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.EntityId;
@@ -36,10 +37,7 @@ import org.thingsboard.server.common.data.relation.EntityRelation;
import org.thingsboard.server.dao.customer.CustomerDao;
import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
-import org.thingsboard.server.dao.model.AssetEntity;
-import org.thingsboard.server.dao.model.CustomerEntity;
-import org.thingsboard.server.dao.model.TenantEntity;
-import org.thingsboard.server.dao.relation.EntityRelationsQuery;
+import org.thingsboard.server.dao.model.*;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
import org.thingsboard.server.dao.service.DataValidator;
import org.thingsboard.server.dao.service.PaginatedRemover;
@@ -132,7 +130,18 @@ public class BaseAssetService extends BaseEntityService implements AssetService
validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<AssetEntity> assetEntities = assetDao.findAssetsByTenantId(tenantId.getId(), pageLink);
List<Asset> assets = convertDataList(assetEntities);
- return new TextPageData<Asset>(assets, pageLink);
+ return new TextPageData<>(assets, pageLink);
+ }
+
+ @Override
+ public TextPageData<Asset> findAssetsByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink) {
+ log.trace("Executing findAssetsByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<AssetEntity> assetEntities = assetDao.findAssetsByTenantIdAndType(tenantId.getId(), type, pageLink);
+ List<Asset> assets = convertDataList(assetEntities);
+ return new TextPageData<>(assets, pageLink);
}
@Override
@@ -159,7 +168,19 @@ public class BaseAssetService extends BaseEntityService implements AssetService
validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<AssetEntity> assetEntities = assetDao.findAssetsByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
List<Asset> assets = convertDataList(assetEntities);
- return new TextPageData<Asset>(assets, pageLink);
+ return new TextPageData<>(assets, pageLink);
+ }
+
+ @Override
+ public TextPageData<Asset> findAssetsByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink) {
+ log.trace("Executing findAssetsByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateId(customerId, "Incorrect customerId " + customerId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<AssetEntity> assetEntities = assetDao.findAssetsByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink);
+ List<Asset> assets = convertDataList(assetEntities);
+ return new TextPageData<>(assets, pageLink);
}
@Override
@@ -207,6 +228,25 @@ public class BaseAssetService extends BaseEntityService implements AssetService
return assets;
}
+ @Override
+ public ListenableFuture<List<TenantAssetType>> findAssetTypesByTenantId(TenantId tenantId) {
+ log.trace("Executing findAssetTypesByTenantId, tenantId [{}]", tenantId);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ ListenableFuture<List<TenantAssetTypeEntity>> tenantAssetTypeEntities = assetDao.findTenantAssetTypesAsync();
+ ListenableFuture<List<TenantAssetType>> tenantAssetTypes = Futures.transform(tenantAssetTypeEntities,
+ (Function<List<TenantAssetTypeEntity>, List<TenantAssetType>>) assetTypeEntities -> {
+ List<TenantAssetType> assetTypes = new ArrayList<>();
+ for (TenantAssetTypeEntity assetTypeEntity : assetTypeEntities) {
+ if (assetTypeEntity.getTenantId().equals(tenantId.getId())) {
+ assetTypes.add(assetTypeEntity.toTenantAssetType());
+ }
+ }
+ assetTypes.sort((TenantAssetType o1, TenantAssetType o2) -> o1.getType().compareTo(o2.getType()));
+ return assetTypes;
+ });
+ return tenantAssetTypes;
+ }
+
private DataValidator<Asset> assetValidator =
new DataValidator<Asset>() {
@@ -232,6 +272,9 @@ public class BaseAssetService extends BaseEntityService implements AssetService
@Override
protected void validateDataImpl(Asset asset) {
+ if (StringUtils.isEmpty(asset.getType())) {
+ throw new DataValidationException("Asset type should be specified!");
+ }
if (StringUtils.isEmpty(asset.getName())) {
throw new DataValidationException("Asset name should be specified!");
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
index b8d395c..241759c 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDao.java
@@ -24,6 +24,7 @@ import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.page.TextPageLink;
import org.thingsboard.server.dao.Dao;
import org.thingsboard.server.dao.model.DeviceEntity;
+import org.thingsboard.server.dao.model.TenantDeviceTypeEntity;
/**
* The Interface DeviceDao.
@@ -49,6 +50,16 @@ public interface DeviceDao extends Dao<DeviceEntity> {
List<DeviceEntity> findDevicesByTenantId(UUID tenantId, TextPageLink pageLink);
/**
+ * Find devices by tenantId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of device objects
+ */
+ List<DeviceEntity> findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink);
+
+ /**
* Find devices by tenantId and devices Ids.
*
* @param tenantId the tenantId
@@ -68,6 +79,18 @@ public interface DeviceDao extends Dao<DeviceEntity> {
List<DeviceEntity> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink);
/**
+ * Find devices by tenantId, customerId, type and page link.
+ *
+ * @param tenantId the tenantId
+ * @param customerId the customerId
+ * @param type the type
+ * @param pageLink the page link
+ * @return the list of device objects
+ */
+ List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink);
+
+
+ /**
* Find devices by tenantId, customerId and devices Ids.
*
* @param tenantId the tenantId
@@ -85,4 +108,11 @@ public interface DeviceDao extends Dao<DeviceEntity> {
* @return the optional device object
*/
Optional<DeviceEntity> findDevicesByTenantIdAndName(UUID tenantId, String name);
+
+ /**
+ * Find tenants device types.
+ *
+ * @return the list of tenant device type objects
+ */
+ ListenableFuture<List<TenantDeviceTypeEntity>> findTenantDeviceTypesAsync();
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDaoImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDaoImpl.java
index 81fc0bc..590f7b7 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDaoImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceDaoImpl.java
@@ -22,7 +22,12 @@ import static org.thingsboard.server.dao.model.ModelConstants.*;
import java.util.*;
+import com.datastax.driver.core.ResultSet;
+import com.datastax.driver.core.ResultSetFuture;
import com.datastax.driver.core.querybuilder.Select;
+import com.datastax.driver.mapping.Result;
+import com.google.common.base.Function;
+import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
@@ -32,6 +37,9 @@ import org.thingsboard.server.dao.AbstractSearchTextDao;
import org.thingsboard.server.dao.model.DeviceEntity;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import org.thingsboard.server.dao.model.TenantDeviceTypeEntity;
+
+import javax.annotation.Nullable;
@Component
@Slf4j
@@ -64,6 +72,16 @@ public class DeviceDaoImpl extends AbstractSearchTextDao<DeviceEntity> implement
}
@Override
+ public List<DeviceEntity> findDevicesByTenantIdAndType(UUID tenantId, String type, TextPageLink pageLink) {
+ log.debug("Try to find devices by tenantId [{}], type [{}] and pageLink [{}]", tenantId, type, pageLink);
+ List<DeviceEntity> deviceEntities = findPageWithTextSearch(DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type),
+ eq(DEVICE_TENANT_ID_PROPERTY, tenantId)), pageLink);
+ log.trace("Found devices [{}] by tenantId [{}], type [{}] and pageLink [{}]", deviceEntities, tenantId, type, pageLink);
+ return deviceEntities;
+ }
+
+ @Override
public ListenableFuture<List<DeviceEntity>> findDevicesByTenantIdAndIdsAsync(UUID tenantId, List<UUID> deviceIds) {
log.debug("Try to find devices by tenantId [{}] and device Ids [{}]", tenantId, deviceIds);
Select select = select().from(getColumnFamilyName());
@@ -75,7 +93,7 @@ public class DeviceDaoImpl extends AbstractSearchTextDao<DeviceEntity> implement
@Override
public List<DeviceEntity> findDevicesByTenantIdAndCustomerId(UUID tenantId, UUID customerId, TextPageLink pageLink) {
- log.debug("Try to find devices by tenantId [{}], customerId[{}] and pageLink [{}]", tenantId, customerId, pageLink);
+ log.debug("Try to find devices by tenantId [{}], customerId [{}] and pageLink [{}]", tenantId, customerId, pageLink);
List<DeviceEntity> deviceEntities = findPageWithTextSearch(DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
Arrays.asList(eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId),
eq(DEVICE_TENANT_ID_PROPERTY, tenantId)),
@@ -86,6 +104,19 @@ public class DeviceDaoImpl extends AbstractSearchTextDao<DeviceEntity> implement
}
@Override
+ public List<DeviceEntity> findDevicesByTenantIdAndCustomerIdAndType(UUID tenantId, UUID customerId, String type, TextPageLink pageLink) {
+ log.debug("Try to find devices by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", tenantId, customerId, type, pageLink);
+ List<DeviceEntity> deviceEntities = findPageWithTextSearch(DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME,
+ Arrays.asList(eq(DEVICE_TYPE_PROPERTY, type),
+ eq(DEVICE_CUSTOMER_ID_PROPERTY, customerId),
+ eq(DEVICE_TENANT_ID_PROPERTY, tenantId)),
+ pageLink);
+
+ log.trace("Found devices [{}] by tenantId [{}], customerId [{}], type [{}] and pageLink [{}]", deviceEntities, tenantId, customerId, type, pageLink);
+ return deviceEntities;
+ }
+
+ @Override
public ListenableFuture<List<DeviceEntity>> findDevicesByTenantIdCustomerIdAndIdsAsync(UUID tenantId, UUID customerId, List<UUID> deviceIds) {
log.debug("Try to find devices by tenantId [{}], customerId [{}] and device Ids [{}]", tenantId, customerId, deviceIds);
Select select = select().from(getColumnFamilyName());
@@ -105,4 +136,24 @@ public class DeviceDaoImpl extends AbstractSearchTextDao<DeviceEntity> implement
return Optional.ofNullable(findOneByStatement(query));
}
+ @Override
+ public ListenableFuture<List<TenantDeviceTypeEntity>> findTenantDeviceTypesAsync() {
+ Select statement = select().distinct().column(DEVICE_TYPE_PROPERTY).column(DEVICE_TENANT_ID_PROPERTY).from(DEVICE_TYPES_BY_TENANT_VIEW_NAME);
+ statement.setConsistencyLevel(cluster.getDefaultReadConsistencyLevel());
+ ResultSetFuture resultSetFuture = getSession().executeAsync(statement);
+ ListenableFuture<List<TenantDeviceTypeEntity>> result = Futures.transform(resultSetFuture, new Function<ResultSet, List<TenantDeviceTypeEntity>>() {
+ @Nullable
+ @Override
+ public List<TenantDeviceTypeEntity> apply(@Nullable ResultSet resultSet) {
+ Result<TenantDeviceTypeEntity> result = cluster.getMapper(TenantDeviceTypeEntity.class).map(resultSet);
+ if (result != null) {
+ return result.all();
+ } else {
+ return Collections.emptyList();
+ }
+ }
+ });
+ return result;
+ }
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
index 4715435..3e7b6af 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceService.java
@@ -17,6 +17,7 @@ package org.thingsboard.server.dao.device;
import com.google.common.util.concurrent.ListenableFuture;
import org.thingsboard.server.common.data.Device;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.TenantId;
@@ -44,16 +45,22 @@ public interface DeviceService {
TextPageData<Device> findDevicesByTenantId(TenantId tenantId, TextPageLink pageLink);
+ TextPageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Device>> findDevicesByTenantIdAndIdsAsync(TenantId tenantId, List<DeviceId> deviceIds);
void deleteDevicesByTenantId(TenantId tenantId);
TextPageData<Device> findDevicesByTenantIdAndCustomerId(TenantId tenantId, CustomerId customerId, TextPageLink pageLink);
+ TextPageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink);
+
ListenableFuture<List<Device>> findDevicesByTenantIdCustomerIdAndIdsAsync(TenantId tenantId, CustomerId customerId, List<DeviceId> deviceIds);
void unassignCustomerDevices(TenantId tenantId, CustomerId customerId);
ListenableFuture<List<Device>> findDevicesByQuery(DeviceSearchQuery query);
+ ListenableFuture<List<TenantDeviceType>> findDeviceTypesByTenantId(TenantId tenantId);
+
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
index ab6fa4e..d01f154 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/device/DeviceServiceImpl.java
@@ -26,6 +26,7 @@ import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.EntityType;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceId;
import org.thingsboard.server.common.data.id.EntityId;
@@ -40,6 +41,7 @@ import org.thingsboard.server.dao.entity.BaseEntityService;
import org.thingsboard.server.dao.exception.DataValidationException;
import org.thingsboard.server.dao.model.CustomerEntity;
import org.thingsboard.server.dao.model.DeviceEntity;
+import org.thingsboard.server.dao.model.TenantDeviceTypeEntity;
import org.thingsboard.server.dao.model.TenantEntity;
import org.thingsboard.server.dao.relation.EntitySearchDirection;
import org.thingsboard.server.dao.service.DataValidator;
@@ -47,9 +49,7 @@ import org.thingsboard.server.dao.service.PaginatedRemover;
import org.thingsboard.server.dao.tenant.TenantDao;
import javax.annotation.Nullable;
-import java.util.ArrayList;
-import java.util.List;
-import java.util.Optional;
+import java.util.*;
import java.util.stream.Collectors;
import static org.thingsboard.server.dao.DaoUtil.*;
@@ -148,7 +148,18 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DeviceEntity> deviceEntities = deviceDao.findDevicesByTenantId(tenantId.getId(), pageLink);
List<Device> devices = convertDataList(deviceEntities);
- return new TextPageData<Device>(devices, pageLink);
+ return new TextPageData<>(devices, pageLink);
+ }
+
+ @Override
+ public TextPageData<Device> findDevicesByTenantIdAndType(TenantId tenantId, String type, TextPageLink pageLink) {
+ log.trace("Executing findDevicesByTenantIdAndType, tenantId [{}], type [{}], pageLink [{}]", tenantId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<DeviceEntity> deviceEntities = deviceDao.findDevicesByTenantIdAndType(tenantId.getId(), type, pageLink);
+ List<Device> devices = convertDataList(deviceEntities);
+ return new TextPageData<>(devices, pageLink);
}
@Override
@@ -176,7 +187,19 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
validatePageLink(pageLink, "Incorrect page link " + pageLink);
List<DeviceEntity> deviceEntities = deviceDao.findDevicesByTenantIdAndCustomerId(tenantId.getId(), customerId.getId(), pageLink);
List<Device> devices = convertDataList(deviceEntities);
- return new TextPageData<Device>(devices, pageLink);
+ return new TextPageData<>(devices, pageLink);
+ }
+
+ @Override
+ public TextPageData<Device> findDevicesByTenantIdAndCustomerIdAndType(TenantId tenantId, CustomerId customerId, String type, TextPageLink pageLink) {
+ log.trace("Executing findDevicesByTenantIdAndCustomerIdAndType, tenantId [{}], customerId [{}], type [{}], pageLink [{}]", tenantId, customerId, type, pageLink);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ validateId(customerId, "Incorrect customerId " + customerId);
+ validateString(type, "Incorrect type " + type);
+ validatePageLink(pageLink, "Incorrect page link " + pageLink);
+ List<DeviceEntity> deviceEntities = deviceDao.findDevicesByTenantIdAndCustomerIdAndType(tenantId.getId(), customerId.getId(), type, pageLink);
+ List<Device> devices = convertDataList(deviceEntities);
+ return new TextPageData<>(devices, pageLink);
}
@Override
@@ -224,6 +247,25 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
return devices;
}
+ @Override
+ public ListenableFuture<List<TenantDeviceType>> findDeviceTypesByTenantId(TenantId tenantId) {
+ log.trace("Executing findDeviceTypesByTenantId, tenantId [{}]", tenantId);
+ validateId(tenantId, "Incorrect tenantId " + tenantId);
+ ListenableFuture<List<TenantDeviceTypeEntity>> tenantDeviceTypeEntities = deviceDao.findTenantDeviceTypesAsync();
+ ListenableFuture<List<TenantDeviceType>> tenantDeviceTypes = Futures.transform(tenantDeviceTypeEntities,
+ (Function<List<TenantDeviceTypeEntity>, List<TenantDeviceType>>) deviceTypeEntities -> {
+ List<TenantDeviceType> deviceTypes = new ArrayList<>();
+ for (TenantDeviceTypeEntity deviceTypeEntity : deviceTypeEntities) {
+ if (deviceTypeEntity.getTenantId().equals(tenantId.getId())) {
+ deviceTypes.add(deviceTypeEntity.toTenantDeviceType());
+ }
+ }
+ deviceTypes.sort((TenantDeviceType o1, TenantDeviceType o2) -> o1.getType().compareTo(o2.getType()));
+ return deviceTypes;
+ });
+ return tenantDeviceTypes;
+ }
+
private DataValidator<Device> deviceValidator =
new DataValidator<Device>() {
@@ -249,6 +291,9 @@ public class DeviceServiceImpl extends BaseEntityService implements DeviceServic
@Override
protected void validateDataImpl(Device device) {
+ if (StringUtils.isEmpty(device.getType())) {
+ throw new DataValidationException("Device type should be specified!");
+ }
if (StringUtils.isEmpty(device.getName())) {
throw new DataValidationException("Device name should be specified!");
}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/AssetEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/AssetEntity.java
index 0444d11..0d477c0 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/AssetEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/AssetEntity.java
@@ -49,12 +49,13 @@ public final class AssetEntity implements SearchTextEntity<Asset> {
@Column(name = ASSET_CUSTOMER_ID_PROPERTY)
private UUID customerId;
- @Column(name = ASSET_NAME_PROPERTY)
- private String name;
-
+ @PartitionKey(value = 3)
@Column(name = ASSET_TYPE_PROPERTY)
private String type;
+ @Column(name = ASSET_NAME_PROPERTY)
+ private String name;
+
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java
index 69ed92c..a7fe243 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/DeviceEntity.java
@@ -49,12 +49,13 @@ public final class DeviceEntity implements SearchTextEntity<Device> {
@Column(name = DEVICE_CUSTOMER_ID_PROPERTY)
private UUID customerId;
- @Column(name = DEVICE_NAME_PROPERTY)
- private String name;
-
+ @PartitionKey(value = 3)
@Column(name = DEVICE_TYPE_PROPERTY)
private String type;
+ @Column(name = DEVICE_NAME_PROPERTY)
+ private String name;
+
@Column(name = SEARCH_TEXT_PROPERTY)
private String searchText;
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
index 2648b49..967f4d1 100644
--- a/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/ModelConstants.java
@@ -124,8 +124,11 @@ public class ModelConstants {
public static final String DEVICE_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String DEVICE_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_and_search_text";
+ public static final String DEVICE_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_tenant_by_type_and_search_text";
public static final String DEVICE_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_and_search_text";
+ public static final String DEVICE_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "device_by_customer_by_type_and_search_text";
public static final String DEVICE_BY_TENANT_AND_NAME_VIEW_NAME = "device_by_tenant_and_name";
+ public static final String DEVICE_TYPES_BY_TENANT_VIEW_NAME = "device_types_by_tenant";
/**
* Cassandra asset constants.
@@ -138,8 +141,11 @@ public class ModelConstants {
public static final String ASSET_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
public static final String ASSET_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_and_search_text";
+ public static final String ASSET_BY_TENANT_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_tenant_by_type_and_search_text";
public static final String ASSET_BY_CUSTOMER_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_and_search_text";
+ public static final String ASSET_BY_CUSTOMER_BY_TYPE_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "asset_by_customer_by_type_and_search_text";
public static final String ASSET_BY_TENANT_AND_NAME_VIEW_NAME = "asset_by_tenant_and_name";
+ public static final String ASSET_TYPES_BY_TENANT_VIEW_NAME = "asset_types_by_tenant";
/**
* Cassandra alarm constants.
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/TenantAssetTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/TenantAssetTypeEntity.java
new file mode 100644
index 0000000..36361ef
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/TenantAssetTypeEntity.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Table(name = ASSET_TYPES_BY_TENANT_VIEW_NAME)
+public class TenantAssetTypeEntity {
+
+ @Transient
+ private static final long serialVersionUID = -1268181161886910152L;
+
+ @PartitionKey(value = 0)
+ @Column(name = ASSET_TYPE_PROPERTY)
+ private String type;
+
+ @PartitionKey(value = 1)
+ @Column(name = ASSET_TENANT_ID_PROPERTY)
+ private UUID tenantId;
+
+ public TenantAssetTypeEntity() {
+ super();
+ }
+
+ public TenantAssetTypeEntity(TenantAssetType tenantAssetType) {
+ this.type = tenantAssetType.getType();
+ if (tenantAssetType.getTenantId() != null) {
+ this.tenantId = tenantAssetType.getTenantId().getId();
+ }
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public UUID getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(UUID tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantAssetTypeEntity that = (TenantAssetTypeEntity) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantAssetTypeEntity{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public TenantAssetType toTenantAssetType() {
+ TenantAssetType tenantAssetType = new TenantAssetType();
+ tenantAssetType.setType(type);
+ if (tenantId != null) {
+ tenantAssetType.setTenantId(new TenantId(tenantId));
+ }
+ return tenantAssetType;
+ }
+}
diff --git a/dao/src/main/java/org/thingsboard/server/dao/model/TenantDeviceTypeEntity.java b/dao/src/main/java/org/thingsboard/server/dao/model/TenantDeviceTypeEntity.java
new file mode 100644
index 0000000..dad954c
--- /dev/null
+++ b/dao/src/main/java/org/thingsboard/server/dao/model/TenantDeviceTypeEntity.java
@@ -0,0 +1,107 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.thingsboard.server.dao.model;
+
+import com.datastax.driver.mapping.annotations.Column;
+import com.datastax.driver.mapping.annotations.PartitionKey;
+import com.datastax.driver.mapping.annotations.Table;
+import com.datastax.driver.mapping.annotations.Transient;
+import org.thingsboard.server.common.data.TenantDeviceType;
+import org.thingsboard.server.common.data.id.TenantId;
+
+import java.util.UUID;
+
+import static org.thingsboard.server.dao.model.ModelConstants.*;
+
+@Table(name = DEVICE_TYPES_BY_TENANT_VIEW_NAME)
+public class TenantDeviceTypeEntity {
+
+ @Transient
+ private static final long serialVersionUID = -1268181166886910152L;
+
+ @PartitionKey(value = 0)
+ @Column(name = DEVICE_TYPE_PROPERTY)
+ private String type;
+
+ @PartitionKey(value = 1)
+ @Column(name = DEVICE_TENANT_ID_PROPERTY)
+ private UUID tenantId;
+
+ public TenantDeviceTypeEntity() {
+ super();
+ }
+
+ public TenantDeviceTypeEntity(TenantDeviceType tenantDeviceType) {
+ this.type = tenantDeviceType.getType();
+ if (tenantDeviceType.getTenantId() != null) {
+ this.tenantId = tenantDeviceType.getTenantId().getId();
+ }
+ }
+
+ public String getType() {
+ return type;
+ }
+
+ public void setType(String type) {
+ this.type = type;
+ }
+
+ public UUID getTenantId() {
+ return tenantId;
+ }
+
+ public void setTenantId(UUID tenantId) {
+ this.tenantId = tenantId;
+ }
+
+ @Override
+ public int hashCode() {
+ int result = type != null ? type.hashCode() : 0;
+ result = 31 * result + (tenantId != null ? tenantId.hashCode() : 0);
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) return true;
+ if (o == null || getClass() != o.getClass()) return false;
+
+ TenantDeviceTypeEntity that = (TenantDeviceTypeEntity) o;
+
+ if (type != null ? !type.equals(that.type) : that.type != null) return false;
+ return tenantId != null ? tenantId.equals(that.tenantId) : that.tenantId == null;
+
+ }
+
+ @Override
+ public String toString() {
+ final StringBuilder sb = new StringBuilder("TenantDeviceTypeEntity{");
+ sb.append("type='").append(type).append('\'');
+ sb.append(", tenantId=").append(tenantId);
+ sb.append('}');
+ return sb.toString();
+ }
+
+ public TenantDeviceType toTenantDeviceType() {
+ TenantDeviceType tenantDeviceType = new TenantDeviceType();
+ tenantDeviceType.setType(type);
+ if (tenantId != null) {
+ tenantDeviceType.setTenantId(new TenantId(tenantId));
+ }
+ return tenantDeviceType;
+ }
+}
dao/src/main/resources/demo-data.cql 21(+14 -7)
diff --git a/dao/src/main/resources/demo-data.cql b/dao/src/main/resources/demo-data.cql
index 023cf1b..47d8de4 100644
--- a/dao/src/main/resources/demo-data.cql
+++ b/dao/src/main/resources/demo-data.cql
@@ -149,66 +149,73 @@ VALUES (
/** Demo device **/
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0000' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+ 'default',
'Test Device A1',
'test device a1'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0001' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+ 'default',
'Test Device A2',
'test device a2'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0002' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0000' ),
+ 'default',
'Test Device A3',
'test device a3'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0003' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0001' ),
+ 'default',
'Test Device B1',
'test device b1'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text)
VALUES (
minTimeuuid ( '2016-11-01 01:02:05+0004' ),
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( '2016-11-01 01:02:03+0002' ),
+ 'default',
'Test Device C1',
'test device c1'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text, additional_info)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text, additional_info)
VALUES (
c8f1a6f0-b993-11e6-8a04-9ff4e1b7933c,
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( 0 ),
+ 'default',
'DHT11 Demo Device',
'dht11 demo device',
'{"description":"Demo device that is used in sample applications that upload data from DHT11 temperature and humidity sensor"}'
);
-INSERT INTO thingsboard.device ( id, tenant_id, customer_id, name, search_text, additional_info)
+INSERT INTO thingsboard.device ( id, tenant_id, customer_id, type, name, search_text, additional_info)
VALUES (
c8f1a6f0-b993-11e6-8a04-9ff4e1b7933d,
minTimeuuid ( '2016-11-01 01:02:01+0000' ),
minTimeuuid ( 0 ),
+ 'default',
'Raspberry Pi Demo Device',
'raspberry pi demo device',
'{"description":"Demo device that is used in Raspberry Pi GPIO control sample application"}'
dao/src/main/resources/schema.cql 135(+88 -47)
diff --git a/dao/src/main/resources/schema.cql b/dao/src/main/resources/schema.cql
index e0ef1e2..b72d2f6 100644
--- a/dao/src/main/resources/schema.cql
+++ b/dao/src/main/resources/schema.cql
@@ -152,36 +152,57 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_search
WITH CLUSTERING ORDER BY ( search_text ASC, id DESC );
CREATE TABLE IF NOT EXISTS thingsboard.device (
- id timeuuid,
- tenant_id timeuuid,
- customer_id timeuuid,
- name text,
- type text,
- search_text text,
- additional_info text,
- PRIMARY KEY (id, tenant_id, customer_id)
+ id timeuuid,
+ tenant_id timeuuid,
+ customer_id timeuuid,
+ name text,
+ type text,
+ search_text text,
+ additional_info text,
+ PRIMARY KEY (id, tenant_id, customer_id, type)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_name AS
- SELECT *
- from thingsboard.device
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, name, id, customer_id)
- WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, name, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_and_search_text AS
- SELECT *
- from thingsboard.device
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, search_text, id, customer_id)
- WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, search_text, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_tenant_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, type, search_text, id, customer_id)
+ WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_and_search_text AS
- SELECT *
- from thingsboard.device
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( customer_id, tenant_id, search_text, id )
- WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, search_text, id, type )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_by_customer_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, type, search_text, id )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_types_by_tenant AS
+ SELECT *
+ from thingsboard.device
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( (type, tenant_id), id, customer_id)
+ WITH CLUSTERING ORDER BY ( id ASC, customer_id DESC);
CREATE TABLE IF NOT EXISTS thingsboard.device_credentials (
id timeuuid PRIMARY KEY,
@@ -203,38 +224,58 @@ CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.device_credentials_by_credent
WHERE credentials_id IS NOT NULL AND id IS NOT NULL
PRIMARY KEY ( credentials_id, id );
-
CREATE TABLE IF NOT EXISTS thingsboard.asset (
- id timeuuid,
- tenant_id timeuuid,
- customer_id timeuuid,
- name text,
- type text,
- search_text text,
- additional_info text,
- PRIMARY KEY (id, tenant_id, customer_id)
+ id timeuuid,
+ tenant_id timeuuid,
+ customer_id timeuuid,
+ name text,
+ type text,
+ search_text text,
+ additional_info text,
+ PRIMARY KEY (id, tenant_id, customer_id, type)
);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_name AS
- SELECT *
- from thingsboard.asset
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, name, id, customer_id)
- WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND name IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, name, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( name ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_and_search_text AS
- SELECT *
- from thingsboard.asset
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( tenant_id, search_text, id, customer_id)
- WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, search_text, id, customer_id, type)
+ WITH CLUSTERING ORDER BY ( search_text ASC, id DESC, customer_id DESC);
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_tenant_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( tenant_id, type, search_text, id, customer_id)
+ WITH CLUSTERING ORDER BY ( type ASC, search_text ASC, id DESC, customer_id DESC);
CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_and_search_text AS
- SELECT *
- from thingsboard.asset
- WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
- PRIMARY KEY ( customer_id, tenant_id, search_text, id )
- WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, search_text, id, type )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_by_customer_by_type_and_search_text AS
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND search_text IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( customer_id, tenant_id, type, search_text, id )
+ WITH CLUSTERING ORDER BY ( tenant_id DESC, type ASC, search_text ASC, id DESC );
+
+CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.asset_types_by_tenant AS
+ SELECT *
+ from thingsboard.asset
+ WHERE tenant_id IS NOT NULL AND customer_id IS NOT NULL AND type IS NOT NULL AND id IS NOT NULL
+ PRIMARY KEY ( (type, tenant_id), id, customer_id)
+ WITH CLUSTERING ORDER BY ( id ASC, customer_id DESC);
CREATE TABLE IF NOT EXISTS thingsboard.alarm (
id timeuuid,
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
index d724d7f..95340e6 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/AbstractServiceTest.java
@@ -32,7 +32,6 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.support.AnnotationConfigContextLoader;
import org.thingsboard.server.common.data.BaseData;
import org.thingsboard.server.common.data.Event;
-import org.thingsboard.server.common.data.alarm.AlarmStatus;
import org.thingsboard.server.common.data.id.EntityId;
import org.thingsboard.server.common.data.id.TenantId;
import org.thingsboard.server.common.data.id.UUIDBased;
@@ -42,6 +41,7 @@ import org.thingsboard.server.common.data.plugin.ComponentType;
import org.thingsboard.server.common.data.plugin.PluginMetaData;
import org.thingsboard.server.common.data.rule.RuleMetaData;
import org.thingsboard.server.dao.alarm.AlarmService;
+import org.thingsboard.server.dao.asset.AssetService;
import org.thingsboard.server.dao.component.ComponentDescriptorService;
import org.thingsboard.server.dao.customer.CustomerService;
import org.thingsboard.server.dao.dashboard.DashboardService;
@@ -90,6 +90,9 @@ public abstract class AbstractServiceTest {
protected DeviceService deviceService;
@Autowired
+ protected AssetService assetService;
+
+ @Autowired
protected DeviceCredentialsService deviceCredentialsService;
@Autowired
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java
new file mode 100644
index 0000000..9587703
--- /dev/null
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/BaseAssetServiceTest.java
@@ -0,0 +1,634 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.thingsboard.server.dao.service;
+
+import com.datastax.driver.core.utils.UUIDs;
+import org.apache.commons.lang3.RandomStringUtils;
+import org.junit.After;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+import org.thingsboard.server.common.data.Customer;
+import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.asset.Asset;
+import org.thingsboard.server.common.data.asset.TenantAssetType;
+import org.thingsboard.server.common.data.id.CustomerId;
+import org.thingsboard.server.common.data.id.TenantId;
+import org.thingsboard.server.common.data.page.TextPageData;
+import org.thingsboard.server.common.data.page.TextPageLink;
+import org.thingsboard.server.dao.exception.DataValidationException;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
+
+public class BaseAssetServiceTest extends AbstractServiceTest {
+
+ private IdComparator<Asset> idComparator = new IdComparator<>();
+
+ private TenantId tenantId;
+
+ @Before
+ public void before() {
+ Tenant tenant = new Tenant();
+ tenant.setTitle("My tenant");
+ Tenant savedTenant = tenantService.saveTenant(tenant);
+ Assert.assertNotNull(savedTenant);
+ tenantId = savedTenant.getId();
+ }
+
+ @After
+ public void after() {
+ tenantService.deleteTenant(tenantId);
+ }
+
+ @Test
+ public void testSaveAsset() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = assetService.saveAsset(asset);
+
+ Assert.assertNotNull(savedAsset);
+ Assert.assertNotNull(savedAsset.getId());
+ Assert.assertTrue(savedAsset.getCreatedTime() > 0);
+ Assert.assertEquals(asset.getTenantId(), savedAsset.getTenantId());
+ Assert.assertNotNull(savedAsset.getCustomerId());
+ Assert.assertEquals(NULL_UUID, savedAsset.getCustomerId().getId());
+ Assert.assertEquals(asset.getName(), savedAsset.getName());
+
+ savedAsset.setName("My new asset");
+
+ assetService.saveAsset(savedAsset);
+ Asset foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertEquals(foundAsset.getName(), savedAsset.getName());
+
+ assetService.deleteAsset(savedAsset.getId());
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveAssetWithEmptyName() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setType("default");
+ assetService.saveAsset(asset);
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveAssetWithEmptyTenant() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ assetService.saveAsset(asset);
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testSaveAssetWithInvalidTenant() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ asset.setTenantId(new TenantId(UUIDs.timeBased()));
+ assetService.saveAsset(asset);
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testAssignAssetToNonExistentCustomer() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ asset.setTenantId(tenantId);
+ asset = assetService.saveAsset(asset);
+ try {
+ assetService.assignAssetToCustomer(asset.getId(), new CustomerId(UUIDs.timeBased()));
+ } finally {
+ assetService.deleteAsset(asset.getId());
+ }
+ }
+
+ @Test(expected = DataValidationException.class)
+ public void testAssignAssetToCustomerFromDifferentTenant() {
+ Asset asset = new Asset();
+ asset.setName("My asset");
+ asset.setType("default");
+ asset.setTenantId(tenantId);
+ asset = assetService.saveAsset(asset);
+ Tenant tenant = new Tenant();
+ tenant.setTitle("Test different tenant");
+ tenant = tenantService.saveTenant(tenant);
+ Customer customer = new Customer();
+ customer.setTenantId(tenant.getId());
+ customer.setTitle("Test different customer");
+ customer = customerService.saveCustomer(customer);
+ try {
+ assetService.assignAssetToCustomer(asset.getId(), customer.getId());
+ } finally {
+ assetService.deleteAsset(asset.getId());
+ tenantService.deleteTenant(tenant.getId());
+ }
+ }
+
+ @Test
+ public void testFindAssetById() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = assetService.saveAsset(asset);
+ Asset foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertNotNull(foundAsset);
+ Assert.assertEquals(savedAsset, foundAsset);
+ assetService.deleteAsset(savedAsset.getId());
+ }
+
+ @Test
+ public void testFindAssetTypesByTenantId() throws Exception {
+ List<Asset> assets = new ArrayList<>();
+ try {
+ for (int i=0;i<3;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset B"+i);
+ asset.setType("typeB");
+ assets.add(assetService.saveAsset(asset));
+ }
+ for (int i=0;i<7;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset C"+i);
+ asset.setType("typeC");
+ assets.add(assetService.saveAsset(asset));
+ }
+ for (int i=0;i<9;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset A"+i);
+ asset.setType("typeA");
+ assets.add(assetService.saveAsset(asset));
+ }
+ List<TenantAssetType> assetTypes = assetService.findAssetTypesByTenantId(tenantId).get();
+ Assert.assertNotNull(assetTypes);
+ Assert.assertEquals(3, assetTypes.size());
+ Assert.assertEquals("typeA", assetTypes.get(0).getType());
+ Assert.assertEquals("typeB", assetTypes.get(1).getType());
+ Assert.assertEquals("typeC", assetTypes.get(2).getType());
+ } finally {
+ assets.forEach((asset) -> { assetService.deleteAsset(asset.getId()); });
+ }
+ }
+
+ @Test
+ public void testDeleteAsset() {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("My asset");
+ asset.setType("default");
+ Asset savedAsset = assetService.saveAsset(asset);
+ Asset foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertNotNull(foundAsset);
+ assetService.deleteAsset(savedAsset.getId());
+ foundAsset = assetService.findAssetById(savedAsset.getId());
+ Assert.assertNull(foundAsset);
+ }
+
+ @Test
+ public void testFindAssetsByTenantId() {
+ Tenant tenant = new Tenant();
+ tenant.setTitle("Test tenant");
+ tenant = tenantService.saveTenant(tenant);
+
+ TenantId tenantId = tenant.getId();
+
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<178;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ assets.add(assetService.saveAsset(asset));
+ }
+
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+
+ assetService.deleteAssetsByTenantId(tenantId);
+
+ pageLink = new TextPageLink(33);
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertTrue(pageData.getData().isEmpty());
+
+ tenantService.deleteTenant(tenantId);
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdAndName() {
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle1.add(assetService.saveAsset(asset));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ assetsTitle2.add(assetService.saveAsset(asset));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = assetService.findAssetsByTenantId(tenantId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdAndType() {
+ String title1 = "Asset title 1";
+ String type1 = "typeA";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ assetsType1.add(assetService.saveAsset(asset));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeB";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ assetsType2.add(assetService.saveAsset(asset));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type1, pageLink);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type2, pageLink);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndType(tenantId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdAndCustomerId() {
+ Tenant tenant = new Tenant();
+ tenant.setTitle("Test tenant");
+ tenant = tenantService.saveTenant(tenant);
+
+ TenantId tenantId = tenant.getId();
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ List<Asset> assets = new ArrayList<>();
+ for (int i=0;i<278;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ asset.setName("Asset"+i);
+ asset.setType("default");
+ asset = assetService.saveAsset(asset);
+ assets.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+
+ List<Asset> loadedAssets = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(23);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ loadedAssets.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assets, idComparator);
+ Collections.sort(loadedAssets, idComparator);
+
+ Assert.assertEquals(assets, loadedAssets);
+
+ assetService.unassignCustomerAssets(tenantId, customerId);
+
+ pageLink = new TextPageLink(33);
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertTrue(pageData.getData().isEmpty());
+
+ tenantService.deleteTenant(tenantId);
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdCustomerIdAndName() {
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ List<Asset> assetsTitle1 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = assetService.saveAsset(asset);
+ assetsTitle1.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+ String title2 = "Asset title 2";
+ List<Asset> assetsTitle2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType("default");
+ asset = assetService.saveAsset(asset);
+ assetsTitle2.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+
+ List<Asset> loadedAssetsTitle1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15, title1);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ loadedAssetsTitle1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle1, idComparator);
+ Collections.sort(loadedAssetsTitle1, idComparator);
+
+ Assert.assertEquals(assetsTitle1, loadedAssetsTitle1);
+
+ List<Asset> loadedAssetsTitle2 = new ArrayList<>();
+ pageLink = new TextPageLink(4, title2);
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ loadedAssetsTitle2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsTitle2, idComparator);
+ Collections.sort(loadedAssetsTitle2, idComparator);
+
+ Assert.assertEquals(assetsTitle2, loadedAssetsTitle2);
+
+ for (Asset asset : loadedAssetsTitle1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title1);
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsTitle2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4, title2);
+ pageData = assetService.findAssetsByTenantIdAndCustomerId(tenantId, customerId, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ customerService.deleteCustomer(customerId);
+ }
+
+ @Test
+ public void testFindAssetsByTenantIdCustomerIdAndType() {
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Asset title 1";
+ String type1 = "typeC";
+ List<Asset> assetsType1 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type1);
+ asset = assetService.saveAsset(asset);
+ assetsType1.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+ String title2 = "Asset title 2";
+ String type2 = "typeD";
+ List<Asset> assetsType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Asset asset = new Asset();
+ asset.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ asset.setName(name);
+ asset.setType(type2);
+ asset = assetService.saveAsset(asset);
+ assetsType2.add(assetService.assignAssetToCustomer(asset.getId(), customerId));
+ }
+
+ List<Asset> loadedAssetsType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Asset> pageData = null;
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ loadedAssetsType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType1, idComparator);
+ Collections.sort(loadedAssetsType1, idComparator);
+
+ Assert.assertEquals(assetsType1, loadedAssetsType1);
+
+ List<Asset> loadedAssetsType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ loadedAssetsType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(assetsType2, idComparator);
+ Collections.sort(loadedAssetsType2, idComparator);
+
+ Assert.assertEquals(assetsType2, loadedAssetsType2);
+
+ for (Asset asset : loadedAssetsType1) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Asset asset : loadedAssetsType2) {
+ assetService.deleteAsset(asset.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = assetService.findAssetsByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ customerService.deleteCustomer(customerId);
+ }
+
+}
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
index efdab8a..4d9ef9f 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceCredentialsServiceImplTest.java
@@ -58,6 +58,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithEmptyDevice() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -73,6 +74,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsType() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -88,6 +90,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithEmptyCredentialsId() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -103,6 +106,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveNonExistentDeviceCredentials() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -122,6 +126,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithNonExistentDevice() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -137,6 +142,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceCredentialsWithInvalidCredemtialsIdLength() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(device.getId());
@@ -153,6 +159,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
@@ -166,6 +173,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
@@ -181,6 +189,7 @@ public class DeviceCredentialsServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
DeviceCredentials deviceCredentials = deviceCredentialsService.findDeviceCredentialsByDeviceId(savedDevice.getId());
Assert.assertEquals(savedDevice.getId(), deviceCredentials.getDeviceId());
diff --git a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
index e9113d4..4f0bc8e 100644
--- a/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
+++ b/dao/src/test/java/org/thingsboard/server/dao/service/DeviceServiceImplTest.java
@@ -24,6 +24,7 @@ import org.junit.Test;
import org.thingsboard.server.common.data.Customer;
import org.thingsboard.server.common.data.Device;
import org.thingsboard.server.common.data.Tenant;
+import org.thingsboard.server.common.data.TenantDeviceType;
import org.thingsboard.server.common.data.id.CustomerId;
import org.thingsboard.server.common.data.id.DeviceCredentialsId;
import org.thingsboard.server.common.data.id.DeviceId;
@@ -37,6 +38,7 @@ import org.thingsboard.server.dao.exception.DataValidationException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.Executors;
import static org.thingsboard.server.dao.model.ModelConstants.NULL_UUID;
@@ -65,6 +67,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
Assert.assertNotNull(savedDevice);
@@ -95,6 +98,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
@Test(expected = DataValidationException.class)
public void testSaveDeviceWithEmptyName() {
Device device = new Device();
+ device.setType("default");
device.setTenantId(tenantId);
deviceService.saveDevice(device);
}
@@ -103,6 +107,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceWithEmptyTenant() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
deviceService.saveDevice(device);
}
@@ -110,6 +115,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testSaveDeviceWithInvalidTenant() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(new TenantId(UUIDs.timeBased()));
deviceService.saveDevice(device);
}
@@ -118,6 +124,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testAssignDeviceToNonExistentCustomer() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
try {
@@ -131,6 +138,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
public void testAssignDeviceToCustomerFromDifferentTenant() {
Device device = new Device();
device.setName("My device");
+ device.setType("default");
device.setTenantId(tenantId);
device = deviceService.saveDevice(device);
Tenant tenant = new Tenant();
@@ -153,18 +161,56 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
Device foundDevice = deviceService.findDeviceById(savedDevice.getId());
Assert.assertNotNull(foundDevice);
Assert.assertEquals(savedDevice, foundDevice);
deviceService.deleteDevice(savedDevice.getId());
}
+
+ @Test
+ public void testFindDeviceTypesByTenantId() throws Exception {
+ List<Device> devices = new ArrayList<>();
+ try {
+ for (int i=0;i<3;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("My device B"+i);
+ device.setType("typeB");
+ devices.add(deviceService.saveDevice(device));
+ }
+ for (int i=0;i<7;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("My device C"+i);
+ device.setType("typeC");
+ devices.add(deviceService.saveDevice(device));
+ }
+ for (int i=0;i<9;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ device.setName("My device A"+i);
+ device.setType("typeA");
+ devices.add(deviceService.saveDevice(device));
+ }
+ List<TenantDeviceType> deviceTypes = deviceService.findDeviceTypesByTenantId(tenantId).get();
+ Assert.assertNotNull(deviceTypes);
+ Assert.assertEquals(3, deviceTypes.size());
+ Assert.assertEquals("typeA", deviceTypes.get(0).getType());
+ Assert.assertEquals("typeB", deviceTypes.get(1).getType());
+ Assert.assertEquals("typeC", deviceTypes.get(2).getType());
+ } finally {
+ devices.forEach((device) -> { deviceService.deleteDevice(device.getId()); });
+ }
+ }
@Test
public void testDeleteDevice() {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("My device");
+ device.setType("default");
Device savedDevice = deviceService.saveDevice(device);
Device foundDevice = deviceService.findDeviceById(savedDevice.getId());
Assert.assertNotNull(foundDevice);
@@ -188,6 +234,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Device"+i);
+ device.setType("default");
devices.add(deviceService.saveDevice(device));
}
@@ -216,7 +263,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
tenantService.deleteTenant(tenantId);
}
-
+
@Test
public void testFindDevicesByTenantIdAndName() {
String title1 = "Device title 1";
@@ -228,6 +275,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle1.add(deviceService.saveDevice(device));
}
String title2 = "Device title 2";
@@ -239,6 +287,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
devicesTitle2.add(deviceService.saveDevice(device));
}
@@ -291,6 +340,85 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Assert.assertFalse(pageData.hasNext());
Assert.assertEquals(0, pageData.getData().size());
}
+
+ @Test
+ public void testFindDevicesByTenantIdAndType() {
+ String title1 = "Device title 1";
+ String type1 = "typeA";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ devicesType1.add(deviceService.saveDevice(device));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeB";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ devicesType2.add(deviceService.saveDevice(device));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type1, pageLink);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type2, pageLink);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndType(tenantId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ }
@Test
public void testFindDevicesByTenantIdAndCustomerId() {
@@ -311,6 +439,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Device device = new Device();
device.setTenantId(tenantId);
device.setName("Device"+i);
+ device.setType("default");
device = deviceService.saveDevice(device);
devices.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
}
@@ -359,6 +488,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title1+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = deviceService.saveDevice(device);
devicesTitle1.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
}
@@ -371,6 +501,7 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
String name = title2+suffix;
name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
device.setName(name);
+ device.setType("default");
device = deviceService.saveDevice(device);
devicesTitle2.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
}
@@ -425,4 +556,94 @@ public class DeviceServiceImplTest extends AbstractServiceTest {
Assert.assertEquals(0, pageData.getData().size());
customerService.deleteCustomer(customerId);
}
+
+ @Test
+ public void testFindDevicesByTenantIdCustomerIdAndType() {
+
+ Customer customer = new Customer();
+ customer.setTitle("Test customer");
+ customer.setTenantId(tenantId);
+ customer = customerService.saveCustomer(customer);
+ CustomerId customerId = customer.getId();
+
+ String title1 = "Device title 1";
+ String type1 = "typeC";
+ List<Device> devicesType1 = new ArrayList<>();
+ for (int i=0;i<175;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title1+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type1);
+ device = deviceService.saveDevice(device);
+ devicesType1.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
+ }
+ String title2 = "Device title 2";
+ String type2 = "typeD";
+ List<Device> devicesType2 = new ArrayList<>();
+ for (int i=0;i<143;i++) {
+ Device device = new Device();
+ device.setTenantId(tenantId);
+ String suffix = RandomStringUtils.randomAlphanumeric(15);
+ String name = title2+suffix;
+ name = i % 2 == 0 ? name.toLowerCase() : name.toUpperCase();
+ device.setName(name);
+ device.setType(type2);
+ device = deviceService.saveDevice(device);
+ devicesType2.add(deviceService.assignDeviceToCustomer(device.getId(), customerId));
+ }
+
+ List<Device> loadedDevicesType1 = new ArrayList<>();
+ TextPageLink pageLink = new TextPageLink(15);
+ TextPageData<Device> pageData = null;
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ loadedDevicesType1.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType1, idComparator);
+ Collections.sort(loadedDevicesType1, idComparator);
+
+ Assert.assertEquals(devicesType1, loadedDevicesType1);
+
+ List<Device> loadedDevicesType2 = new ArrayList<>();
+ pageLink = new TextPageLink(4);
+ do {
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ loadedDevicesType2.addAll(pageData.getData());
+ if (pageData.hasNext()) {
+ pageLink = pageData.getNextPageLink();
+ }
+ } while (pageData.hasNext());
+
+ Collections.sort(devicesType2, idComparator);
+ Collections.sort(loadedDevicesType2, idComparator);
+
+ Assert.assertEquals(devicesType2, loadedDevicesType2);
+
+ for (Device device : loadedDevicesType1) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type1, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+
+ for (Device device : loadedDevicesType2) {
+ deviceService.deleteDevice(device.getId());
+ }
+
+ pageLink = new TextPageLink(4);
+ pageData = deviceService.findDevicesByTenantIdAndCustomerIdAndType(tenantId, customerId, type2, pageLink);
+ Assert.assertFalse(pageData.hasNext());
+ Assert.assertEquals(0, pageData.getData().size());
+ customerService.deleteCustomer(customerId);
+ }
+
}
ui/src/app/api/asset.service.js 24(+21 -3)
diff --git a/ui/src/app/api/asset.service.js b/ui/src/app/api/asset.service.js
index f685b1e..2fbb11d 100644
--- a/ui/src/app/api/asset.service.js
+++ b/ui/src/app/api/asset.service.js
@@ -31,7 +31,8 @@ function AssetService($http, $q, customerService, userService) {
getTenantAssets: getTenantAssets,
getCustomerAssets: getCustomerAssets,
findByQuery: findByQuery,
- fetchAssetsByNameFilter: fetchAssetsByNameFilter
+ fetchAssetsByNameFilter: fetchAssetsByNameFilter,
+ getAssetTypes: getAssetTypes
}
return service;
@@ -152,7 +153,7 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
- function getTenantAssets(pageLink, applyCustomersInfo, config) {
+ function getTenantAssets(pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/tenant/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -164,6 +165,9 @@ function AssetService($http, $q, customerService, userService) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -184,7 +188,7 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
- function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config) {
+ function getCustomerAssets(customerId, pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/assets?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -196,6 +200,9 @@ function AssetService($http, $q, customerService, userService) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -258,4 +265,15 @@ function AssetService($http, $q, customerService, userService) {
return deferred.promise;
}
+ function getAssetTypes() {
+ var deferred = $q.defer();
+ var url = '/api/asset/types';
+ $http.get(url).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
}
ui/src/app/api/device.service.js 24(+21 -3)
diff --git a/ui/src/app/api/device.service.js b/ui/src/app/api/device.service.js
index d12d025..b1d1bb6 100644
--- a/ui/src/app/api/device.service.js
+++ b/ui/src/app/api/device.service.js
@@ -41,12 +41,13 @@ function DeviceService($http, $q, attributeService, customerService, types) {
deleteDeviceAttributes: deleteDeviceAttributes,
sendOneWayRpcCommand: sendOneWayRpcCommand,
sendTwoWayRpcCommand: sendTwoWayRpcCommand,
- findByQuery: findByQuery
+ findByQuery: findByQuery,
+ getDeviceTypes: getDeviceTypes
}
return service;
- function getTenantDevices(pageLink, applyCustomersInfo, config) {
+ function getTenantDevices(pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/tenant/devices?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -58,6 +59,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomersInfo(response.data.data).then(
@@ -78,7 +82,7 @@ function DeviceService($http, $q, attributeService, customerService, types) {
return deferred.promise;
}
- function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config) {
+ function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config, type) {
var deferred = $q.defer();
var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
if (angular.isDefined(pageLink.textSearch)) {
@@ -90,6 +94,9 @@ function DeviceService($http, $q, attributeService, customerService, types) {
if (angular.isDefined(pageLink.textOffset)) {
url += '&textOffset=' + pageLink.textOffset;
}
+ if (angular.isDefined(type) && type.length) {
+ url += '&type=' + type;
+ }
$http.get(url, config).then(function success(response) {
if (applyCustomersInfo) {
customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
@@ -286,4 +293,15 @@ function DeviceService($http, $q, attributeService, customerService, types) {
return deferred.promise;
}
+ function getDeviceTypes() {
+ var deferred = $q.defer();
+ var url = '/api/device/types';
+ $http.get(url).then(function success(response) {
+ deferred.resolve(response.data);
+ }, function fail() {
+ deferred.reject();
+ });
+ return deferred.promise;
+ }
+
}
ui/src/app/api/entity.service.js 29(+28 -1)
diff --git a/ui/src/app/api/entity.service.js b/ui/src/app/api/entity.service.js
index 43c537c..a70308b 100644
--- a/ui/src/app/api/entity.service.js
+++ b/ui/src/app/api/entity.service.js
@@ -36,7 +36,8 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
saveRelatedEntity: saveRelatedEntity,
getRelatedEntity: getRelatedEntity,
deleteRelatedEntity: deleteRelatedEntity,
- moveEntity: moveEntity
+ moveEntity: moveEntity,
+ copyEntity: copyEntity
};
return service;
@@ -626,6 +627,32 @@ function EntityService($http, $q, $filter, $translate, userService, deviceServic
return deferred.promise;
}
+ function copyEntity(entity, targetParentId, keys) {
+ var deferred = $q.defer();
+ if (!entity.id && !entity.id.id) {
+ deferred.reject();
+ } else {
+ getRelatedEntity(entity.id, keys).then(
+ function success(relatedEntity) {
+ delete relatedEntity.id.id;
+ relatedEntity.name = entity.name;
+ saveRelatedEntity(relatedEntity, targetParentId, keys).then(
+ function success(savedEntity) {
+ deferred.resolve(savedEntity);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ }
+ return deferred.promise;
+ }
+
function saveEntityPromise(entity) {
var entityType = entity.id.entityType;
if (!entity.id.id) {
ui/src/app/app.config.js 4(+0 -4)
diff --git a/ui/src/app/app.config.js b/ui/src/app/app.config.js
index fef3273..074437d 100644
--- a/ui/src/app/app.config.js
+++ b/ui/src/app/app.config.js
@@ -160,10 +160,6 @@ export default function AppConfig($provide,
indigoTheme();
}
- $mdThemingProvider.theme('tb-search-input', 'default')
- .primaryPalette('tb-primary')
- .backgroundPalette('tb-primary');
-
$mdThemingProvider.setDefaultTheme('default');
//$mdThemingProvider.alwaysWatchTheme(true);
}
ui/src/app/asset/asset.controller.js 12(+7 -5)
diff --git a/ui/src/app/asset/asset.controller.js b/ui/src/app/asset/asset.controller.js
index d0944ae..1253891 100644
--- a/ui/src/app/asset/asset.controller.js
+++ b/ui/src/app/asset/asset.controller.js
@@ -47,7 +47,8 @@ export function AssetCardController(types) {
/*@ngInject*/
-export function AssetController(userService, assetService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
+export function AssetController($rootScope, userService, assetService, customerService, $state, $stateParams,
+ $document, $mdDialog, $q, $translate, types) {
var customerId = $stateParams.customerId;
@@ -129,8 +130,8 @@ export function AssetController(userService, assetService, customerService, $sta
}
if (vm.assetsScope === 'tenant') {
- fetchAssetsFunction = function (pageLink) {
- return assetService.getTenantAssets(pageLink, true);
+ fetchAssetsFunction = function (pageLink, assetType) {
+ return assetService.getTenantAssets(pageLink, true, null, assetType);
};
deleteAssetFunction = function (assetId) {
return assetService.deleteAsset(assetId);
@@ -229,8 +230,8 @@ export function AssetController(userService, assetService, customerService, $sta
} else if (vm.assetsScope === 'customer' || vm.assetsScope === 'customer_user') {
- fetchAssetsFunction = function (pageLink) {
- return assetService.getCustomerAssets(customerId, pageLink, true);
+ fetchAssetsFunction = function (pageLink, assetType) {
+ return assetService.getCustomerAssets(customerId, pageLink, true, null, assetType);
};
deleteAssetFunction = function (assetId) {
return assetService.unassignAssetFromCustomer(assetId);
@@ -333,6 +334,7 @@ export function AssetController(userService, assetService, customerService, $sta
var deferred = $q.defer();
assetService.saveAsset(asset).then(
function success(savedAsset) {
+ $rootScope.$broadcast('assetSaved');
var assets = [ savedAsset ];
customerService.applyAssignedCustomersInfo(assets).then(
function success(items) {
ui/src/app/asset/asset.directive.js 1(+1 -0)
diff --git a/ui/src/app/asset/asset.directive.js b/ui/src/app/asset/asset.directive.js
index 8c13082..7110e6a 100644
--- a/ui/src/app/asset/asset.directive.js
+++ b/ui/src/app/asset/asset.directive.js
@@ -25,6 +25,7 @@ export default function AssetDirective($compile, $templateCache, toast, $transla
var template = $templateCache.get(assetFieldsetTemplate);
element.html(template);
+ scope.types = types;
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
ui/src/app/asset/asset.routes.js 6(+5 -1)
diff --git a/ui/src/app/asset/asset.routes.js b/ui/src/app/asset/asset.routes.js
index c9a312d..732f74c 100644
--- a/ui/src/app/asset/asset.routes.js
+++ b/ui/src/app/asset/asset.routes.js
@@ -20,7 +20,7 @@ import assetsTemplate from './assets.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function AssetRoutes($stateProvider) {
+export default function AssetRoutes($stateProvider, types) {
$stateProvider
.state('home.assets', {
url: '/assets',
@@ -37,6 +37,8 @@ export default function AssetRoutes($stateProvider) {
data: {
assetsType: 'tenant',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.asset,
pageTitle: 'asset.assets'
},
ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function AssetRoutes($stateProvider) {
data: {
assetsType: 'customer',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.asset,
pageTitle: 'customer.assets'
},
ncyBreadcrumb: {
ui/src/app/asset/asset-card.tpl.html 7(+5 -2)
diff --git a/ui/src/app/asset/asset-card.tpl.html b/ui/src/app/asset/asset-card.tpl.html
index 3c06558..30d0483 100644
--- a/ui/src/app/asset/asset-card.tpl.html
+++ b/ui/src/app/asset/asset-card.tpl.html
@@ -15,5 +15,8 @@
limitations under the License.
-->
-<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
-<div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
+<div flex layout="column" style="margin-top: -10px;">
+ <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
+ <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'asset.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+ <div class="tb-small" ng-show="vm.isPublic()">{{'asset.public' | translate}}</div>
+</div>
ui/src/app/asset/asset-fieldset.tpl.html 14(+7 -7)
diff --git a/ui/src/app/asset/asset-fieldset.tpl.html b/ui/src/app/asset/asset-fieldset.tpl.html
index 8cf0c96..d921b2e 100644
--- a/ui/src/app/asset/asset-fieldset.tpl.html
+++ b/ui/src/app/asset/asset-fieldset.tpl.html
@@ -56,13 +56,13 @@
<div translate ng-message="required">asset.name-required</div>
</div>
</md-input-container>
- <md-input-container class="md-block">
- <label translate>asset.type</label>
- <input required name="type" ng-model="asset.type">
- <div ng-messages="theForm.name.$error">
- <div translate ng-message="required">asset.type-required</div>
- </div>
- </md-input-container>
+ <tb-entity-subtype-autocomplete
+ ng-disabled="loading || !isEdit"
+ tb-required="true"
+ the-form="theForm"
+ ng-model="asset.type"
+ entity-type="types.entityType.asset">
+ </tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<label translate>asset.description</label>
<textarea ng-model="asset.additionalInfo.description" rows="2"></textarea>
diff --git a/ui/src/app/common/dashboard-utils.service.js b/ui/src/app/common/dashboard-utils.service.js
index 57317b5..78136df 100644
--- a/ui/src/app/common/dashboard-utils.service.js
+++ b/ui/src/app/common/dashboard-utils.service.js
@@ -179,6 +179,7 @@ function DashboardUtils(types, utils, timeService) {
dashboard.configuration.settings.showEntitiesSelect = true;
dashboard.configuration.settings.showDashboardTimewindow = true;
dashboard.configuration.settings.showDashboardExport = true;
+ dashboard.configuration.settings.toolbarAlwaysOpen = false;
} else {
if (angular.isUndefined(dashboard.configuration.settings.stateControllerId)) {
dashboard.configuration.settings.stateControllerId = 'default';
diff --git a/ui/src/app/components/expand-fullscreen.directive.js b/ui/src/app/components/expand-fullscreen.directive.js
index 37491e7..b70fd94 100644
--- a/ui/src/app/components/expand-fullscreen.directive.js
+++ b/ui/src/app/components/expand-fullscreen.directive.js
@@ -24,7 +24,7 @@ export default angular.module('thingsboard.directives.expandFullscreen', [])
/* eslint-disable angular/angularelement */
/*@ngInject*/
-function ExpandFullscreen($compile, $document) {
+function ExpandFullscreen($compile, $document, $timeout) {
var uniqueId = 1;
var linker = function (scope, element, attrs) {
@@ -97,10 +97,6 @@ function ExpandFullscreen($compile, $document) {
scope.expanded = !scope.expanded;
}
- var expandButton = null;
- if (attrs.expandButtonId) {
- expandButton = $('#' + attrs.expandButtonId, element)[0];
- }
var buttonSize;
if (attrs.expandButtonSize) {
buttonSize = attrs.expandButtonSize;
@@ -115,27 +111,38 @@ function ExpandFullscreen($compile, $document) {
'options=\'{"easing": "circ-in-out", "duration": 375, "rotation": "none"}\'>' +
'</ng-md-icon>';
- if (expandButton) {
- expandButton = angular.element(expandButton);
- if (scope.hideExpandButton()) {
- expandButton.remove();
- } else {
- expandButton.attr('md-ink-ripple', 'false');
- expandButton.append(html);
+ if (attrs.expandButtonId) {
+ $timeout(function() {
+ var expandButton = $('#' + attrs.expandButtonId, element)[0];
+ renderExpandButton(expandButton);
+ });
+ } else {
+ renderExpandButton();
+ }
+
+ function renderExpandButton(expandButton) {
+ if (expandButton) {
+ expandButton = angular.element(expandButton);
+ if (scope.hideExpandButton()) {
+ expandButton.remove();
+ } else {
+ expandButton.attr('md-ink-ripple', 'false');
+ expandButton.append(html);
- $compile(expandButton.contents())(scope);
+ $compile(expandButton.contents())(scope);
- expandButton.on("click", scope.toggleExpand);
- }
- } else if (!scope.hideExpandButton()) {
- var button = angular.element('<md-button class="tb-fullscreen-button-style tb-fullscreen-button-pos md-icon-button" ' +
- 'md-ink-ripple="false" ng-click="toggleExpand($event)">' +
- html +
- '</md-button>');
+ expandButton.on("click", scope.toggleExpand);
+ }
+ } else if (!scope.hideExpandButton()) {
+ var button = angular.element('<md-button class="tb-fullscreen-button-style tb-fullscreen-button-pos md-icon-button" ' +
+ 'md-ink-ripple="false" ng-click="toggleExpand($event)">' +
+ html +
+ '</md-button>');
- $compile(button)(scope);
+ $compile(button)(scope);
- element.prepend(button);
+ element.prepend(button);
+ }
}
}
ui/src/app/components/grid.directive.js 28(+15 -13)
diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js
index 5664400..296456a 100644
--- a/ui/src/app/components/grid.directive.js
+++ b/ui/src/app/components/grid.directive.js
@@ -197,7 +197,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
},
getLength: function () {
- if (vm.items.hasNext) {
+ if (vm.items.hasNext && !vm.items.pending) {
return vm.items.rowData.length + pageSize;
} else {
return vm.items.rowData.length;
@@ -206,7 +206,7 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
fetchMoreItems_: function () {
if (vm.items.hasNext && !vm.items.pending) {
- var promise = vm.fetchItemsFunc(vm.items.nextPageLink);
+ var promise = vm.fetchItemsFunc(vm.items.nextPageLink, $scope.searchConfig.searchEntitySubtype);
if (promise) {
vm.items.pending = true;
promise.then(
@@ -433,6 +433,10 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
reload();
});
+ $scope.$on('searchEntitySubtypeUpdated', function () {
+ reload();
+ });
+
vm.onGridInited(vm);
vm.itemRows.getItemAtIndex(pageSize);
@@ -441,18 +445,16 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
if (vm.items && vm.items.pending) {
vm.items.reloadPending = true;
} else {
- vm.items = {
- data: [],
- rowData: [],
- nextPageLink: {
- limit: pageSize,
- textSearch: $scope.searchConfig.searchText
- },
- selections: {},
- selectedCount: 0,
- hasNext: true,
- pending: false
+ vm.items.data.length = 0;
+ vm.items.rowData.length = 0;
+ vm.items.nextPageLink = {
+ limit: pageSize,
+ textSearch: $scope.searchConfig.searchText
};
+ vm.items.selections = {};
+ vm.items.selectedCount = 0;
+ vm.items.hasNext = true;
+ vm.items.pending = false;
vm.detailsConfig.isDetailsOpen = false;
vm.items.reloadPending = false;
vm.itemRows.getItemAtIndex(pageSize);
ui/src/app/components/grid.tpl.html 9(+5 -4)
diff --git a/ui/src/app/components/grid.tpl.html b/ui/src/app/components/grid.tpl.html
index c29c2e5..24285d8 100644
--- a/ui/src/app/components/grid.tpl.html
+++ b/ui/src/app/components/grid.tpl.html
@@ -24,9 +24,8 @@
<md-virtual-repeat-container ng-show="vm.hasData()" tb-scope-element="repeatContainer" id="tb-vertical-container" md-top-index="vm.topIndex" flex>
<div class="md-padding" layout="column">
<section layout="row" md-virtual-repeat="rowItem in vm.itemRows" md-on-demand md-item-size="vm.itemHeight">
- <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}">
- <md-card ng-if="rowItem[n]"
- ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
+ <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="rowItem[n]">
+ <md-card ng-class="{'tb-current-item': vm.isCurrentItem(rowItem[n])}"
class="repeated-item tb-card-item" ng-style="{'height':(vm.itemHeight-16)+'px','cursor':'pointer'}"
ng-click="vm.clickItemFunc($event, rowItem[n])">
<section layout="row" layout-wrap>
@@ -43,7 +42,7 @@
</md-card-title>
</section>
<md-card-content flex>
- <tb-grid-card-content grid-ctl="vm" parent-ctl="vm.parentCtl" item-controller="vm.itemCardController" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
+ <tb-grid-card-content flex grid-ctl="vm" parent-ctl="vm.parentCtl" item-controller="vm.itemCardController" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
</md-card-content>
<md-card-actions layout="row" layout-align="end end">
<md-button ng-if="action.isEnabled(rowItem[n])" ng-disabled="loading" class="md-icon-button md-primary" ng-repeat="action in vm.actionsList"
@@ -56,6 +55,8 @@
</md-card-actions>
</md-card>
</div>
+ <div flex ng-repeat="n in [] | range:vm.columns" ng-style="{'height':vm.itemHeight+'px'}" ng-if="!rowItem[n]">
+ </div>
</section>
</div>
</md-virtual-repeat-container>
ui/src/app/dashboard/dashboard.controller.js 17(+15 -2)
diff --git a/ui/src/app/dashboard/dashboard.controller.js b/ui/src/app/dashboard/dashboard.controller.js
index c446be4..ecf072d 100644
--- a/ui/src/app/dashboard/dashboard.controller.js
+++ b/ui/src/app/dashboard/dashboard.controller.js
@@ -63,7 +63,9 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
Object.defineProperty(vm, 'toolbarOpened', {
- get: function() { return !vm.widgetEditMode && ($scope.forceFullscreen || vm.isToolbarOpened || vm.isEdit || vm.showRightLayoutSwitch()); },
+ get: function() {
+ return !vm.widgetEditMode &&
+ (toolbarAlwaysOpen() || $scope.forceFullscreen || vm.isToolbarOpened || vm.isEdit || vm.showRightLayoutSwitch()); },
set: function() { }
});
@@ -103,9 +105,11 @@ export default function DashboardController(types, dashboardUtils, widgetService
}
vm.showCloseToolbar = function() {
- return !$scope.forceFullscreen && !vm.isEdit && !vm.showRightLayoutSwitch();
+ return !vm.toolbarAlwaysOpen() && !$scope.forceFullscreen && !vm.isEdit && !vm.showRightLayoutSwitch();
}
+ vm.toolbarAlwaysOpen = toolbarAlwaysOpen;
+
vm.showRightLayoutSwitch = function() {
return vm.isMobile && vm.layouts.right.show;
}
@@ -738,6 +742,15 @@ export default function DashboardController(types, dashboardUtils, widgetService
return link;
}
+ function toolbarAlwaysOpen() {
+ if (vm.dashboard && vm.dashboard.configuration.settings &&
+ angular.isDefined(vm.dashboard.configuration.settings.toolbarAlwaysOpen)) {
+ return vm.dashboard.configuration.settings.toolbarAlwaysOpen;
+ } else {
+ return false;
+ }
+ }
+
function displayTitle() {
if (vm.dashboard && vm.dashboard.configuration.settings &&
angular.isDefined(vm.dashboard.configuration.settings.showTitle)) {
ui/src/app/dashboard/dashboard.scss 80(+8 -72)
diff --git a/ui/src/app/dashboard/dashboard.scss b/ui/src/app/dashboard/dashboard.scss
index bc5ec56..ca2bbab 100644
--- a/ui/src/app/dashboard/dashboard.scss
+++ b/ui/src/app/dashboard/dashboard.scss
@@ -68,87 +68,23 @@ section.tb-dashboard-toolbar {
pointer-events: none;
&.tb-dashboard-toolbar-opened {
right: 0px;
- @include transition(right .3s cubic-bezier(.55,0,.55,.2));
+ // @include transition(right .3s cubic-bezier(.55,0,.55,.2));
}
&.tb-dashboard-toolbar-closed {
right: 18px;
@include transition(right .3s cubic-bezier(.55,0,.55,.2) .2s);
}
- md-fab-toolbar {
- &.md-is-open {
- md-fab-trigger {
- .md-button {
- &.md-fab {
- opacity: 1;
- @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
- }
- }
- }
- }
- md-fab-trigger {
- .md-button {
- &.md-fab {
- line-height: 36px;
- width: 36px;
- height: 36px;
- margin: 4px 0 0 4px;
- opacity: 0.5;
- @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
- md-icon {
- position: absolute;
- top: 25%;
- margin: 0;
- line-height: 18px;
- height: 18px;
- width: 18px;
- min-height: 18px;
- min-width: 18px;
- }
- }
- }
- }
- .md-fab-toolbar-wrapper {
- height: 50px;
- md-toolbar {
- min-height: 46px;
- height: 46px;
- md-fab-actions {
- font-size: 16px;
- margin-top: 0px;
- .close-action {
- margin-right: -18px;
- }
- .md-fab-action-item {
- width: 100%;
- height: 46px;
- .tb-dashboard-action-panels {
- height: 46px;
- flex-direction: row-reverse;
- .tb-dashboard-action-panel {
- height: 46px;
- flex-direction: row-reverse;
- div {
- height: 46px;
- }
- md-select {
- pointer-events: all;
- }
- tb-states-component {
- pointer-events: all;
- }
- }
- }
- }
- }
- }
- }
- }
}
.tb-dashboard-container {
&.tb-dashboard-toolbar-opened {
- margin-top: 50px;
- @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+ &.is-fullscreen {
+ margin-top: 64px;
+ }
+ &:not(.is-fullscreen) {
+ margin-top: 50px;
+ @include transition(margin-top .3s cubic-bezier(.55,0,.55,.2));
+ }
}
&.tb-dashboard-toolbar-closed {
margin-top: 0px;
ui/src/app/dashboard/dashboard.tpl.html 190(+89 -101)
diff --git a/ui/src/app/dashboard/dashboard.tpl.html b/ui/src/app/dashboard/dashboard.tpl.html
index 94e016e..8338612 100644
--- a/ui/src/app/dashboard/dashboard.tpl.html
+++ b/ui/src/app/dashboard/dashboard.tpl.html
@@ -19,110 +19,98 @@
hide-expand-button="vm.widgetEditMode || vm.iframeMode || forceFullscreen" expand-tooltip-direction="bottom">
<section class="tb-dashboard-toolbar" ng-show="vm.showDashboardToolbar()"
ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
- <md-fab-toolbar ng-show="!vm.widgetEditMode" md-open="vm.toolbarOpened"
- md-direction="left">
- <md-fab-trigger class="align-with-text">
- <md-button aria-label="menu" class="md-fab md-primary" ng-click="vm.openToolbar()">
- <md-tooltip ng-show="!vm.toolbarOpened" md-direction="bottom">
- {{ 'dashboard.open-toolbar' | translate }}
- </md-tooltip>
- <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
- </md-button>
- </md-fab-trigger>
- <md-toolbar>
- <md-fab-actions class="md-toolbar-tools">
- <div class="tb-dashboard-action-panels" flex layout="row" layout-align="start center">
- <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="start center">
- <md-button ng-show="vm.showCloseToolbar()" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
- <md-tooltip md-direction="bottom">
- {{ 'dashboard.close-toolbar' | translate }}
- </md-tooltip>
- <md-icon aria-label="close-toolbar" class="material-icons">arrow_forward</md-icon>
- </md-button>
- <md-button ng-show="vm.showRightLayoutSwitch()" aria-label="switch-layouts" class="md-icon-button" ng-click="vm.toggleLayouts()">
- <ng-md-icon icon="{{vm.isRightLayoutOpened ? 'arrow_back' : 'menu'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
- <md-tooltip md-direction="bottom">
- {{ (vm.isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}
- </md-tooltip>
- </md-button>
- <md-button id="dashboard-expand-button"
- aria-label="{{ 'fullscreen.fullscreen' | translate }}"
- class="md-icon-button">
- </md-button>
- <tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true">
- </tb-user-menu>
- <md-button ng-show="vm.isEdit || vm.displayExport()"
- aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
- ng-click="vm.exportDashboard($event)">
- <md-tooltip md-direction="bottom">
- {{ 'dashboard.export' | translate }}
- </md-tooltip>
- <md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon>
- </md-button>
- <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()"
- is-toolbar
- direction="left"
- tooltip-direction="bottom" aggregation
- ng-model="vm.dashboardCtx.dashboardTimewindow">
- </tb-timewindow>
- <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
- tooltip-direction="bottom"
- ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"
- entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo">
- </tb-aliases-entity-select>
- <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
- ng-click="vm.openEntityAliases($event)">
- <md-tooltip md-direction="bottom">
- {{ 'entity.aliases' | translate }}
- </md-tooltip>
- <md-icon aria-label="{{ 'entity.aliases' | translate }}" class="material-icons">devices_other</md-icon>
- </md-button>
- <md-button ng-show="vm.isEdit" aria-label="{{ 'dashboard.settings' | translate }}" class="md-icon-button"
- ng-click="vm.openDashboardSettings($event)">
- <md-tooltip md-direction="bottom">
- {{ 'dashboard.settings' | translate }}
- </md-tooltip>
- <md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
- </md-button>
- <tb-dashboard-select ng-show="!vm.isEdit && !vm.widgetEditMode && vm.displayDashboardsSelect()"
- ng-model="vm.currentDashboardId"
- dashboards-scope="{{vm.currentDashboardScope}}"
- customer-id="vm.currentCustomerId">
- </tb-dashboard-select>
- </div>
- <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="end center">
- <div layout="row" layout-align="start center" ng-show="vm.isEdit">
- <md-button aria-label="{{ 'dashboard.manage-states' | translate }}" class="md-icon-button"
- ng-click="vm.manageDashboardStates($event)">
- <md-tooltip md-direction="bottom">
- {{ 'dashboard.manage-states' | translate }}
- </md-tooltip>
- <md-icon aria-label="{{ 'dashboard.manage-states' | translate }}" class="material-icons">layers</md-icon>
- </md-button>
- <md-button aria-label="{{ 'layout.manage' | translate }}" class="md-icon-button"
- ng-click="vm.manageDashboardLayouts($event)">
- <md-tooltip md-direction="bottom">
- {{ 'layout.manage' | translate }}
- </md-tooltip>
- <md-icon aria-label="{{ 'layout.manage' | translate }}" class="material-icons">view_compact</md-icon>
- </md-button>
- </div>
- <div layout="row" layout-align="start center">
- <tb-states-component ng-if="vm.isEdit" states-controller-id="'default'"
- dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
- </tb-states-component>
- <tb-states-component ng-if="!vm.isEdit" states-controller-id="vm.dashboardConfiguration.settings.stateControllerId"
- dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
- </tb-states-component>
- </div>
- </div>
+ <tb-dashboard-toolbar ng-show="!vm.widgetEditMode" force-fullscreen="forceFullscreen"
+ toolbar-opened="vm.toolbarOpened" on-trigger-click="vm.openToolbar()">
+ <div class="tb-dashboard-action-panels" flex layout="row" layout-align="start center">
+ <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="start center">
+ <md-button ng-show="vm.showCloseToolbar()" aria-label="close-toolbar" class="md-icon-button close-action" ng-click="vm.closeToolbar()">
+ <md-tooltip md-direction="bottom">
+ {{ 'dashboard.close-toolbar' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="close-toolbar" class="material-icons">arrow_forward</md-icon>
+ </md-button>
+ <md-button ng-show="vm.showRightLayoutSwitch()" aria-label="switch-layouts" class="md-icon-button" ng-click="vm.toggleLayouts()">
+ <ng-md-icon icon="{{vm.isRightLayoutOpened ? 'arrow_back' : 'menu'}}" options='{"easing": "circ-in-out", "duration": 375, "rotation": "none"}'></ng-md-icon>
+ <md-tooltip md-direction="bottom">
+ {{ (vm.isRightLayoutOpened ? 'dashboard.hide-details' : 'dashboard.show-details') | translate }}
+ </md-tooltip>
+ </md-button>
+ <md-button id="dashboard-expand-button"
+ aria-label="{{ 'fullscreen.fullscreen' | translate }}"
+ class="md-icon-button">
+ </md-button>
+ <tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true">
+ </tb-user-menu>
+ <md-button ng-show="vm.isEdit || vm.displayExport()"
+ aria-label="{{ 'action.export' | translate }}" class="md-icon-button"
+ ng-click="vm.exportDashboard($event)">
+ <md-tooltip md-direction="bottom">
+ {{ 'dashboard.export' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'action.export' | translate }}" class="material-icons">file_download</md-icon>
+ </md-button>
+ <tb-timewindow ng-show="vm.isEdit || vm.displayDashboardTimewindow()"
+ is-toolbar
+ direction="left"
+ tooltip-direction="bottom" aggregation
+ ng-model="vm.dashboardCtx.dashboardTimewindow">
+ </tb-timewindow>
+ <tb-aliases-entity-select ng-show="!vm.isEdit && vm.displayEntitiesSelect()"
+ tooltip-direction="bottom"
+ ng-model="vm.dashboardCtx.aliasesInfo.entityAliases"
+ entity-aliases-info="vm.dashboardCtx.aliasesInfo.entityAliasesInfo">
+ </tb-aliases-entity-select>
+ <md-button ng-show="vm.isEdit" aria-label="{{ 'entity.aliases' | translate }}" class="md-icon-button"
+ ng-click="vm.openEntityAliases($event)">
+ <md-tooltip md-direction="bottom">
+ {{ 'entity.aliases' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'entity.aliases' | translate }}" class="material-icons">devices_other</md-icon>
+ </md-button>
+ <md-button ng-show="vm.isEdit" aria-label="{{ 'dashboard.settings' | translate }}" class="md-icon-button"
+ ng-click="vm.openDashboardSettings($event)">
+ <md-tooltip md-direction="bottom">
+ {{ 'dashboard.settings' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'dashboard.settings' | translate }}" class="material-icons">settings</md-icon>
+ </md-button>
+ <tb-dashboard-select ng-show="!vm.isEdit && !vm.widgetEditMode && vm.displayDashboardsSelect()"
+ ng-model="vm.currentDashboardId"
+ dashboards-scope="{{vm.currentDashboardScope}}"
+ customer-id="vm.currentCustomerId">
+ </tb-dashboard-select>
+ </div>
+ <div class="tb-dashboard-action-panel" flex="50" layout="row" layout-align="end center">
+ <div layout="row" layout-align="start center" ng-show="vm.isEdit">
+ <md-button aria-label="{{ 'dashboard.manage-states' | translate }}" class="md-icon-button"
+ ng-click="vm.manageDashboardStates($event)">
+ <md-tooltip md-direction="bottom">
+ {{ 'dashboard.manage-states' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'dashboard.manage-states' | translate }}" class="material-icons">layers</md-icon>
+ </md-button>
+ <md-button aria-label="{{ 'layout.manage' | translate }}" class="md-icon-button"
+ ng-click="vm.manageDashboardLayouts($event)">
+ <md-tooltip md-direction="bottom">
+ {{ 'layout.manage' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="{{ 'layout.manage' | translate }}" class="material-icons">view_compact</md-icon>
+ </md-button>
</div>
- </md-fab-actions>
- </md-toolbar>
- </md-fab-toolbar>
+ <div layout="row" layout-align="start center">
+ <tb-states-component ng-if="vm.isEdit" states-controller-id="'default'"
+ dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
+ </tb-states-component>
+ <tb-states-component ng-if="!vm.isEdit" states-controller-id="vm.dashboardConfiguration.settings.stateControllerId"
+ dashboard-ctrl="vm" states="vm.dashboardConfiguration.states">
+ </tb-states-component>
+ </div>
+ </div>
+ </div>
+ </tb-dashboard-toolbar>
</section>
<section class="tb-dashboard-container tb-absolute-fill"
- ng-class="{ 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
+ ng-class="{ 'is-fullscreen': forceFullscreen, 'tb-dashboard-toolbar-opened': vm.toolbarOpened, 'tb-dashboard-toolbar-closed': !vm.toolbarOpened }">
<section ng-show="!loading && vm.dashboardConfigurationError()" layout-align="center center"
ng-style="{'color': vm.dashboard.configuration.settings.titleColor}"
ng-class="{'tb-padded' : !vm.widgetEditMode}"
diff --git a/ui/src/app/dashboard/dashboard-settings.controller.js b/ui/src/app/dashboard/dashboard-settings.controller.js
index dbe4cbe..ac98709 100644
--- a/ui/src/app/dashboard/dashboard-settings.controller.js
+++ b/ui/src/app/dashboard/dashboard-settings.controller.js
@@ -57,6 +57,10 @@ export default function DashboardSettingsController($scope, $mdDialog, statesCon
if (angular.isUndefined(vm.settings.showDashboardExport)) {
vm.settings.showDashboardExport = true;
}
+
+ if (angular.isUndefined(vm.settings.toolbarAlwaysOpen)) {
+ vm.settings.toolbarAlwaysOpen = false;
+ }
}
if (vm.gridSettings) {
diff --git a/ui/src/app/dashboard/dashboard-settings.tpl.html b/ui/src/app/dashboard/dashboard-settings.tpl.html
index 88fc66d..f3ff704 100644
--- a/ui/src/app/dashboard/dashboard-settings.tpl.html
+++ b/ui/src/app/dashboard/dashboard-settings.tpl.html
@@ -41,6 +41,9 @@
</md-select>
</md-input-container>
<div layout="row" layout-align="start center">
+ <md-checkbox flex aria-label="{{ 'dashboard.toolbar-always-open' | translate }}"
+ ng-model="vm.settings.toolbarAlwaysOpen">{{ 'dashboard.toolbar-always-open' | translate }}
+ </md-checkbox>
<md-checkbox flex aria-label="{{ 'dashboard.display-title' | translate }}"
ng-model="vm.settings.showTitle">{{ 'dashboard.display-title' | translate }}
</md-checkbox>
diff --git a/ui/src/app/dashboard/dashboard-toolbar.directive.js b/ui/src/app/dashboard/dashboard-toolbar.directive.js
new file mode 100644
index 0000000..63a34f8
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.directive.js
@@ -0,0 +1,88 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './dashboard-toolbar.scss';
+
+import 'javascript-detect-element-resize/detect-element-resize';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import dashboardToolbarTemplate from './dashboard-toolbar.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function DashboardToolbar() {
+ return {
+ restrict: "E",
+ scope: true,
+ transclude: true,
+ bindToController: {
+ toolbarOpened: '=',
+ forceFullscreen: '=',
+ onTriggerClick: '&'
+ },
+ controller: DashboardToolbarController,
+ controllerAs: 'vm',
+ templateUrl: dashboardToolbarTemplate
+ };
+}
+
+/* eslint-disable angular/angularelement */
+
+
+/*@ngInject*/
+function DashboardToolbarController($scope, $element, $timeout, mdFabToolbarAnimation) {
+
+ let vm = this;
+
+ vm.mdFabToolbarElement = angular.element($element[0].querySelector('md-fab-toolbar'));
+
+ $timeout(function() {
+ vm.mdFabBackgroundElement = angular.element(vm.mdFabToolbarElement[0].querySelector('.md-fab-toolbar-background'));
+ vm.mdFabTriggerElement = angular.element(vm.mdFabToolbarElement[0].querySelector('md-fab-trigger button'));
+ });
+
+ addResizeListener(vm.mdFabToolbarElement[0], triggerFabResize); // eslint-disable-line no-undef
+
+ $scope.$on("$destroy", function () {
+ removeResizeListener(vm.mdFabToolbarElement[0], triggerFabResize); // eslint-disable-line no-undef
+ });
+
+ function triggerFabResize() {
+ var ctrl = vm.mdFabToolbarElement.controller('mdFabToolbar');
+ if (ctrl.isOpen) {
+ if (!vm.mdFabBackgroundElement[0].offsetWidth) {
+ mdFabToolbarAnimation.addClass(vm.mdFabToolbarElement, 'md-is-open', function () {
+ });
+ } else {
+ var color = window.getComputedStyle(vm.mdFabTriggerElement[0]).getPropertyValue('background-color'); //eslint-disable-line
+
+ var width = vm.mdFabToolbarElement[0].offsetWidth;
+ var scale = 2 * (width / vm.mdFabTriggerElement[0].offsetWidth);
+ vm.mdFabBackgroundElement[0].style.backgroundColor = color;
+ vm.mdFabBackgroundElement[0].style.borderRadius = width + 'px';
+
+ var transform = vm.mdFabBackgroundElement[0].style.transform;
+ var targetTransform = 'scale(' + scale + ')';
+ if (!transform || !angular.equals(transform, targetTransform)) {
+ vm.mdFabBackgroundElement[0].style.transform = targetTransform;
+ }
+ }
+ }
+ }
+}
+
ui/src/app/dashboard/dashboard-toolbar.scss 114(+114 -0)
diff --git a/ui/src/app/dashboard/dashboard-toolbar.scss b/ui/src/app/dashboard/dashboard-toolbar.scss
new file mode 100644
index 0000000..af4889e
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.scss
@@ -0,0 +1,114 @@
+/**
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@import "~compass-sass-mixins/lib/compass";
+@import '../../scss/constants';
+
+tb-dashboard-toolbar {
+ md-fab-toolbar {
+ &.md-is-open {
+ md-fab-trigger {
+ .md-button {
+ &.md-fab {
+ opacity: 1;
+ @include transition(opacity .3s cubic-bezier(.55,0,.55,.2));
+ .md-fab-toolbar-background {
+ background-color: $primary-default !important;
+ }
+ }
+ }
+ }
+ }
+ md-fab-trigger {
+ .md-button {
+ &.md-fab {
+ line-height: 36px;
+ width: 36px;
+ height: 36px;
+ margin: 4px 0 0 4px;
+ opacity: 0.5;
+ @include transition(opacity .3s cubic-bezier(.55,0,.55,.2) .2s);
+ md-icon {
+ position: absolute;
+ top: 25%;
+ margin: 0;
+ line-height: 18px;
+ height: 18px;
+ width: 18px;
+ min-height: 18px;
+ min-width: 18px;
+ }
+ }
+ }
+ }
+ &.is-fullscreen {
+ &.md-is-open {
+ md-fab-trigger {
+ .md-button {
+ &.md-fab {
+ .md-fab-toolbar-background {
+ transition-delay: 0ms !important;
+ transition-duration: 0ms !important;
+ }
+ }
+ }
+ }
+ }
+ .md-fab-toolbar-wrapper {
+ height: 64px;
+ md-toolbar {
+ min-height: 64px;
+ height: 64px;
+ }
+ }
+ }
+ .md-fab-toolbar-wrapper {
+ height: 50px;
+ md-toolbar {
+ min-height: 50px;
+ height: 50px;
+ md-fab-actions {
+ font-size: 16px;
+ margin-top: 0px;
+ .close-action {
+ margin-right: -18px;
+ }
+ .md-fab-action-item {
+ width: 100%;
+ height: 46px;
+ .tb-dashboard-action-panels {
+ height: 46px;
+ flex-direction: row-reverse;
+ .tb-dashboard-action-panel {
+ height: 46px;
+ flex-direction: row-reverse;
+ div {
+ height: 46px;
+ }
+ md-select {
+ pointer-events: all;
+ }
+ tb-states-component {
+ pointer-events: all;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/ui/src/app/dashboard/dashboard-toolbar.tpl.html b/ui/src/app/dashboard/dashboard-toolbar.tpl.html
new file mode 100644
index 0000000..46192ff
--- /dev/null
+++ b/ui/src/app/dashboard/dashboard-toolbar.tpl.html
@@ -0,0 +1,35 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+
+<md-fab-toolbar md-open="vm.toolbarOpened"
+ md-direction="left"
+ ng-class="{'is-fullscreen': vm.forceFullscreen, 'md-whiteframe-z1': vm.forceFullscreen}">
+ <md-fab-trigger class="align-with-text">
+ <md-button aria-label="menu" class="md-fab md-primary" ng-click="vm.onTriggerClick()">
+ <md-tooltip ng-show="!vm.toolbarOpened" md-direction="bottom">
+ {{ 'dashboard.open-toolbar' | translate }}
+ </md-tooltip>
+ <md-icon aria-label="dashboard-toolbar" class="material-icons">more_horiz</md-icon>
+ </md-button>
+ </md-fab-trigger>
+ <md-toolbar>
+ <md-fab-actions class="md-toolbar-tools">
+ <div ng-transclude></div>
+ </md-fab-actions>
+ </md-toolbar>
+</md-fab-toolbar>
ui/src/app/dashboard/index.js 2(+2 -0)
diff --git a/ui/src/app/dashboard/index.js b/ui/src/app/dashboard/index.js
index 73a97a1..d940f09 100644
--- a/ui/src/app/dashboard/index.js
+++ b/ui/src/app/dashboard/index.js
@@ -45,6 +45,7 @@ import AddDashboardsToCustomerController from './add-dashboards-to-customer.cont
import AddWidgetController from './add-widget.controller';
import DashboardDirective from './dashboard.directive';
import EditWidgetDirective from './edit-widget.directive';
+import DashboardToolbar from './dashboard-toolbar.directive';
export default angular.module('thingsboard.dashboard', [
uiRouter,
@@ -78,4 +79,5 @@ export default angular.module('thingsboard.dashboard', [
.controller('AddWidgetController', AddWidgetController)
.directive('tbDashboardDetails', DashboardDirective)
.directive('tbEditWidget', EditWidgetDirective)
+ .directive('tbDashboardToolbar', DashboardToolbar)
.name;
diff --git a/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
index 151c05a..9f86b9b 100644
--- a/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
+++ b/ui/src/app/dashboard/states/manage-dashboard-states.tpl.html
@@ -58,7 +58,7 @@
{{ 'dashboard.search-states' | translate }}
</md-tooltip>
</md-button>
- <md-input-container md-theme="tb-search-input" flex>
+ <md-input-container flex>
<label> </label>
<input ng-model="vm.query.search" placeholder="{{ 'dashboard.search-states' | translate }}"/>
</md-input-container>
ui/src/app/device/device.controller.js 12(+7 -5)
diff --git a/ui/src/app/device/device.controller.js b/ui/src/app/device/device.controller.js
index 55b7083..3e8c9ac 100644
--- a/ui/src/app/device/device.controller.js
+++ b/ui/src/app/device/device.controller.js
@@ -48,7 +48,8 @@ export function DeviceCardController(types) {
/*@ngInject*/
-export function DeviceController(userService, deviceService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) {
+export function DeviceController($rootScope, userService, deviceService, customerService, $state, $stateParams,
+ $document, $mdDialog, $q, $translate, types) {
var customerId = $stateParams.customerId;
@@ -131,8 +132,8 @@ export function DeviceController(userService, deviceService, customerService, $s
}
if (vm.devicesScope === 'tenant') {
- fetchDevicesFunction = function (pageLink) {
- return deviceService.getTenantDevices(pageLink, true);
+ fetchDevicesFunction = function (pageLink, deviceType) {
+ return deviceService.getTenantDevices(pageLink, true, null, deviceType);
};
deleteDeviceFunction = function (deviceId) {
return deviceService.deleteDevice(deviceId);
@@ -242,8 +243,8 @@ export function DeviceController(userService, deviceService, customerService, $s
} else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') {
- fetchDevicesFunction = function (pageLink) {
- return deviceService.getCustomerDevices(customerId, pageLink, true);
+ fetchDevicesFunction = function (pageLink, deviceType) {
+ return deviceService.getCustomerDevices(customerId, pageLink, true, null, deviceType);
};
deleteDeviceFunction = function (deviceId) {
return deviceService.unassignDeviceFromCustomer(deviceId);
@@ -368,6 +369,7 @@ export function DeviceController(userService, deviceService, customerService, $s
var deferred = $q.defer();
deviceService.saveDevice(device).then(
function success(savedDevice) {
+ $rootScope.$broadcast('deviceSaved');
var devices = [ savedDevice ];
customerService.applyAssignedCustomersInfo(devices).then(
function success(items) {
diff --git a/ui/src/app/device/device.directive.js b/ui/src/app/device/device.directive.js
index eb50a15..eda4fb2 100644
--- a/ui/src/app/device/device.directive.js
+++ b/ui/src/app/device/device.directive.js
@@ -25,6 +25,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl
var template = $templateCache.get(deviceFieldsetTemplate);
element.html(template);
+ scope.types = types;
scope.isAssignedToCustomer = false;
scope.isPublic = false;
scope.assignedCustomer = null;
ui/src/app/device/device.routes.js 6(+5 -1)
diff --git a/ui/src/app/device/device.routes.js b/ui/src/app/device/device.routes.js
index c1f5b27..b1ca7ed 100644
--- a/ui/src/app/device/device.routes.js
+++ b/ui/src/app/device/device.routes.js
@@ -20,7 +20,7 @@ import devicesTemplate from './devices.tpl.html';
/* eslint-enable import/no-unresolved, import/default */
/*@ngInject*/
-export default function DeviceRoutes($stateProvider) {
+export default function DeviceRoutes($stateProvider, types) {
$stateProvider
.state('home.devices', {
url: '/devices',
@@ -37,6 +37,8 @@ export default function DeviceRoutes($stateProvider) {
data: {
devicesType: 'tenant',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.device,
pageTitle: 'device.devices'
},
ncyBreadcrumb: {
@@ -58,6 +60,8 @@ export default function DeviceRoutes($stateProvider) {
data: {
devicesType: 'customer',
searchEnabled: true,
+ searchByEntitySubtype: true,
+ searchEntityType: types.entityType.device,
pageTitle: 'customer.devices'
},
ncyBreadcrumb: {
diff --git a/ui/src/app/device/device-card.tpl.html b/ui/src/app/device/device-card.tpl.html
index bb84af7..58f4c85 100644
--- a/ui/src/app/device/device-card.tpl.html
+++ b/ui/src/app/device/device-card.tpl.html
@@ -15,5 +15,8 @@
limitations under the License.
-->
-<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
-<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
+<div flex layout="column" style="margin-top: -10px;">
+ <div flex style="text-transform: uppercase; padding-bottom: 10px;">{{vm.item.type}}</div>
+ <div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div>
+ <div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div>
+</div>
diff --git a/ui/src/app/device/device-fieldset.tpl.html b/ui/src/app/device/device-fieldset.tpl.html
index fd6c9d1..767e47a 100644
--- a/ui/src/app/device/device-fieldset.tpl.html
+++ b/ui/src/app/device/device-fieldset.tpl.html
@@ -66,6 +66,13 @@
<div translate ng-message="required">device.name-required</div>
</div>
</md-input-container>
+ <tb-entity-subtype-autocomplete
+ ng-disabled="loading || !isEdit"
+ tb-required="true"
+ the-form="theForm"
+ ng-model="device.type"
+ entity-type="types.entityType.device">
+ </tb-entity-subtype-autocomplete>
<md-input-container class="md-block">
<md-checkbox ng-disabled="loading || !isEdit" flex aria-label="{{ 'device.is-gateway' | translate }}"
ng-model="device.additionalInfo.gateway">{{ 'device.is-gateway' | translate }}
diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html
index bf3b8cd..3b10902 100644
--- a/ui/src/app/entity/attribute/attribute-table.tpl.html
+++ b/ui/src/app/entity/attribute/attribute-table.tpl.html
@@ -63,7 +63,7 @@
{{ 'action.search' | translate }}
</md-tooltip>
</md-button>
- <md-input-container md-theme="tb-search-input" flex>
+ <md-input-container flex>
<label> </label>
<input ng-model="query.search" placeholder="{{ 'common.enter-search' | translate }}"/>
</md-input-container>
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.directive.js b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
new file mode 100644
index 0000000..98110b0
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-autocomplete.directive.js
@@ -0,0 +1,141 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import './entity-subtype-autocomplete.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeAutocompleteTemplate from './entity-subtype-autocomplete.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntitySubtypeAutocomplete($compile, $templateCache, $q, $filter, assetService, deviceService, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entitySubtypeAutocompleteTemplate);
+ element.html(template);
+
+ scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false;
+ scope.subType = null;
+ scope.subTypeSearchText = '';
+ scope.entitySubtypes = null;
+
+ scope.fetchSubTypes = function(searchText) {
+ var deferred = $q.defer();
+ loadSubTypes().then(
+ function success(subTypes) {
+ var result = $filter('filter')(subTypes, {'$': searchText});
+ if (result && result.length) {
+ deferred.resolve(result);
+ } else {
+ deferred.resolve([searchText]);
+ }
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ return deferred.promise;
+ }
+
+ scope.subTypeSearchTextChanged = function() {
+ }
+
+ scope.updateView = function () {
+ if (!scope.disabled) {
+ ngModelCtrl.$setViewValue(scope.subType);
+ }
+ }
+
+ ngModelCtrl.$render = function () {
+ scope.subType = ngModelCtrl.$viewValue;
+ }
+
+ scope.$watch('entityType', function () {
+ load();
+ });
+
+ scope.$watch('subType', function (newValue, prevValue) {
+ if (!angular.equals(newValue, prevValue)) {
+ scope.updateView();
+ }
+ });
+
+ scope.$watch('disabled', function () {
+ scope.updateView();
+ });
+
+ function loadSubTypes() {
+ var deferred = $q.defer();
+ if (!scope.entitySubtypes) {
+ var entitySubtypesPromise;
+ if (scope.entityType == types.entityType.asset) {
+ entitySubtypesPromise = assetService.getAssetTypes();
+ } else if (scope.entityType == types.entityType.device) {
+ entitySubtypesPromise = deviceService.getDeviceTypes();
+ }
+ if (entitySubtypesPromise) {
+ entitySubtypesPromise.then(
+ function success(types) {
+ scope.entitySubtypes = [];
+ types.forEach(function (type) {
+ scope.entitySubtypes.push(type.type);
+ });
+ deferred.resolve(scope.entitySubtypes);
+ },
+ function fail() {
+ deferred.reject();
+ }
+ );
+ } else {
+ deferred.reject();
+ }
+ } else {
+ deferred.resolve(scope.entitySubtypes);
+ }
+ return deferred.promise;
+ }
+
+ function load() {
+ if (scope.entityType == types.entityType.asset) {
+ scope.selectEntitySubtypeText = 'asset.select-asset-type';
+ scope.entitySubtypeText = 'asset.asset-type';
+ scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+ } else if (scope.entityType == types.entityType.device) {
+ scope.selectEntitySubtypeText = 'device.select-device-type';
+ scope.entitySubtypeText = 'device.device-type';
+ scope.entitySubtypeRequiredText = 'device.device-type-required';
+ scope.$on('deviceSaved', function() {
+ scope.entitySubtypes = null;
+ });
+ }
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ tbRequired: '=?',
+ disabled:'=ngDisabled',
+ entityType: "="
+ }
+ };
+}
diff --git a/ui/src/app/entity/entity-subtype-autocomplete.tpl.html b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html
new file mode 100644
index 0000000..ce220ee
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-autocomplete.tpl.html
@@ -0,0 +1,41 @@
+<!--
+
+ Copyright © 2016-2017 The Thingsboard Authors
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+
+-->
+<md-autocomplete ng-required="tbRequired"
+ ng-disabled="disabled"
+ md-no-cache="true"
+ md-input-name="subType"
+ ng-model="subType"
+ md-selected-item="subType"
+ md-search-text="subTypeSearchText"
+ md-search-text-change="subTypeSearchTextChanged()"
+ md-items="item in fetchSubTypes(subTypeSearchText)"
+ md-item-text="item"
+ md-min-length="0"
+ placeholder="{{ selectEntitySubtypeText | translate }}"
+ md-floating-label="{{ entitySubtypeText | translate }}"
+ md-select-on-match="true"
+ md-menu-class="tb-entity-subtype-autocomplete">
+ <md-item-template>
+ <div class="tb-entity-subtype-item">
+ <span md-highlight-text="subTypeSearchText" md-highlight-flags="^i">{{item}}</span>
+ </div>
+ </md-item-template>
+ <div ng-messages="theForm.subType.$error">
+ <div translate ng-message="required">{{ entitySubtypeRequiredText }}</div>
+ </div>
+</md-autocomplete>
ui/src/app/entity/entity-subtype-select.directive.js 138(+138 -0)
diff --git a/ui/src/app/entity/entity-subtype-select.directive.js b/ui/src/app/entity/entity-subtype-select.directive.js
new file mode 100644
index 0000000..4a2d33e
--- /dev/null
+++ b/ui/src/app/entity/entity-subtype-select.directive.js
@@ -0,0 +1,138 @@
+/*
+ * Copyright © 2016-2017 The Thingsboard Authors
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import './entity-subtype-select.scss';
+
+/* eslint-disable import/no-unresolved, import/default */
+
+import entitySubtypeSelectTemplate from './entity-subtype-select.tpl.html';
+
+/* eslint-enable import/no-unresolved, import/default */
+
+/*@ngInject*/
+export default function EntitySubtypeSelect($compile, $templateCache, $translate, assetService, deviceService, types) {
+
+ var linker = function (scope, element, attrs, ngModelCtrl) {
+ var template = $templateCache.get(entitySubtypeSelectTemplate);
+ element.html(template);
+
+ if (angular.isDefined(attrs.hideLabel)) {
+ scope.showLabel = false;
+ } else {
+ scope.showLabel = true;
+ }
+
+ scope.ngModelCtrl = ngModelCtrl;
+
+ scope.entitySubtypes = [];
+
+ scope.subTypeName = function(subType) {
+ if (subType && subType.length) {
+ if (scope.typeTranslatePrefix) {
+ return $translate.instant(scope.typeTranslatePrefix + '.' + subType);
+ } else {
+ return subType;
+ }
+ } else {
+ return $translate.instant('entity.all-subtypes');
+ }
+ }
+
+ scope.$watch('entityType', function () {
+ load();
+ });
+
+ scope.$watch('entitySubtype', function (newValue, prevValue) {
+ if (!angular.equals(newValue, prevValue)) {
+ scope.updateView();
+ }
+ });
+
+ scope.updateView = function () {
+ ngModelCtrl.$setViewValue(scope.entitySubtype);
+ };
+
+ ngModelCtrl.$render = function () {
+ scope.entitySubtype = ngModelCtrl.$viewValue;
+ };
+
+ function loadSubTypes() {
+ scope.entitySubtypes.length = 0;
+ var entitySubtypesPromise;
+ if (scope.entityType == types.entityType.asset) {
+ entitySubtypesPromise = assetService.getAssetTypes();
+ } else if (scope.entityType == types.entityType.device) {
+ entitySubtypesPromise = deviceService.getDeviceTypes();
+ }
+ if (entitySubtypesPromise) {
+ entitySubtypesPromise.then(
+ function success(types) {
+ scope.entitySubtypes.push('');
+ types.forEach(function(type) {
+ scope.entitySubtypes.push(type.type);
+ });
+ if (scope.entitySubtypes.indexOf(scope.entitySubtype) == -1) {
+ scope.entitySubtype = '';
+ }
+ },
+ function fail() {}
+ );
+ }
+
+ }
+
+ function load() {
+ if (scope.entityType == types.entityType.asset) {
+ scope.entitySubtypeTitle = 'asset.asset-type';
+ scope.entitySubtypeRequiredText = 'asset.asset-type-required';
+ } else if (scope.entityType == types.entityType.device) {
+ scope.entitySubtypeTitle = 'device.device-type';
+ scope.entitySubtypeRequiredText = 'device.device-type-required';
+ }
+ scope.entitySubtypes.length = 0;
+ if (scope.entitySubtypesList && scope.entitySubtypesList.length) {
+ scope.entitySubtypesList.forEach(function(subType) {
+ scope.entitySubtypes.push(subType);
+ });
+ } else {
+ loadSubTypes();
+ if (scope.entityType == types.entityType.asset) {
+ scope.$on('assetSaved', function() {
+ loadSubTypes();
+ });
+ } else if (scope.entityType == types.entityType.device) {
+ scope.$on('deviceSaved', function() {
+ loadSubTypes();
+ });
+ }
+ }
+ }
+
+ $compile(element.contents())(scope);
+ }
+
+ return {
+ restrict: "E",
+ require: "^ngModel",
+ link: linker,
+ scope: {
+ theForm: '=?',
+ entityType: "=",
+ entitySubtypesList: "=?",
+ typeTranslatePrefix: "@?"
+ }
+ };
+}
ui/src/app/entity/index.js 4(+4 -0)
diff --git a/ui/src/app/entity/index.js b/ui/src/app/entity/index.js
index 07c0862..8423f11 100644
--- a/ui/src/app/entity/index.js
+++ b/ui/src/app/entity/index.js
@@ -16,6 +16,8 @@
import EntityAliasesController from './entity-aliases.controller';
import EntityTypeSelectDirective from './entity-type-select.directive';
+import EntitySubtypeSelectDirective from './entity-subtype-select.directive';
+import EntitySubtypeAutocompleteDirective from './entity-subtype-autocomplete.directive';
import EntityFilterDirective from './entity-filter.directive';
import AliasesEntitySelectPanelController from './aliases-entity-select-panel.controller';
import AliasesEntitySelectDirective from './aliases-entity-select.directive';
@@ -29,6 +31,8 @@ export default angular.module('thingsboard.entity', [])
.controller('AddAttributeDialogController', AddAttributeDialogController)
.controller('AddWidgetToDashboardDialogController', AddWidgetToDashboardDialogController)
.directive('tbEntityTypeSelect', EntityTypeSelectDirective)
+ .directive('tbEntitySubtypeSelect', EntitySubtypeSelectDirective)
+ .directive('tbEntitySubtypeAutocomplete', EntitySubtypeAutocompleteDirective)
.directive('tbEntityFilter', EntityFilterDirective)
.directive('tbAliasesEntitySelect', AliasesEntitySelectDirective)
.directive('tbAttributeTable', AttributeTableDirective)
ui/src/app/layout/home.controller.js 37(+34 -3)
diff --git a/ui/src/app/layout/home.controller.js b/ui/src/app/layout/home.controller.js
index 4979501..5091da8 100644
--- a/ui/src/app/layout/home.controller.js
+++ b/ui/src/app/layout/home.controller.js
@@ -25,7 +25,7 @@ import logoSvg from '../../svg/logo_title_white.svg';
/* eslint-disable angular/angularelement */
/*@ngInject*/
-export default function HomeController(loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
+export default function HomeController(types, loginService, userService, deviceService, Fullscreen, $scope, $element, $rootScope, $document, $state,
$window, $log, $mdMedia, $animate, $timeout) {
var siteSideNav = $('.tb-site-sidenav', $element);
@@ -38,8 +38,11 @@ export default function HomeController(loginService, userService, deviceService,
if (angular.isUndefined($rootScope.searchConfig)) {
$rootScope.searchConfig = {
searchEnabled: false,
+ searchByEntitySubtype: false,
+ searchEntityType: null,
showSearch: false,
- searchText: ""
+ searchText: "",
+ searchEntitySubtype: ""
};
}
@@ -47,6 +50,7 @@ export default function HomeController(loginService, userService, deviceService,
vm.isLockSidenav = false;
vm.displaySearchMode = displaySearchMode;
+ vm.displayEntitySubtypeSearch = displayEntitySubtypeSearch;
vm.openSidenav = openSidenav;
vm.goBack = goBack;
vm.searchTextUpdated = searchTextUpdated;
@@ -54,25 +58,35 @@ export default function HomeController(loginService, userService, deviceService,
vm.toggleFullscreen = toggleFullscreen;
$scope.$on('$stateChangeSuccess', function (evt, to, toParams, from) {
+ watchEntitySubtype(false);
if (angular.isDefined(to.data.searchEnabled)) {
$scope.searchConfig.searchEnabled = to.data.searchEnabled;
+ $scope.searchConfig.searchByEntitySubtype = to.data.searchByEntitySubtype;
+ $scope.searchConfig.searchEntityType = to.data.searchEntityType;
if ($scope.searchConfig.searchEnabled === false || to.name !== from.name) {
$scope.searchConfig.showSearch = false;
$scope.searchConfig.searchText = "";
+ $scope.searchConfig.searchEntitySubtype = "";
}
} else {
$scope.searchConfig.searchEnabled = false;
+ $scope.searchConfig.searchByEntitySubtype = false;
+ $scope.searchConfig.searchEntityType = null;
$scope.searchConfig.showSearch = false;
$scope.searchConfig.searchText = "";
+ $scope.searchConfig.searchEntitySubtype = "";
}
+ watchEntitySubtype($scope.searchConfig.searchByEntitySubtype);
});
- if ($mdMedia('gt-sm')) {
+ vm.isGtSm = $mdMedia('gt-sm');
+ if (vm.isGtSm) {
vm.isLockSidenav = true;
$animate.enabled(siteSideNav, false);
}
$scope.$watch(function() { return $mdMedia('gt-sm'); }, function(isGtSm) {
+ vm.isGtSm = isGtSm;
vm.isLockSidenav = isGtSm;
vm.isShowSidenav = isGtSm;
if (!isGtSm) {
@@ -84,11 +98,28 @@ export default function HomeController(loginService, userService, deviceService,
}
});
+ function watchEntitySubtype(enableWatch) {
+ if ($scope.entitySubtypeWatch) {
+ $scope.entitySubtypeWatch();
+ }
+ if (enableWatch) {
+ $scope.entitySubtypeWatch = $scope.$watch('searchConfig.searchEntitySubtype', function (newVal, prevVal) {
+ if (!angular.equals(newVal, prevVal)) {
+ $scope.$broadcast('searchEntitySubtypeUpdated');
+ }
+ });
+ }
+ }
+
function displaySearchMode() {
return $scope.searchConfig.searchEnabled &&
$scope.searchConfig.showSearch;
}
+ function displayEntitySubtypeSearch() {
+ return $scope.searchConfig.searchByEntitySubtype && vm.isGtSm;
+ }
+
function toggleFullscreen() {
if (Fullscreen.isEnabled()) {
Fullscreen.cancel();
ui/src/app/layout/home.scss 8(+8 -0)
diff --git a/ui/src/app/layout/home.scss b/ui/src/app/layout/home.scss
index edcdbc8..a809cf6 100644
--- a/ui/src/app/layout/home.scss
+++ b/ui/src/app/layout/home.scss
@@ -70,3 +70,11 @@ md-icon.tb-logo-title {
z-index: 2;
white-space: nowrap;
}
+
+.tb-entity-subtype-search {
+ margin-top: 15px;
+}
+
+.tb-entity-search {
+ margin-top: 34px;
+}
ui/src/app/layout/home.tpl.html 18(+13 -5)
diff --git a/ui/src/app/layout/home.tpl.html b/ui/src/app/layout/home.tpl.html
index bfb37eb..8e642eb 100644
--- a/ui/src/app/layout/home.tpl.html
+++ b/ui/src/app/layout/home.tpl.html
@@ -39,7 +39,7 @@
</md-sidenav>
<div flex layout="column" tabIndex="-1" role="main">
- <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar" ng-class="{'md-hue-1': vm.displaySearchMode()}">
+ <md-toolbar class="md-whiteframe-z1 tb-primary-toolbar">
<div layout="row" flex class="md-toolbar-tools">
<md-button id="main" hide-gt-sm ng-show="!forceFullscreen"
class="md-icon-button" ng-click="vm.openSidenav()" aria-label="{{ 'home.menu' | translate }}" ng-class="{'tb-invisible': vm.displaySearchMode()}">
@@ -55,10 +55,18 @@
<div flex layout="row" ng-show="!vm.displaySearchMode()" tb-no-animate class="md-toolbar-tools">
<span ng-cloak ncy-breadcrumb></span>
</div>
- <md-input-container ng-show="vm.displaySearchMode()" md-theme="tb-search-input" flex>
- <label> </label>
- <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
- </md-input-container>
+ <div layout="row" ng-show="vm.displaySearchMode()" md-theme="tb-dark" flex>
+ <div class="tb-entity-subtype-search" layout="row" layout-align="start center" ng-if="vm.displayEntitySubtypeSearch()">
+ <tb-entity-subtype-select
+ entity-type="searchConfig.searchEntityType"
+ ng-model="searchConfig.searchEntitySubtype">
+ </tb-entity-subtype-select>
+ </div>
+ <md-input-container ng-class="{'tb-entity-search': vm.displayEntitySubtypeSearch()}" flex>
+ <label> </label>
+ <input ng-model="searchConfig.searchText" ng-change="vm.searchTextUpdated()" placeholder="{{ 'common.enter-search' | translate }}"/>
+ </md-input-container>
+ </div>
<md-button class="md-icon-button" aria-label="{{ 'action.search' | translate }}" ng-show="searchConfig.searchEnabled" ng-click="searchConfig.showSearch = !searchConfig.showSearch">
<md-icon aria-label="{{ 'action.search' | translate }}" class="material-icons">search</md-icon>
</md-button>
diff --git a/ui/src/app/layout/user-menu.directive.js b/ui/src/app/layout/user-menu.directive.js
index 6d09e67..53e8c45 100644
--- a/ui/src/app/layout/user-menu.directive.js
+++ b/ui/src/app/layout/user-menu.directive.js
@@ -48,16 +48,10 @@ function UserMenuController($scope, userService, $translate, $state) {
var dashboardUser = userService.getCurrentUser();
vm.authorityName = authorityName;
- vm.displaySearchMode = displaySearchMode;
vm.logout = logout;
vm.openProfile = openProfile;
vm.userDisplayName = userDisplayName;
- function displaySearchMode() {
- return $scope.searchConfig.searchEnabled &&
- $scope.searchConfig.showSearch;
- }
-
function authorityName() {
var name = "user.anonymous";
if (dashboardUser) {
ui/src/app/locale/locale.constant.js 8(+8 -0)
diff --git a/ui/src/app/locale/locale.constant.js b/ui/src/app/locale/locale.constant.js
index 6e6308d..50f6d8f 100644
--- a/ui/src/app/locale/locale.constant.js
+++ b/ui/src/app/locale/locale.constant.js
@@ -124,6 +124,9 @@ export default angular.module('thingsboard.locale', [])
"unassign-from-customer": "Unassign from customer",
"delete": "Delete asset",
"asset-public": "Asset is public",
+ "asset-type": "Asset type",
+ "asset-type-required": "Asset type is required.",
+ "select-asset-type": "Select asset type",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -328,6 +331,7 @@ export default angular.module('thingsboard.locale', [])
"min-vertical-margin-message": "Only 0 is allowed as minimum vertical margin value.",
"max-vertical-margin-message": "Only 50 is allowed as maximum vertical margin value.",
"display-title": "Display dashboard title",
+ "toolbar-always-open": "Keep toolbar opened",
"title-color": "Title color",
"display-dashboards-selection": "Display dashboards selection",
"display-entities-selection": "Display entities selection",
@@ -476,6 +480,9 @@ export default angular.module('thingsboard.locale', [])
"rsa-key-required": "RSA public key is required.",
"secret": "Secret",
"secret-required": "Secret is required.",
+ "device-type": "Device type",
+ "device-type-required": "Device type is required.",
+ "select-device-type": "Select device type",
"name": "Name",
"name-required": "Name is required.",
"description": "Description",
@@ -520,6 +527,7 @@ export default angular.module('thingsboard.locale', [])
"entity-list-empty": "No entities selected.",
"entity-name-filter-required": "Entity name filter is required.",
"entity-name-filter-no-entity-matched": "No entities starting with '{{entity}}' were found.",
+ "all-subtypes": "All",
"type": "Type",
"type-device": "Device",
"type-asset": "Asset",
ui/src/scss/constants.scss 2(+2 -0)
diff --git a/ui/src/scss/constants.scss b/ui/src/scss/constants.scss
index 73697fc..c56d40b 100644
--- a/ui/src/scss/constants.scss
+++ b/ui/src/scss/constants.scss
@@ -20,10 +20,12 @@
$gray: #eee;
$primary-palette-color: 'indigo';
+$default: '500';
$hue-1: '300';
$hue-2: '800';
$hue-3: 'a100';
+$primary-default: #305680; //material-color($primary-palette-color, $default);
$primary-hue-1: material-color($primary-palette-color, $hue-1);
$primary-hue-2: material-color($primary-palette-color, $hue-2);
$primary-hue-3: rgb(207, 216, 220);