diff --git a/application/pom.xml b/application/pom.xml index c7d3240ef4..e5a4a4e614 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -125,10 +125,6 @@ io.jsonwebtoken jjwt - - joda-time - joda-time - org.apache.velocity velocity diff --git a/application/src/main/data/json/system/widget_bundles/cards.json b/application/src/main/data/json/system/widget_bundles/cards.json index 0fc0085f12..ddb90dbaae 100644 --- a/application/src/main/data/json/system/widget_bundles/cards.json +++ b/application/src/main/data/json/system/widget_bundles/cards.json @@ -112,9 +112,9 @@ "templateHtml": "\n", "templateCss": "", "controllerScript": "self.onInit = function() {\n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get('utils').guid();\n scope.tableId = \"table-\"+id;\n scope.ctx = self.ctx;\n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.$broadcast('timeseries-table-data-updated', self.ctx.$scope.tableId);\n}\n\nself.onDestroy = function() {\n}", - "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\"\n ]\n}", + "settingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n },\n \"displayPagination\": {\n \"title\": \"Display pagination\",\n \"type\": \"boolean\",\n \"default\": true\n }, \n \"defaultPageSize\": {\n \"title\": \"Default page size\",\n \"type\": \"number\",\n \"default\": 10\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\",\n \"displayPagination\",\n \"defaultPageSize\"\n ]\n}", "dataKeySettingsSchema": "{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}", - "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false}" + "defaultConfig": "{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix('blue', 'red', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: '20px',\\n color: '#ffffff',\\n background: color.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor('blue');\\n backgroundColor.setAlpha(value/100);\\n var color = 'blue';\\n if (value > 50) {\\n color = 'white';\\n }\\n \\n return {\\n paddingLeft: '20px',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: '18px'\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true,\"displayPagination\":true,\"defaultPageSize\":10},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false,\"widgetStyle\":{},\"actions\":{}}" } } ] diff --git a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java index a2dd53bd82..d2d9b7ae10 100644 --- a/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java +++ b/application/src/main/java/org/thingsboard/server/actors/plugin/PluginProcessingContext.java @@ -62,6 +62,7 @@ public final class PluginProcessingContext implements PluginContext { private static final Executor executor = Executors.newSingleThreadExecutor(); public static final String CUSTOMER_USER_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "Customer user is not allowed to perform this operation!"; public static final String SYSTEM_ADMINISTRATOR_IS_NOT_ALLOWED_TO_PERFORM_THIS_OPERATION = "System administrator is not allowed to perform this operation!"; + public static final String DEVICE_WITH_REQUESTED_ID_NOT_FOUND = "Device with requested id wasn't found!"; private final SharedPluginProcessingContext pluginCtx; private final Optional securityCtx; @@ -309,7 +310,7 @@ public final class PluginProcessingContext implements PluginContext { ListenableFuture deviceFuture = pluginCtx.deviceService.findDeviceByIdAsync(new DeviceId(entityId.getId())); Futures.addCallback(deviceFuture, getCallback(callback, device -> { if (device == null) { - return ValidationResult.entityNotFound("Device with requested id wasn't found!"); + return ValidationResult.entityNotFound(DEVICE_WITH_REQUESTED_ID_NOT_FOUND); } else { if (!device.getTenantId().equals(ctx.getTenantId())) { return ValidationResult.accessDenied("Device doesn't belong to the current Tenant!"); diff --git a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java index 35d17732fe..9f7c08f511 100644 --- a/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java +++ b/application/src/main/java/org/thingsboard/server/config/WebSocketConfiguration.java @@ -47,8 +47,8 @@ public class WebSocketConfiguration implements WebSocketConfigurer { @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); - container.setMaxTextMessageBufferSize(8192); - container.setMaxBinaryMessageBufferSize(8192); + container.setMaxTextMessageBufferSize(32768); + container.setMaxBinaryMessageBufferSize(32768); return container; } diff --git a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java index c54973f0bc..d87ea5cfe0 100644 --- a/application/src/main/java/org/thingsboard/server/controller/CustomerController.java +++ b/application/src/main/java/org/thingsboard/server/controller/CustomerController.java @@ -82,7 +82,7 @@ public class CustomerController extends BaseController { @PreAuthorize("hasAuthority('TENANT_ADMIN')") @RequestMapping(value = "/customer", method = RequestMethod.POST) - @ResponseBody + @ResponseBody public Customer saveCustomer(@RequestBody Customer customer) throws ThingsboardException { try { customer.setTenantId(getCurrentUser().getTenantId()); @@ -107,7 +107,7 @@ public class CustomerController extends BaseController { } @PreAuthorize("hasAuthority('TENANT_ADMIN')") - @RequestMapping(value = "/customers", params = { "limit" }, method = RequestMethod.GET) + @RequestMapping(value = "/customers", params = {"limit"}, method = RequestMethod.GET) @ResponseBody public TextPageData getCustomers(@RequestParam int limit, @RequestParam(required = false) String textSearch, @@ -122,4 +122,16 @@ public class CustomerController extends BaseController { } } + @PreAuthorize("hasAuthority('TENANT_ADMIN')") + @RequestMapping(value = "/tenant/customers", params = {"customerTitle"}, method = RequestMethod.GET) + @ResponseBody + public Customer getTenantCustomer( + @RequestParam String customerTitle) throws ThingsboardException { + try { + TenantId tenantId = getCurrentUser().getTenantId(); + return checkNotNull(customerService.findCustomerByTenantIdAndTitle(tenantId, customerTitle)); + } catch (Exception e) { + throw handleException(e); + } + } } diff --git a/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java index d1df671972..727fd30f87 100644 --- a/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java +++ b/application/src/main/java/org/thingsboard/server/controller/plugin/PluginWebSocketHandler.java @@ -77,7 +77,6 @@ public class PluginWebSocketHandler extends TextWebSocketHandler implements Plug log.warn("[{}] Failed to find session", session.getId()); session.close(CloseStatus.SERVER_ERROR.withReason("Session not found!")); } - session.sendMessage(message); } catch (IOException e) { log.warn("IO error", e); } diff --git a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java index c069ba637f..a6c4f43e6c 100644 --- a/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java +++ b/application/src/main/java/org/thingsboard/server/service/mail/DefaultMailService.java @@ -96,7 +96,7 @@ public class DefaultMailService implements MailService { javaMailProperties.put(MAIL_PROP + protocol + ".port", jsonConfig.get("smtpPort").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".timeout", jsonConfig.get("timeout").asText()); javaMailProperties.put(MAIL_PROP + protocol + ".auth", String.valueOf(StringUtils.isNotEmpty(jsonConfig.get("username").asText()))); - javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", jsonConfig.get("enableTls")); + javaMailProperties.put(MAIL_PROP + protocol + ".starttls.enable", jsonConfig.has("enableTls") ? jsonConfig.get("enableTls").asText() : "false"); return javaMailProperties; } diff --git a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java index 20de6d8e73..7e5fba5477 100644 --- a/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java +++ b/application/src/main/java/org/thingsboard/server/service/security/model/token/JwtTokenFactory.java @@ -20,7 +20,6 @@ import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.apache.commons.lang3.StringUtils; -import org.joda.time.DateTime; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import org.thingsboard.server.common.data.id.CustomerId; @@ -31,7 +30,9 @@ import org.thingsboard.server.config.JwtSettings; import org.thingsboard.server.service.security.model.SecurityUser; import org.thingsboard.server.service.security.model.UserPrincipal; -import java.util.Arrays; +import java.time.ZonedDateTime; +import java.util.Collections; +import java.util.Date; import java.util.List; import java.util.UUID; import java.util.stream.Collectors; @@ -81,13 +82,13 @@ public class JwtTokenFactory { claims.put(CUSTOMER_ID, securityUser.getCustomerId().getId().toString()); } - DateTime currentTime = new DateTime(); + ZonedDateTime currentTime = ZonedDateTime.now(); String token = Jwts.builder() .setClaims(claims) .setIssuer(settings.getTokenIssuer()) - .setIssuedAt(currentTime.toDate()) - .setExpiration(currentTime.plusSeconds(settings.getTokenExpirationTime()).toDate()) + .setIssuedAt(Date.from(currentTime.toInstant())) + .setExpiration(Date.from(currentTime.plusSeconds(settings.getTokenExpirationTime()).toInstant())) .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()) .compact(); @@ -129,11 +130,11 @@ public class JwtTokenFactory { throw new IllegalArgumentException("Cannot create JWT Token without username/email"); } - DateTime currentTime = new DateTime(); + ZonedDateTime currentTime = ZonedDateTime.now(); UserPrincipal principal = securityUser.getUserPrincipal(); Claims claims = Jwts.claims().setSubject(principal.getValue()); - claims.put(SCOPES, Arrays.asList(Authority.REFRESH_TOKEN.name())); + claims.put(SCOPES, Collections.singletonList(Authority.REFRESH_TOKEN.name())); claims.put(USER_ID, securityUser.getId().getId().toString()); claims.put(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID); @@ -141,8 +142,8 @@ public class JwtTokenFactory { .setClaims(claims) .setIssuer(settings.getTokenIssuer()) .setId(UUID.randomUUID().toString()) - .setIssuedAt(currentTime.toDate()) - .setExpiration(currentTime.plusSeconds(settings.getRefreshTokenExpTime()).toDate()) + .setIssuedAt(Date.from(currentTime.toInstant())) + .setExpiration(Date.from(currentTime.plusSeconds(settings.getRefreshTokenExpTime()).toInstant())) .signWith(SignatureAlgorithm.HS512, settings.getTokenSigningKey()) .compact(); 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 36d736d6e7..2204fff2af 100644 --- a/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java +++ b/application/src/test/java/org/thingsboard/server/controller/AbstractControllerTest.java @@ -107,6 +107,11 @@ public abstract class AbstractControllerTest { protected static final String CUSTOMER_USER_EMAIL = "testcustomer@thingsboard.org"; private static final String CUSTOMER_USER_PASSWORD = "customer"; + /** See {@link org.springframework.test.web.servlet.DefaultMvcResult#getAsyncResult(long)} + * and {@link org.springframework.mock.web.MockAsyncContext#getTimeout()} + */ + private static final long DEFAULT_TIMEOUT = -1L; + protected MediaType contentType = new MediaType(MediaType.APPLICATION_JSON.getType(), MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8")); @@ -366,7 +371,7 @@ public abstract class AbstractControllerTest { } protected T doPost(String urlTemplate, T content, Class responseClass, ResultMatcher resultMatcher, String... params) throws Exception { - return readResponse(doPost(urlTemplate, params).andExpect(resultMatcher), responseClass); + return readResponse(doPost(urlTemplate, content, params).andExpect(resultMatcher), responseClass); } protected T doPost(String urlTemplate, T content, Class responseClass, String... params) throws Exception { @@ -374,7 +379,11 @@ public abstract class AbstractControllerTest { } protected T doPostAsync(String urlTemplate, T content, Class responseClass, ResultMatcher resultMatcher, String... params) throws Exception { - return readResponse(doPostAsync(urlTemplate, content, params).andExpect(resultMatcher), responseClass); + return readResponse(doPostAsync(urlTemplate, content, DEFAULT_TIMEOUT, params).andExpect(resultMatcher), responseClass); + } + + protected T doPostAsync(String urlTemplate, T content, Class responseClass, ResultMatcher resultMatcher, Long timeout, String... params) throws Exception { + return readResponse(doPostAsync(urlTemplate, content, timeout, params).andExpect(resultMatcher), responseClass); } protected T doDelete(String urlTemplate, Class responseClass, String... params) throws Exception { @@ -396,12 +405,13 @@ public abstract class AbstractControllerTest { return mockMvc.perform(postRequest); } - protected ResultActions doPostAsync(String urlTemplate, T content, String... params) throws Exception { + protected ResultActions doPostAsync(String urlTemplate, T content, Long timeout, String... params) throws Exception { MockHttpServletRequestBuilder postRequest = post(urlTemplate); setJwtToken(postRequest); String json = json(content); postRequest.contentType(contentType).content(json); MvcResult result = mockMvc.perform(postRequest).andReturn(); + result.getAsyncResult(timeout); return mockMvc.perform(asyncDispatch(result)); } @@ -414,8 +424,8 @@ public abstract class AbstractControllerTest { protected void populateParams(MockHttpServletRequestBuilder request, String... params) { if (params != null && params.length > 0) { - Assert.assertEquals(params.length % 2, 0); - MultiValueMap paramsMap = new LinkedMultiValueMap(); + Assert.assertEquals(0, params.length % 2); + MultiValueMap paramsMap = new LinkedMultiValueMap<>(); for (int i = 0; i < params.length; i += 2) { paramsMap.add(params[i], params[i + 1]); } diff --git a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java index ac474b818e..8b4332c9f0 100644 --- a/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java +++ b/application/src/test/java/org/thingsboard/server/mqtt/rpc/AbstractMqttServerSideRpcIntegrationTest.java @@ -15,21 +15,23 @@ */ package org.thingsboard.server.mqtt.rpc; +import java.util.Arrays; + +import com.datastax.driver.core.utils.UUIDs; +import com.fasterxml.jackson.core.type.TypeReference; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.eclipse.paho.client.mqttv3.*; import org.junit.*; -import org.springframework.http.HttpStatus; -import org.springframework.web.client.HttpClientErrorException; +import org.thingsboard.server.actors.plugin.PluginProcessingContext; 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.page.TextPageData; +import org.thingsboard.server.common.data.plugin.PluginMetaData; import org.thingsboard.server.common.data.security.Authority; import org.thingsboard.server.common.data.security.DeviceCredentials; import org.thingsboard.server.controller.AbstractControllerTest; -import org.thingsboard.server.dao.service.DaoNoSqlTest; - -import java.util.UUID; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -42,15 +44,19 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractControllerTest { private static final String MQTT_URL = "tcp://localhost:1883"; - private static final String FAIL_MSG_IF_HTTP_CLIENT_ERROR_NOT_ENCOUNTERED = "HttpClientErrorException expected, but not encountered"; + private static final Long TIME_TO_HANDLE_REQUEST = 500L; private Tenant savedTenant; private User tenantAdmin; + private Long asyncContextTimeoutToUseRpcPlugin; + @Before public void beforeTest() throws Exception { loginSysAdmin(); + asyncContextTimeoutToUseRpcPlugin = getAsyncContextTimeoutToUseRpcPlugin(); + Tenant tenant = new Tenant(); tenant.setTitle("My tenant"); savedTenant = doPost("/api/tenant", tenant, Tenant.class); @@ -70,8 +76,7 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC public void afterTest() throws Exception { loginSysAdmin(); if (savedTenant != null) { - doDelete("/api/tenant/" + savedTenant.getId().getId().toString()) - .andExpect(status().isOk()); + doDelete("/api/tenant/" + savedTenant.getId().getId().toString()).andExpect(status().isOk()); } } @@ -102,7 +107,6 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC } @Test - @Ignore // TODO: figure out the right error code for this case. Ignored due to failure: expected 408 but was: 200 public void testServerMqttOneWayRpcDeviceOffline() throws Exception { Device device = new Device(); device.setName("Test One-Way Server-Side RPC Device Offline"); @@ -115,29 +119,19 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - try { - doPost("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().is(408)); - Assert.fail(FAIL_MSG_IF_HTTP_CLIENT_ERROR_NOT_ENCOUNTERED); - } catch (HttpClientErrorException e) { - log.error(e.getMessage(), e); - Assert.assertEquals(HttpStatus.REQUEST_TIMEOUT, e.getStatusCode()); - Assert.assertEquals("408 null", e.getMessage()); - } + + doPostAsync("/api/plugins/rpc/oneway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(), + asyncContextTimeoutToUseRpcPlugin); } @Test - @Ignore // TODO: figure out the right error code for this case. Ignored due to failure: expected 400 (404?) but was: 401 public void testServerMqttOneWayRpcDeviceDoesNotExist() throws Exception { String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; - String nonExistentDeviceId = UUID.randomUUID().toString(); - try { - doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, status().is(400)); - Assert.fail(FAIL_MSG_IF_HTTP_CLIENT_ERROR_NOT_ENCOUNTERED); - } catch (HttpClientErrorException e) { - log.error(e.getMessage(), e); - Assert.assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); - Assert.assertEquals("400 null", e.getMessage()); - } + String nonExistentDeviceId = UUIDs.timeBased().toString(); + + String result = doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, + status().isNotFound()); + Assert.assertEquals(PluginProcessingContext.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); } @Test @@ -168,7 +162,6 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC } @Test - @Ignore // TODO: figure out the right error code for this case. Ignored due to failure: expected 408 but was: 200 public void testServerMqttTwoWayRpcDeviceOffline() throws Exception { Device device = new Device(); device.setName("Test Two-Way Server-Side RPC Device Offline"); @@ -181,29 +174,19 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; String deviceId = savedDevice.getId().getId().toString(); - try { - doPost("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().is(408)); - Assert.fail(FAIL_MSG_IF_HTTP_CLIENT_ERROR_NOT_ENCOUNTERED); - } catch (HttpClientErrorException e) { - log.error(e.getMessage(), e); - Assert.assertEquals(HttpStatus.REQUEST_TIMEOUT, e.getStatusCode()); - Assert.assertEquals("408 null", e.getMessage()); - } + + doPostAsync("/api/plugins/rpc/twoway/" + deviceId, setGpioRequest, String.class, status().isRequestTimeout(), + asyncContextTimeoutToUseRpcPlugin); } @Test - @Ignore // TODO: figure out the right error code for this case. Ignored due to failure: expected 400 (404?) but was: 401 public void testServerMqttTwoWayRpcDeviceDoesNotExist() throws Exception { String setGpioRequest = "{\"method\":\"setGpio\",\"params\":{\"pin\": \"23\",\"value\": 1}}"; - String nonExistentDeviceId = UUID.randomUUID().toString(); - try { - doPostAsync("/api/plugins/rpc/oneway/" + nonExistentDeviceId, setGpioRequest, String.class, status().is(400)); - Assert.fail(FAIL_MSG_IF_HTTP_CLIENT_ERROR_NOT_ENCOUNTERED); - } catch (HttpClientErrorException e) { - log.error(e.getMessage(), e); - Assert.assertEquals(HttpStatus.BAD_REQUEST, e.getStatusCode()); - Assert.assertEquals("400 null", e.getMessage()); - } + String nonExistentDeviceId = UUIDs.timeBased().toString(); + + String result = doPostAsync("/api/plugins/rpc/twoway/" + nonExistentDeviceId, setGpioRequest, String.class, + status().isNotFound()); + Assert.assertEquals(PluginProcessingContext.DEVICE_WITH_REQUESTED_ID_NOT_FOUND, result); } private Device getSavedDevice(Device device) throws Exception { @@ -214,6 +197,13 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC return doGet("/api/device/" + savedDevice.getId().getId().toString() + "/credentials", DeviceCredentials.class); } + private Long getAsyncContextTimeoutToUseRpcPlugin() throws Exception { + TextPageData plugins = doGetTyped("/api/plugin/system?limit=1&textSearch=system rpc plugin", + new TypeReference>(){}); + Long systemRpcPluginTimeout = plugins.getData().iterator().next().getConfiguration().get("defaultTimeout").asLong(); + return systemRpcPluginTimeout + TIME_TO_HANDLE_REQUEST; + } + private static class TestMqttCallback implements MqttCallback { private final MqttAsyncClient client; @@ -228,10 +218,10 @@ public abstract class AbstractMqttServerSideRpcIntegrationTest extends AbstractC @Override public void messageArrived(String requestTopic, MqttMessage mqttMessage) throws Exception { - log.info("Message Arrived: " + mqttMessage.getPayload().toString()); + log.info("Message Arrived: " + Arrays.toString(mqttMessage.getPayload())); MqttMessage message = new MqttMessage(); String responseTopic = requestTopic.replace("request", "response"); - message.setPayload("{\"value1\":\"A\", \"value2\":\"B\"}".getBytes()); + message.setPayload("{\"value1\":\"A\", \"value2\":\"B\"}".getBytes("UTF-8")); client.publish(responseTopic, message); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java index 4600d9f048..1d1abda11f 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerService.java @@ -22,20 +22,24 @@ 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 java.util.Optional; + public interface CustomerService { Customer findCustomerById(CustomerId customerId); + Optional findCustomerByTenantIdAndTitle(TenantId tenantId, String title); + ListenableFuture findCustomerByIdAsync(CustomerId customerId); Customer saveCustomer(Customer customer); - + void deleteCustomer(CustomerId customerId); Customer findOrCreatePublicCustomer(TenantId tenantId); TextPageData findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink); - + void deleteCustomersByTenantId(TenantId tenantId); } diff --git a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java index e8ec21f3c0..f76d654bf9 100644 --- a/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java +++ b/dao/src/main/java/org/thingsboard/server/dao/customer/CustomerServiceImpl.java @@ -52,6 +52,7 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom private static final String PUBLIC_CUSTOMER_TITLE = "Public"; public static final String INCORRECT_CUSTOMER_ID = "Incorrect customerId "; + public static final String INCORRECT_TENANT_ID = "Incorrect tenantId "; @Autowired private CustomerDao customerDao; @@ -78,6 +79,13 @@ public class CustomerServiceImpl extends AbstractEntityService implements Custom return customerDao.findById(customerId.getId()); } + @Override + public Optional findCustomerByTenantIdAndTitle(TenantId tenantId, String title) { + log.trace("Executing findCustomerByTenantIdAndTitle [{}] [{}]", tenantId, title); + validateId(tenantId, INCORRECT_TENANT_ID + tenantId); + return customerDao.findCustomersByTenantIdAndTitle(tenantId.getId(), title); + } + @Override public ListenableFuture findCustomerByIdAsync(CustomerId customerId) { log.trace("Executing findCustomerByIdAsync [{}]", customerId); diff --git a/pom.xml b/pom.xml index e45fc3ea28..4b77abbd98 100755 --- a/pom.xml +++ b/pom.xml @@ -33,8 +33,7 @@ 4.3.4.RELEASE 4.2.0.RELEASE 0.7.0 - 2.4 - 2.2.0 + 2.2.0 4.12 1.7.7 1.2.3 @@ -483,11 +482,6 @@ jjwt ${jjwt.version} - - joda-time - joda-time - ${joda-time.version} - org.apache.velocity velocity diff --git a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java index 7000496cdc..0baf1db508 100644 --- a/tools/src/main/java/org/thingsboard/client/tools/RestClient.java +++ b/tools/src/main/java/org/thingsboard/client/tools/RestClient.java @@ -29,13 +29,12 @@ import org.springframework.web.client.RestTemplate; import org.thingsboard.server.common.data.Customer; import org.thingsboard.server.common.data.Device; import org.thingsboard.server.common.data.alarm.Alarm; -import org.thingsboard.server.common.data.alarm.AlarmSeverity; -import org.thingsboard.server.common.data.alarm.AlarmStatus; import org.thingsboard.server.common.data.asset.Asset; import org.thingsboard.server.common.data.id.AssetId; import org.thingsboard.server.common.data.id.CustomerId; import org.thingsboard.server.common.data.id.DeviceId; import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.relation.EntityRelation; import org.thingsboard.server.common.data.security.DeviceCredentials; import java.io.IOException; @@ -78,6 +77,36 @@ public class RestClient implements ClientHttpRequestInterceptor { } } + public Optional findCustomer(String title) { + Map params = new HashMap(); + params.put("customerTitle", title); + try { + ResponseEntity customerEntity = restTemplate.getForEntity(baseURL + "/api/tenant/customers?customerTitle={customerTitle}", Customer.class, params); + return Optional.of(customerEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional findAsset(String name) { + Map params = new HashMap(); + params.put("assetName", name); + try { + ResponseEntity assetEntity = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, params); + return Optional.of(assetEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + public Customer createCustomer(String title) { Customer customer = new Customer(); customer.setTitle(title); @@ -112,6 +141,14 @@ public class RestClient implements ClientHttpRequestInterceptor { customerId.toString(), assetId.toString()).getBody(); } + public EntityRelation makeRelation(String relationType, EntityId idFrom, EntityId idTo) { + EntityRelation relation = new EntityRelation(); + relation.setFrom(idFrom); + relation.setTo(idTo); + relation.setType(relationType); + return restTemplate.postForEntity(baseURL + "/api/relation", relation, EntityRelation.class).getBody(); + } + public DeviceCredentials getCredentials(DeviceId id) { return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); } diff --git a/tools/src/main/shell/client.keygen.sh b/tools/src/main/shell/client.keygen.sh index 2e83ef0ef0..bb59077ba7 100755 --- a/tools/src/main/shell/client.keygen.sh +++ b/tools/src/main/shell/client.keygen.sh @@ -74,13 +74,13 @@ echo "Generating SSL Key Pair..." keytool -genkeypair -v \ -alias $CLIENT_KEY_ALIAS \ - -dname "CN=$DOMAIN_SUFFIX, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" \ -keystore $CLIENT_FILE_PREFIX.jks \ -keypass $CLIENT_KEY_PASSWORD \ -storepass $CLIENT_KEYSTORE_PASSWORD \ -keyalg RSA \ -keysize 2048 \ - -validity 9999 + -validity 9999 \ + -dname "CN=$DOMAIN_SUFFIX, OU=$ORGANIZATIONAL_UNIT, O=$ORGANIZATION, L=$CITY, ST=$STATE_OR_PROVINCE, C=$TWO_LETTER_COUNTRY_CODE" echo "Converting keystore to pkcs12" keytool -importkeystore \ diff --git a/tools/src/main/shell/keygen.properties b/tools/src/main/shell/keygen.properties index 8dd11f2ea4..a01b782a74 100644 --- a/tools/src/main/shell/keygen.properties +++ b/tools/src/main/shell/keygen.properties @@ -17,7 +17,7 @@ DOMAIN_SUFFIX="$(hostname)" ORGANIZATIONAL_UNIT=Thingsboard ORGANIZATION=Thingsboard -CITY=San Francisco +CITY=SF STATE_OR_PROVINCE=CA TWO_LETTER_COUNTRY_CODE=US diff --git a/ui/src/app/admin/general-settings.tpl.html b/ui/src/app/admin/general-settings.tpl.html index f6286511b6..50342d3afd 100644 --- a/ui/src/app/admin/general-settings.tpl.html +++ b/ui/src/app/admin/general-settings.tpl.html @@ -22,11 +22,11 @@ admin.general-settings - - + +
-
+
@@ -35,7 +35,7 @@
- {{'action.save' | translate}} + {{'action.save' | translate}}
diff --git a/ui/src/app/admin/outgoing-mail-settings.tpl.html b/ui/src/app/admin/outgoing-mail-settings.tpl.html index fd8476a499..3c59593ff7 100644 --- a/ui/src/app/admin/outgoing-mail-settings.tpl.html +++ b/ui/src/app/admin/outgoing-mail-settings.tpl.html @@ -24,11 +24,11 @@
- - + +
-
+
@@ -38,7 +38,7 @@ - + {{smtpProtocol.toUpperCase()}} @@ -78,7 +78,7 @@
admin.timeout-invalid
- {{ 'admin.enable-tls' | translate }} @@ -89,8 +89,8 @@
- {{'admin.send-test-mail' | translate}} - {{'action.save' | translate}} + {{'admin.send-test-mail' | translate}} + {{'action.save' | translate}}
diff --git a/ui/src/app/alarm/alarm-details-dialog.tpl.html b/ui/src/app/alarm/alarm-details-dialog.tpl.html index b090052228..cc213d1178 100644 --- a/ui/src/app/alarm/alarm-details-dialog.tpl.html +++ b/ui/src/app/alarm/alarm-details-dialog.tpl.html @@ -87,7 +87,7 @@ {{ 'alarm.acknowledge' | translate }} @@ -95,12 +95,12 @@ {{ 'alarm.clear' | translate }} - {{ 'action.close' | + {{ 'action.close' | translate }} diff --git a/ui/src/app/alarm/alarm-table.tpl.html b/ui/src/app/alarm/alarm-table.tpl.html index c32e39a2d2..98d42a2a93 100644 --- a/ui/src/app/alarm/alarm-table.tpl.html +++ b/ui/src/app/alarm/alarm-table.tpl.html @@ -19,7 +19,7 @@
- + {{ ('alarm.search-status.' + searchStatus) | translate }} @@ -31,8 +31,8 @@ - + tenantService.getTenant(id, config), entityIds); break; case types.entityType.customer: - promise = getEntitiesByIdsPromise(customerService.getCustomer, entityIds); + promise = getEntitiesByIdsPromise( + (id) => customerService.getCustomer(id, config), entityIds); break; case types.entityType.rule: - promise = getEntitiesByIdsPromise(ruleService.getRule, entityIds); + promise = getEntitiesByIdsPromise( + (id) => ruleService.getRule(id, config), entityIds); break; case types.entityType.plugin: - promise = getEntitiesByIdsPromise(pluginService.getPlugin, entityIds); + promise = getEntitiesByIdsPromise( + (id) => pluginService.getPlugin(id, config), entityIds); break; case types.entityType.dashboard: - promise = getEntitiesByIdsPromise(dashboardService.getDashboardInfo, entityIds); + promise = getEntitiesByIdsPromise( + (id) => dashboardService.getDashboardInfo(id, config), entityIds); break; case types.entityType.user: - promise = getEntitiesByIdsPromise(userService.getUser, entityIds); + promise = getEntitiesByIdsPromise( + (id) => userService.getUser(id, true, config), entityIds); break; case types.entityType.alarm: $log.error('Get Alarm Entity is not implemented!'); @@ -178,11 +184,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } - function getSingleTenantByPageLinkPromise(pageLink) { + function getSingleTenantByPageLinkPromise(pageLink, config) { var user = userService.getCurrentUser(); var tenantId = user.tenantId; var deferred = $q.defer(); - tenantService.getTenant(tenantId).then( + tenantService.getTenant(tenantId, config).then( function success(tenant) { var tenantName = tenant.name; var result = { @@ -202,11 +208,11 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } - function getSingleCustomerByPageLinkPromise(pageLink) { + function getSingleCustomerByPageLinkPromise(pageLink, config) { var user = userService.getCurrentUser(); var customerId = user.customerId; var deferred = $q.defer(); - customerService.getCustomer(customerId).then( + customerService.getCustomer(customerId, config).then( function success(customer) { var customerName = customer.name; var result = { @@ -247,29 +253,29 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device break; case types.entityType.tenant: if (user.authority === 'TENANT_ADMIN') { - promise = getSingleTenantByPageLinkPromise(pageLink); + promise = getSingleTenantByPageLinkPromise(pageLink, config); } else { - promise = tenantService.getTenants(pageLink); + promise = tenantService.getTenants(pageLink, config); } break; case types.entityType.customer: if (user.authority === 'CUSTOMER_USER') { - promise = getSingleCustomerByPageLinkPromise(pageLink); + promise = getSingleCustomerByPageLinkPromise(pageLink, config); } else { - promise = customerService.getCustomers(pageLink); + promise = customerService.getCustomers(pageLink, config); } break; case types.entityType.rule: - promise = ruleService.getAllRules(pageLink); + promise = ruleService.getAllRules(pageLink, config); break; case types.entityType.plugin: - promise = pluginService.getAllPlugins(pageLink); + promise = pluginService.getAllPlugins(pageLink, config); break; case types.entityType.dashboard: if (user.authority === 'CUSTOMER_USER') { - promise = dashboardService.getCustomerDashboards(customerId, pageLink, false); + promise = dashboardService.getCustomerDashboards(customerId, pageLink, false, config); } else { - promise = dashboardService.getTenantDashboards(pageLink, false); + promise = dashboardService.getTenantDashboards(pageLink, false, config); } break; case types.entityType.user: @@ -426,7 +432,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device var stateEntityId = getStateEntityId(filter, stateParams); switch (filter.type) { case types.aliasFilterType.singleEntity.value: - getEntity(filter.singleEntity.entityType, filter.singleEntity.id).then( + getEntity(filter.singleEntity.entityType, filter.singleEntity.id, {ignoreLoading: true}).then( function success(entity) { result.entities = entitiesToEntitiesInfo([entity]); deferred.resolve(result); @@ -437,7 +443,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device ); break; case types.aliasFilterType.entityList.value: - getEntities(filter.entityType, filter.entityList).then( + getEntities(filter.entityType, filter.entityList, {ignoreLoading: true}).then( function success(entities) { if (entities && entities.length || !failOnEmpty) { result.entities = entitiesToEntitiesInfo(entities); @@ -452,7 +458,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device ); break; case types.aliasFilterType.entityName.value: - getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems).then( + getEntitiesByNameFilter(filter.entityType, filter.entityNameFilter, maxItems, {ignoreLoading: true}).then( function success(entities) { if (entities && entities.length || !failOnEmpty) { result.entities = entitiesToEntitiesInfo(entities); @@ -469,7 +475,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device case types.aliasFilterType.stateEntity.value: result.stateEntity = true; if (stateEntityId) { - getEntity(stateEntityId.entityType, stateEntityId.id).then( + getEntity(stateEntityId.entityType, stateEntityId.id, {ignoreLoading: true}).then( function success(entity) { result.entities = entitiesToEntitiesInfo([entity]); deferred.resolve(result); @@ -483,7 +489,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } break; case types.aliasFilterType.assetType.value: - getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, null, filter.assetType).then( + getEntitiesByNameFilter(types.entityType.asset, filter.assetNameFilter, maxItems, {ignoreLoading: true}, filter.assetType).then( function success(entities) { if (entities && entities.length || !failOnEmpty) { result.entities = entitiesToEntitiesInfo(entities); @@ -498,7 +504,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device ); break; case types.aliasFilterType.deviceType.value: - getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, null, filter.deviceType).then( + getEntitiesByNameFilter(types.entityType.device, filter.deviceNameFilter, maxItems, {ignoreLoading: true}, filter.deviceType).then( function success(entities) { if (entities && entities.length || !failOnEmpty) { result.entities = entitiesToEntitiesInfo(entities); @@ -533,7 +539,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device filters: filter.filters }; searchQuery.parameters.maxLevel = filter.maxLevel && filter.maxLevel > 0 ? filter.maxLevel : -1; - entityRelationService.findInfoByQuery(searchQuery).then( + entityRelationService.findInfoByQuery(searchQuery, {ignoreLoading: true}).then( function success(allRelations) { if (allRelations && allRelations.length || !failOnEmpty) { if (angular.isDefined(maxItems) && maxItems > 0 && allRelations) { @@ -577,10 +583,10 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device var findByQueryPromise; if (filter.type == types.aliasFilterType.assetSearchQuery.value) { searchQuery.assetTypes = filter.assetTypes; - findByQueryPromise = assetService.findByQuery(searchQuery, false); + findByQueryPromise = assetService.findByQuery(searchQuery, false, {ignoreLoading: true}); } else if (filter.type == types.aliasFilterType.deviceSearchQuery.value) { searchQuery.deviceTypes = filter.deviceTypes; - findByQueryPromise = deviceService.findByQuery(searchQuery, false); + findByQueryPromise = deviceService.findByQuery(searchQuery, false, {ignoreLoading: true}); } findByQueryPromise.then( function success(entities) { @@ -762,7 +768,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device return deferred.promise; } - function getEntityKeys(entityType, entityId, query, type) { + function getEntityKeys(entityType, entityId, query, type, config) { var deferred = $q.defer(); var url = '/api/plugins/telemetry/' + entityType + '/' + entityId + '/keys/'; if (type === types.dataKeyType.timeseries) { @@ -770,7 +776,7 @@ function EntityService($http, $q, $filter, $translate, $log, userService, device } else if (type === types.dataKeyType.attribute) { url += 'attributes'; } - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { var result = []; if (response.data) { if (query) { diff --git a/ui/src/app/api/plugin.service.js b/ui/src/app/api/plugin.service.js index 55429ce1cf..28f63def4e 100644 --- a/ui/src/app/api/plugin.service.js +++ b/ui/src/app/api/plugin.service.js @@ -50,11 +50,11 @@ function PluginService($http, $q, $rootScope, $filter, componentDescriptorServic tenantPlugins = undefined; } - function loadPluginsCache() { + function loadPluginsCache(config) { var deferred = $q.defer(); if (!allPlugins) { var url = '/api/plugins'; - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { componentDescriptorService.getComponentDescriptorsByType(types.componentType.plugin).then( function success(pluginComponents) { allPlugins = response.data; @@ -93,9 +93,9 @@ function PluginService($http, $q, $rootScope, $filter, componentDescriptorServic return deferred.promise; } - function getSystemPlugins(pageLink) { + function getSystemPlugins(pageLink, config) { var deferred = $q.defer(); - loadPluginsCache().then( + loadPluginsCache(config).then( function success() { utils.filterSearchTextEntities(systemPlugins, 'name', pageLink, deferred); }, @@ -106,9 +106,9 @@ function PluginService($http, $q, $rootScope, $filter, componentDescriptorServic return deferred.promise; } - function getTenantPlugins(pageLink) { + function getTenantPlugins(pageLink, config) { var deferred = $q.defer(); - loadPluginsCache().then( + loadPluginsCache(config).then( function success() { utils.filterSearchTextEntities(tenantPlugins, 'name', pageLink, deferred); }, @@ -119,9 +119,9 @@ function PluginService($http, $q, $rootScope, $filter, componentDescriptorServic return deferred.promise; } - function getAllActionPlugins(pageLink) { + function getAllActionPlugins(pageLink, config) { var deferred = $q.defer(); - loadPluginsCache().then( + loadPluginsCache(config).then( function success() { utils.filterSearchTextEntities(allActionPlugins, 'name', pageLink, deferred); }, @@ -132,9 +132,9 @@ function PluginService($http, $q, $rootScope, $filter, componentDescriptorServic return deferred.promise; } - function getAllPlugins(pageLink) { + function getAllPlugins(pageLink, config) { var deferred = $q.defer(); - loadPluginsCache().then( + loadPluginsCache(config).then( function success() { utils.filterSearchTextEntities(allPlugins, 'name', pageLink, deferred); }, @@ -156,10 +156,10 @@ function PluginService($http, $q, $rootScope, $filter, componentDescriptorServic return deferred.promise; } - function getPlugin(pluginId) { + function getPlugin(pluginId, config) { var deferred = $q.defer(); var url = '/api/plugin/' + pluginId; - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { deferred.resolve(response.data); }, function fail(response) { deferred.reject(response.data); diff --git a/ui/src/app/api/rule.service.js b/ui/src/app/api/rule.service.js index ee166f69ac..df37305ec5 100644 --- a/ui/src/app/api/rule.service.js +++ b/ui/src/app/api/rule.service.js @@ -47,11 +47,11 @@ function RuleService($http, $q, $rootScope, $filter, types, utils) { tenantRules = undefined; } - function loadRulesCache() { + function loadRulesCache(config) { var deferred = $q.defer(); if (!allRules) { var url = '/api/rules'; - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { allRules = response.data; systemRules = []; tenantRules = []; @@ -100,9 +100,9 @@ function RuleService($http, $q, $rootScope, $filter, types, utils) { return deferred.promise; } - function getAllRules(pageLink) { + function getAllRules(pageLink, config) { var deferred = $q.defer(); - loadRulesCache().then( + loadRulesCache(config).then( function success() { utils.filterSearchTextEntities(allRules, 'name', pageLink, deferred); }, @@ -124,10 +124,10 @@ function RuleService($http, $q, $rootScope, $filter, types, utils) { return deferred.promise; } - function getRule(ruleId) { + function getRule(ruleId, config) { var deferred = $q.defer(); var url = '/api/rule/' + ruleId; - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { deferred.resolve(response.data); }, function fail(response) { deferred.reject(response.data); diff --git a/ui/src/app/api/telemetry-websocket.service.js b/ui/src/app/api/telemetry-websocket.service.js index f84d66d3dd..b0e8ea91dd 100644 --- a/ui/src/app/api/telemetry-websocket.service.js +++ b/ui/src/app/api/telemetry-websocket.service.js @@ -23,6 +23,8 @@ export default angular.module('thingsboard.api.telemetryWebsocket', [thingsboard const RECONNECT_INTERVAL = 2000; const WS_IDLE_TIMEOUT = 90000; +const MAX_PUBLISH_COMMANDS = 10; + /*@ngInject*/ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, types, userService) { @@ -75,19 +77,40 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty return service; function publishCommands () { - if (isOpened && (cmdsWrapper.tsSubCmds.length > 0 || - cmdsWrapper.historyCmds.length > 0 || - cmdsWrapper.attrSubCmds.length > 0)) { - dataStream.send(angular.copy(cmdsWrapper)).then(function () { + while(isOpened && hasCommands()) { + dataStream.send(preparePublishCommands()).then(function () { checkToClose(); }); - cmdsWrapper.tsSubCmds = []; - cmdsWrapper.historyCmds = []; - cmdsWrapper.attrSubCmds = []; } tryOpenSocket(); } + function hasCommands() { + return cmdsWrapper.tsSubCmds.length > 0 || + cmdsWrapper.historyCmds.length > 0 || + cmdsWrapper.attrSubCmds.length > 0; + } + + function preparePublishCommands() { + var preparedWrapper = {}; + var leftCount = MAX_PUBLISH_COMMANDS; + preparedWrapper.tsSubCmds = popCmds(cmdsWrapper.tsSubCmds, leftCount); + leftCount -= preparedWrapper.tsSubCmds.length; + preparedWrapper.historyCmds = popCmds(cmdsWrapper.historyCmds, leftCount); + leftCount -= preparedWrapper.historyCmds.length; + preparedWrapper.attrSubCmds = popCmds(cmdsWrapper.attrSubCmds, leftCount); + return preparedWrapper; + } + + function popCmds(cmds, leftCount) { + var toPublish = Math.min(cmds.length, leftCount); + if (toPublish > 0) { + return cmds.splice(0, toPublish); + } else { + return []; + } + } + function onError (/*message*/) { isOpening = false; } diff --git a/ui/src/app/api/tenant.service.js b/ui/src/app/api/tenant.service.js index 594b543e7d..be04ae970d 100644 --- a/ui/src/app/api/tenant.service.js +++ b/ui/src/app/api/tenant.service.js @@ -29,7 +29,7 @@ function TenantService($http, $q) { return service; - function getTenants (pageLink) { + function getTenants (pageLink, config) { var deferred = $q.defer(); var url = '/api/tenants?limit=' + pageLink.limit; if (angular.isDefined(pageLink.textSearch)) { @@ -41,7 +41,7 @@ function TenantService($http, $q) { if (angular.isDefined(pageLink.textOffset)) { url += '&textOffset=' + pageLink.textOffset; } - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { deferred.resolve(response.data); }, function fail() { deferred.reject(); @@ -49,10 +49,10 @@ function TenantService($http, $q) { return deferred.promise; } - function getTenant (tenantId) { + function getTenant (tenantId, config) { var deferred = $q.defer(); var url = '/api/tenant/' + tenantId; - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { deferred.resolve(response.data); }, function fail(response) { deferred.reject(response.data); diff --git a/ui/src/app/api/user.service.js b/ui/src/app/api/user.service.js index d09387bda8..1a146b4e00 100644 --- a/ui/src/app/api/user.service.js +++ b/ui/src/app/api/user.service.js @@ -421,10 +421,14 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, logi return deferred.promise; } - function getUser(userId, ignoreErrors) { + function getUser(userId, ignoreErrors, config) { var deferred = $q.defer(); var url = '/api/user/' + userId; - $http.get(url, { ignoreErrors: ignoreErrors }).then(function success(response) { + if (!config) { + config = {}; + } + config = Object.assign(config, { ignoreErrors: ignoreErrors }); + $http.get(url, config).then(function success(response) { deferred.resolve(response.data); }, function fail() { deferred.reject(); diff --git a/ui/src/app/api/widget.service.js b/ui/src/app/api/widget.service.js index 3c74ecbb4c..fe95729f3b 100644 --- a/ui/src/app/api/widget.service.js +++ b/ui/src/app/api/widget.service.js @@ -298,11 +298,11 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr tenantWidgetsBundles = undefined; } - function loadWidgetsBundleCache() { + function loadWidgetsBundleCache(config) { var deferred = $q.defer(); if (!allWidgetsBundles) { var url = '/api/widgetsBundles'; - $http.get(url, null).then(function success(response) { + $http.get(url, config).then(function success(response) { allWidgetsBundles = response.data; systemWidgetsBundles = []; tenantWidgetsBundles = []; @@ -326,9 +326,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr } - function getSystemWidgetsBundles() { + function getSystemWidgetsBundles(config) { var deferred = $q.defer(); - loadWidgetsBundleCache().then( + loadWidgetsBundleCache(config).then( function success() { deferred.resolve(systemWidgetsBundles); }, @@ -339,9 +339,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr return deferred.promise; } - function getTenantWidgetsBundles() { + function getTenantWidgetsBundles(config) { var deferred = $q.defer(); - loadWidgetsBundleCache().then( + loadWidgetsBundleCache(config).then( function success() { deferred.resolve(tenantWidgetsBundles); }, @@ -352,9 +352,9 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, $tr return deferred.promise; } - function getAllWidgetsBundles() { + function getAllWidgetsBundles(config) { var deferred = $q.defer(); - loadWidgetsBundleCache().then( + loadWidgetsBundleCache(config).then( function success() { deferred.resolve(allWidgetsBundles); }, diff --git a/ui/src/app/asset/add-asset.tpl.html b/ui/src/app/asset/add-asset.tpl.html index ce22e4e674..71901e4b6a 100644 --- a/ui/src/app/asset/add-asset.tpl.html +++ b/ui/src/app/asset/add-asset.tpl.html @@ -27,8 +27,8 @@ - - + +
@@ -36,10 +36,10 @@ - + {{ 'action.add' | translate }} - {{ 'action.cancel' | translate }} + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/asset/add-assets-to-customer.tpl.html b/ui/src/app/asset/add-assets-to-customer.tpl.html index 18e23ce0ca..64edcf817a 100644 --- a/ui/src/app/asset/add-assets-to-customer.tpl.html +++ b/ui/src/app/asset/add-assets-to-customer.tpl.html @@ -26,8 +26,8 @@
- - + +
@@ -65,11 +65,11 @@ - {{ 'action.assign' | translate }} - {{ 'action.cancel' | + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/asset/asset-fieldset.tpl.html b/ui/src/app/asset/asset-fieldset.tpl.html index d921b2e6c4..afa14adc07 100644 --- a/ui/src/app/asset/asset-fieldset.tpl.html +++ b/ui/src/app/asset/asset-fieldset.tpl.html @@ -48,7 +48,7 @@ ng-show="!isEdit && isPublic && (assetScope === 'customer' || assetScope === 'tenant')"> {{ 'asset.asset-public' | translate }}
-
+
@@ -57,7 +57,7 @@ - - + +
@@ -65,10 +65,10 @@ - + {{ 'action.assign' | translate }} - {{ 'action.cancel' | + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/common/utils.service.js b/ui/src/app/common/utils.service.js index 9d997816f6..085a28bad5 100644 --- a/ui/src/app/common/utils.service.js +++ b/ui/src/app/common/utils.service.js @@ -134,6 +134,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t defaultAlarmDataKeys.push(dataKey); } + var imageAspectMap = {}; + var service = { getDefaultDatasource: getDefaultDatasource, generateObjectFromJsonSchema: generateObjectFromJsonSchema, @@ -159,7 +161,8 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t insertVariable: insertVariable, customTranslation: customTranslation, objToBase64: objToBase64, - base64toObj: base64toObj + base64toObj: base64toObj, + loadImageAspect: loadImageAspect } return service; @@ -543,4 +546,34 @@ function Utils($mdColorPalette, $rootScope, $window, $translate, $q, $timeout, t return obj; } + function loadImageAspect(imageUrl) { + var deferred = $q.defer(); + if (imageUrl && imageUrl.length) { + var urlHashCode = hashCode(imageUrl); + var aspect = imageAspectMap[urlHashCode]; + if (angular.isUndefined(aspect)) { + var testImage = document.createElement('img'); // eslint-disable-line + testImage.style.visibility = 'hidden'; + testImage.onload = function() { + aspect = testImage.width / testImage.height; + document.body.removeChild(testImage); //eslint-disable-line + imageAspectMap[urlHashCode] = aspect; + deferred.resolve(aspect); + }; + testImage.onerror = function() { + aspect = 0; + imageAspectMap[urlHashCode] = aspect; + deferred.resolve(aspect); + }; + document.body.appendChild(testImage); //eslint-disable-line + testImage.src = imageUrl; + } else { + deferred.resolve(aspect); + } + } else { + deferred.resolve(0); + } + return deferred.promise; + } + } diff --git a/ui/src/app/component/component-dialog.tpl.html b/ui/src/app/component/component-dialog.tpl.html index 271d818555..841d7ef69a 100644 --- a/ui/src/app/component/component-dialog.tpl.html +++ b/ui/src/app/component/component-dialog.tpl.html @@ -27,11 +27,11 @@
- - + +
-
+
@@ -42,7 +42,7 @@ - + {{componentDescriptor.name}} @@ -57,7 +57,7 @@ @@ -67,11 +67,11 @@ - {{ (vm.isAdd ? 'action.add' : 'action.save') | translate }} - {{ 'action.cancel' | + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/component/component.tpl.html b/ui/src/app/component/component.tpl.html index a9faa7f03f..d007ba6ccd 100644 --- a/ui/src/app/component/component.tpl.html +++ b/ui/src/app/component/component.tpl.html @@ -24,7 +24,7 @@ {{ componentTypeName }}
- @@ -43,7 +43,7 @@ edit - diff --git a/ui/src/app/components/dashboard-autocomplete.directive.js b/ui/src/app/components/dashboard-autocomplete.directive.js index ce7e4770fc..2235b82e95 100644 --- a/ui/src/app/components/dashboard-autocomplete.directive.js +++ b/ui/src/app/components/dashboard-autocomplete.directive.js @@ -48,19 +48,19 @@ function DashboardAutocomplete($compile, $templateCache, $q, dashboardService, u var promise; if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { if (scope.customerId) { - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false); + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true}); } else { promise = $q.when({data: []}); } } else { if (userService.getAuthority() === 'SYS_ADMIN') { if (scope.tenantId) { - promise = dashboardService.getTenantDashboardsByTenantId(scope.tenantId, pageLink); + promise = dashboardService.getTenantDashboardsByTenantId(scope.tenantId, pageLink, {ignoreLoading: true}); } else { promise = $q.when({data: []}); } } else { - promise = dashboardService.getTenantDashboards(pageLink, false); + promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true}); } } diff --git a/ui/src/app/components/dashboard-select.directive.js b/ui/src/app/components/dashboard-select.directive.js index d7b32d8b06..ac5cd3d5b1 100644 --- a/ui/src/app/components/dashboard-select.directive.js +++ b/ui/src/app/components/dashboard-select.directive.js @@ -48,12 +48,12 @@ function DashboardSelect($compile, $templateCache, $q, $mdMedia, $mdPanel, $docu var promise; if (scope.dashboardsScope === 'customer' || userService.getAuthority() === 'CUSTOMER_USER') { if (scope.customerId && scope.customerId != types.id.nullUid) { - promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false); + promise = dashboardService.getCustomerDashboards(scope.customerId, pageLink, false, {ignoreLoading: true}); } else { promise = $q.when({data: []}); } } else { - promise = dashboardService.getTenantDashboards(pageLink, false); + promise = dashboardService.getTenantDashboards(pageLink, false, {ignoreLoading: true}); } promise.then(function success(result) { diff --git a/ui/src/app/components/datakey-config-dialog.controller.js b/ui/src/app/components/datakey-config-dialog.controller.js index ccaac21235..c4e85946f9 100644 --- a/ui/src/app/components/datakey-config-dialog.controller.js +++ b/ui/src/app/components/datakey-config-dialog.controller.js @@ -43,7 +43,7 @@ function DatakeyConfigDialogController($scope, $mdDialog, $q, entityService, dat function success(aliasInfo) { var entity = aliasInfo.currentEntity; if (entity) { - entityService.getEntityKeys(entity.entityType, entity.id, query, type).then( + entityService.getEntityKeys(entity.entityType, entity.id, query, type, {ignoreLoading: true}).then( function success(keys) { deferred.resolve(keys); }, diff --git a/ui/src/app/components/datakey-config-dialog.tpl.html b/ui/src/app/components/datakey-config-dialog.tpl.html index a55c3161c1..574a7af3c8 100644 --- a/ui/src/app/components/datakey-config-dialog.tpl.html +++ b/ui/src/app/components/datakey-config-dialog.tpl.html @@ -26,8 +26,8 @@
- - + + - + {{ 'action.save' | translate }} - {{ 'action.cancel' | translate }} + {{ 'action.cancel' | translate }} diff --git a/ui/src/app/components/details-sidenav.tpl.html b/ui/src/app/components/details-sidenav.tpl.html index 24c22704d7..934aed37b2 100644 --- a/ui/src/app/components/details-sidenav.tpl.html +++ b/ui/src/app/components/details-sidenav.tpl.html @@ -35,7 +35,7 @@
- @@ -44,7 +44,7 @@ - diff --git a/ui/src/app/components/grid.tpl.html b/ui/src/app/components/grid.tpl.html index 24285d898a..b334567e5a 100644 --- a/ui/src/app/components/grid.tpl.html +++ b/ui/src/app/components/grid.tpl.html @@ -45,7 +45,7 @@ - {{ action.details( rowItem[n] ) }} @@ -81,28 +81,28 @@
-