diff --git a/.travis.yml b/.travis.yml index 6f22da9139..f635da5a55 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,9 +4,9 @@ before_install: - export MAVEN_OPTS="-Dmaven.repo.local=$HOME/.m2/repository -Xms1024m -Xmx3072m" - export HTTP_LOG_CONTROLLER_ERROR_STACK_TRACE=false jdk: - - oraclejdk8 + - openjdk8 language: java sudo: required services: - docker -script: mvn clean verify -Ddockerfile.skip=false -DblackBoxTests.skip=false -DblackBoxTests.skipTailChildContainers=true +script: mvn clean verify -Ddockerfile.skip=false diff --git a/application/build.gradle b/application/build.gradle index 1d6017c964..1ea603039a 100644 --- a/application/build.gradle +++ b/application/build.gradle @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/application/pom.xml b/application/pom.xml index 21e9b566ce..e23515f5c9 100644 --- a/application/pom.xml +++ b/application/pom.xml @@ -1,6 +1,6 @@ + + + download-dependencies + + true + true + + @@ -162,9 +173,9 @@ ${spring-boot.version} - org.fortasoft + org.thingsboard gradle-maven-plugin - 1.0.8 + 1.0.9 org.apache.maven.plugins @@ -427,6 +438,12 @@ ${project.version} test + + org.thingsboard + rest-client + ${project.version} + test + org.thingsboard dao diff --git a/rest-client/pom.xml b/rest-client/pom.xml new file mode 100644 index 0000000000..5d931d7afd --- /dev/null +++ b/rest-client/pom.xml @@ -0,0 +1,48 @@ + + + 4.0.0 + + org.thingsboard + 2.5.0-SNAPSHOT + thingsboard + + rest-client + jar + + Thingsboard Rest Client + https://thingsboard.io + + + UTF-8 + ${basedir}/.. + + + + + org.thingsboard.common + data + + + org.springframework + spring-web + + + + diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java new file mode 100644 index 0000000000..7e0dbf3858 --- /dev/null +++ b/rest-client/src/main/java/org/thingsboard/rest/client/RestClient.java @@ -0,0 +1,2076 @@ +/** + * Copyright © 2016-2020 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.rest.client; + +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.node.ObjectNode; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpEntity; +import org.springframework.http.HttpMethod; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.support.HttpRequestWrapper; +import org.springframework.util.StringUtils; +import org.springframework.web.client.HttpClientErrorException; +import org.springframework.web.client.RestTemplate; +import org.thingsboard.rest.client.utils.RestJsonConverter; +import org.thingsboard.server.common.data.AdminSettings; +import org.thingsboard.server.common.data.ClaimRequest; +import org.thingsboard.server.common.data.Customer; +import org.thingsboard.server.common.data.Dashboard; +import org.thingsboard.server.common.data.DashboardInfo; +import org.thingsboard.server.common.data.Device; +import org.thingsboard.server.common.data.EntitySubtype; +import org.thingsboard.server.common.data.EntityView; +import org.thingsboard.server.common.data.Event; +import org.thingsboard.server.common.data.Tenant; +import org.thingsboard.server.common.data.UpdateMessage; +import org.thingsboard.server.common.data.User; +import org.thingsboard.server.common.data.alarm.Alarm; +import org.thingsboard.server.common.data.alarm.AlarmId; +import org.thingsboard.server.common.data.alarm.AlarmInfo; +import org.thingsboard.server.common.data.alarm.AlarmSearchStatus; +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.asset.AssetSearchQuery; +import org.thingsboard.server.common.data.audit.ActionType; +import org.thingsboard.server.common.data.audit.AuditLog; +import org.thingsboard.server.common.data.device.DeviceSearchQuery; +import org.thingsboard.server.common.data.entityview.EntityViewSearchQuery; +import org.thingsboard.server.common.data.id.AssetId; +import org.thingsboard.server.common.data.id.CustomerId; +import org.thingsboard.server.common.data.id.DashboardId; +import org.thingsboard.server.common.data.id.DeviceId; +import org.thingsboard.server.common.data.id.EntityId; +import org.thingsboard.server.common.data.id.EntityViewId; +import org.thingsboard.server.common.data.id.RuleChainId; +import org.thingsboard.server.common.data.id.RuleNodeId; +import org.thingsboard.server.common.data.id.TenantId; +import org.thingsboard.server.common.data.id.UserId; +import org.thingsboard.server.common.data.id.WidgetTypeId; +import org.thingsboard.server.common.data.id.WidgetsBundleId; +import org.thingsboard.server.common.data.kv.Aggregation; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; +import org.thingsboard.server.common.data.page.TextPageData; +import org.thingsboard.server.common.data.page.TextPageLink; +import org.thingsboard.server.common.data.page.TimePageData; +import org.thingsboard.server.common.data.page.TimePageLink; +import org.thingsboard.server.common.data.plugin.ComponentDescriptor; +import org.thingsboard.server.common.data.plugin.ComponentType; +import org.thingsboard.server.common.data.relation.EntityRelation; +import org.thingsboard.server.common.data.relation.EntityRelationInfo; +import org.thingsboard.server.common.data.relation.EntityRelationsQuery; +import org.thingsboard.server.common.data.relation.RelationTypeGroup; +import org.thingsboard.server.common.data.rule.RuleChain; +import org.thingsboard.server.common.data.rule.RuleChainMetaData; +import org.thingsboard.server.common.data.security.DeviceCredentials; +import org.thingsboard.server.common.data.security.DeviceCredentialsType; +import org.thingsboard.server.common.data.security.model.SecuritySettings; +import org.thingsboard.server.common.data.security.model.UserPasswordPolicy; +import org.thingsboard.server.common.data.widget.WidgetType; +import org.thingsboard.server.common.data.widget.WidgetsBundle; + +import java.io.Closeable; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.stream.Collectors; + +import static org.springframework.util.StringUtils.isEmpty; + +/** + * @author Andrew Shvayka + */ +public class RestClient implements ClientHttpRequestInterceptor, Closeable { + private static final String JWT_TOKEN_HEADER_PARAM = "X-Authorization"; + protected final RestTemplate restTemplate; + protected final String baseURL; + private String token; + private String refreshToken; + private final ObjectMapper objectMapper = new ObjectMapper(); + private ExecutorService service = Executors.newWorkStealingPool(10); + + + protected static final String ACTIVATE_TOKEN_REGEX = "/api/noauth/activate?activateToken="; + + public RestClient(String baseURL) { + this(new RestTemplate(), baseURL); + } + + public RestClient(RestTemplate restTemplate, String baseURL) { + this.restTemplate = restTemplate; + this.baseURL = baseURL; + } + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] bytes, ClientHttpRequestExecution execution) throws IOException { + HttpRequest wrapper = new HttpRequestWrapper(request); + wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); + ClientHttpResponse response = execution.execute(wrapper, bytes); + if (response.getStatusCode() == HttpStatus.UNAUTHORIZED) { + synchronized (this) { + restTemplate.getInterceptors().remove(this); + refreshToken(); + wrapper.getHeaders().set(JWT_TOKEN_HEADER_PARAM, "Bearer " + token); + return execution.execute(wrapper, bytes); + } + } + return response; + } + + public RestTemplate getRestTemplate() { + return restTemplate; + } + + public String getToken() { + return token; + } + + public String getRefreshToken() { + return refreshToken; + } + + public void refreshToken() { + Map refreshTokenRequest = new HashMap<>(); + refreshTokenRequest.put("refreshToken", refreshToken); + ResponseEntity tokenInfo = restTemplate.postForEntity(baseURL + "/api/auth/token", refreshTokenRequest, JsonNode.class); + setTokenInfo(tokenInfo.getBody()); + } + + public void login(String username, String password) { + Map loginRequest = new HashMap<>(); + loginRequest.put("username", username); + loginRequest.put("password", password); + ResponseEntity tokenInfo = restTemplate.postForEntity(baseURL + "/api/auth/login", loginRequest, JsonNode.class); + setTokenInfo(tokenInfo.getBody()); + } + + private void setTokenInfo(JsonNode tokenInfo) { + this.token = tokenInfo.get("token").asText(); + this.refreshToken = tokenInfo.get("refreshToken").asText(); + restTemplate.getInterceptors().add(this); + } + + public Optional getAdminSettings(String key) { + try { + ResponseEntity adminSettings = restTemplate.getForEntity(baseURL + "/api/admin/settings/{key}", AdminSettings.class, key); + return Optional.ofNullable(adminSettings.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public AdminSettings saveAdminSettings(AdminSettings adminSettings) { + return restTemplate.postForEntity(baseURL + "/api/admin/settings", adminSettings, AdminSettings.class).getBody(); + } + + public void sendTestMail(AdminSettings adminSettings) { + restTemplate.postForEntity(baseURL + "/api/admin/settings/testMail", adminSettings, AdminSettings.class); + } + + public Optional getSecuritySettings() { + try { + ResponseEntity securitySettings = restTemplate.getForEntity(baseURL + "/api/admin/securitySettings", SecuritySettings.class); + return Optional.ofNullable(securitySettings.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public SecuritySettings saveSecuritySettings(SecuritySettings securitySettings) { + return restTemplate.postForEntity(baseURL + "/api/admin/securitySettings", securitySettings, SecuritySettings.class).getBody(); + } + + public Optional checkUpdates() { + try { + ResponseEntity updateMsg = restTemplate.getForEntity(baseURL + "/api/admin/updates", UpdateMessage.class); + return Optional.ofNullable(updateMsg.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getAlarmById(AlarmId alarmId) { + try { + ResponseEntity alarm = restTemplate.getForEntity(baseURL + "/api/alarm/{alarmId}", Alarm.class, alarmId.getId()); + return Optional.ofNullable(alarm.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getAlarmInfoById(AlarmId alarmId) { + try { + ResponseEntity alarmInfo = restTemplate.getForEntity(baseURL + "/api/alarm/info/{alarmId}", AlarmInfo.class, alarmId.getId()); + return Optional.ofNullable(alarmInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Alarm saveAlarm(Alarm alarm) { + return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); + } + + public void deleteAlarm(AlarmId alarmId) { + restTemplate.delete(baseURL + "/api/alarm/{alarmId}", alarmId.getId()); + } + + public void ackAlarm(AlarmId alarmId) { + restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/ack", null, alarmId.getId()); + } + + public void clearAlarm(AlarmId alarmId) { + restTemplate.postForLocation(baseURL + "/api/alarm/{alarmId}/clear", null, alarmId.getId()); + } + + public TimePageData getAlarms(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status, TimePageLink pageLink, Boolean fetchOriginator) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("searchStatus", searchStatus.name()); + params.put("status", status.name()); + params.put("fetchOriginator", String.valueOf(fetchOriginator)); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/alarm/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}&fetchOriginator={fetchOriginator}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public Optional getHighestAlarmSeverity(EntityId entityId, AlarmSearchStatus searchStatus, AlarmStatus status) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("searchStatus", searchStatus.name()); + params.put("status", status.name()); + try { + ResponseEntity alarmSeverity = restTemplate.getForEntity(baseURL + "/api/alarm/highestSeverity/{entityType}/{entityId}?searchStatus={searchStatus}&status={status}", AlarmSeverity.class, params); + return Optional.ofNullable(alarmSeverity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + @Deprecated + public Alarm createAlarm(Alarm alarm) { + return restTemplate.postForEntity(baseURL + "/api/alarm", alarm, Alarm.class).getBody(); + } + + public Optional getAssetById(AssetId assetId) { + try { + ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/asset/{assetId}", Asset.class, assetId.getId()); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Asset saveAsset(Asset asset) { + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); + } + + public void deleteAsset(AssetId assetId) { + restTemplate.delete(baseURL + "/api/asset/{assetId}", assetId.getId()); + } + + public Optional assignAssetToCustomer(CustomerId customerId, AssetId assetId) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + params.put("assetId", assetId.getId().toString()); + + try { + ResponseEntity asset = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", null, Asset.class, params); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignAssetFromCustomer(AssetId assetId) { + try { + ResponseEntity asset = restTemplate.exchange(baseURL + "/api/customer/asset/{assetId}", HttpMethod.DELETE, HttpEntity.EMPTY, Asset.class, assetId.getId()); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignAssetToPublicCustomer(AssetId assetId) { + try { + ResponseEntity asset = restTemplate.postForEntity(baseURL + "/api/customer/public/asset/{assetId}", null, Asset.class, assetId.getId()); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getTenantAssets(TextPageLink pageLink, String assetType) { + Map params = new HashMap<>(); + params.put("type", assetType); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/api/tenant/assets?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + + public Optional getTenantAsset(String assetName) { + try { + ResponseEntity asset = restTemplate.getForEntity(baseURL + "/api/tenant/assets?assetName={assetName}", Asset.class, assetName); + return Optional.ofNullable(asset.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getCustomerAssets(CustomerId customerId, TextPageLink pageLink, String assetType) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + params.put("type", assetType); + addPageLinkToParam(params, pageLink); + + ResponseEntity> assets = restTemplate.exchange( + baseURL + "/api/customer/{customerId}/assets?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return assets.getBody(); + } + + public List getAssetsByIds(List assetIds) { + return restTemplate.exchange( + baseURL + "/api/assets?assetIds={assetIds}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + listIdsToString(assetIds)) + .getBody(); + } + + public List findByQuery(AssetSearchQuery query) { + return restTemplate.exchange( + URI.create(baseURL + "/api/assets"), + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getAssetTypes() { + return restTemplate.exchange(URI.create( + baseURL + "/api/asset/types"), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + @Deprecated + 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; + } + } + } + + @Deprecated + public Asset createAsset(Asset asset) { + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); + } + + @Deprecated + public Asset createAsset(String name, String type) { + Asset asset = new Asset(); + asset.setName(name); + asset.setType(type); + return restTemplate.postForEntity(baseURL + "/api/asset", asset, Asset.class).getBody(); + } + + @Deprecated + public Asset assignAsset(CustomerId customerId, AssetId assetId) { + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/asset/{assetId}", HttpEntity.EMPTY, Asset.class, + customerId.toString(), assetId.toString()).getBody(); + } + + public TimePageData getAuditLogsByCustomerId(CustomerId customerId, TimePageLink pageLink, List actionTypes) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + params.put("actionTypes", listEnumToString(actionTypes)); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/api/audit/logs/customer/{customerId}?actionTypes={actionTypes}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public TimePageData getAuditLogsByUserId(UserId userId, TimePageLink pageLink, List actionTypes) { + Map params = new HashMap<>(); + params.put("userId", userId.getId().toString()); + params.put("actionTypes", listEnumToString(actionTypes)); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/api/audit/logs/user/{userId}?actionTypes={actionTypes}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public TimePageData getAuditLogsByEntityId(EntityId entityId, List actionTypes, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("actionTypes", listEnumToString(actionTypes)); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/api/audit/logs/entity/{entityType}/{entityId}?actionTypes={actionTypes}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public TimePageData getAuditLogs(TimePageLink pageLink, List actionTypes) { + Map params = new HashMap<>(); + params.put("actionTypes", listEnumToString(actionTypes)); + addPageLinkToParam(params, pageLink); + + ResponseEntity> auditLog = restTemplate.exchange( + baseURL + "/api/audit/logs?actionTypes={actionTypes}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return auditLog.getBody(); + } + + public String getActivateToken(UserId userId) { + String activationLink = getActivationLink(userId); + return activationLink.substring(activationLink.lastIndexOf(ACTIVATE_TOKEN_REGEX) + ACTIVATE_TOKEN_REGEX.length()); + } + + public Optional getUser() { + ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/auth/user", User.class); + return Optional.ofNullable(user.getBody()); + } + + public void logout() { + restTemplate.postForLocation(baseURL + "/api/auth/logout", null); + } + + public void changePassword(String currentPassword, String newPassword) { + ObjectNode changePasswordRequest = objectMapper.createObjectNode(); + changePasswordRequest.put("currentPassword", currentPassword); + changePasswordRequest.put("newPassword", newPassword); + restTemplate.postForLocation(baseURL + "/api/auth/changePassword", changePasswordRequest); + } + + public Optional getUserPasswordPolicy() { + try { + ResponseEntity userPasswordPolicy = restTemplate.getForEntity(baseURL + "/api/noauth/userPasswordPolicy", UserPasswordPolicy.class); + return Optional.ofNullable(userPasswordPolicy.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public ResponseEntity checkActivateToken(UserId userId) { + String activateToken = getActivateToken(userId); + return restTemplate.getForEntity(baseURL + "/api/noauth/activate?activateToken={activateToken}", String.class, activateToken); + } + + public void requestResetPasswordByEmail(String email) { + ObjectNode resetPasswordByEmailRequest = objectMapper.createObjectNode(); + resetPasswordByEmailRequest.put("email", email); + restTemplate.postForLocation(baseURL + "/api/noauth/resetPasswordByEmail", resetPasswordByEmailRequest); + } + + public Optional activateUser(UserId userId, String password) { + ObjectNode activateRequest = objectMapper.createObjectNode(); + activateRequest.put("activateToken", getActivateToken(userId)); + activateRequest.put("password", password); + try { + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/noauth/activate", activateRequest, JsonNode.class); + return Optional.ofNullable(jsonNode.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getComponentDescriptorByClazz(String componentDescriptorClazz) { + try { + ResponseEntity componentDescriptor = restTemplate.getForEntity(baseURL + "/api/component/{componentDescriptorClazz}", ComponentDescriptor.class, componentDescriptorClazz); + return Optional.ofNullable(componentDescriptor.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public List getComponentDescriptorsByType(ComponentType componentType) { + return restTemplate.exchange( + baseURL + "/api/components?componentType={componentType}", + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + componentType).getBody(); + } + + public List getComponentDescriptorsByTypes(List componentTypes) { + return restTemplate.exchange( + baseURL + "/api/components?componentTypes={componentTypes}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + listEnumToString(componentTypes)) + .getBody(); + } + + public Optional getCustomerById(CustomerId customerId) { + try { + ResponseEntity customer = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}", Customer.class, customerId.getId()); + return Optional.ofNullable(customer.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getShortCustomerInfoById(CustomerId customerId) { + try { + ResponseEntity customerInfo = restTemplate.getForEntity(baseURL + "/api/customer/{customerId}/shortInfo", JsonNode.class, customerId.getId()); + return Optional.ofNullable(customerInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public String getCustomerTitleById(CustomerId customerId) { + return restTemplate.getForObject(baseURL + "/api/customer/{customerId}/title", String.class, customerId.getId()); + } + + public Customer saveCustomer(Customer customer) { + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); + } + + public void deleteCustomer(CustomerId customerId) { + restTemplate.delete(baseURL + "/api/customer/{customerId}", customerId.getId()); + } + + public TextPageData getCustomers(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + + ResponseEntity> customer = restTemplate.exchange( + baseURL + "/api/customers?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params); + return customer.getBody(); + } + + public Optional getTenantCustomer(String customerTitle) { + try { + ResponseEntity customer = restTemplate.getForEntity(baseURL + "/api/tenant/customers?customerTitle={customerTitle}", Customer.class, customerTitle); + return Optional.ofNullable(customer.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + @Deprecated + 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; + } + } + } + + @Deprecated + public Customer createCustomer(Customer customer) { + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); + } + + @Deprecated + public Customer createCustomer(String title) { + Customer customer = new Customer(); + customer.setTitle(title); + return restTemplate.postForEntity(baseURL + "/api/customer", customer, Customer.class).getBody(); + } + + public Long getServerTime() { + return restTemplate.getForObject(baseURL + "/api/dashboard/serverTime", Long.class); + } + + public Long getMaxDatapointsLimit() { + return restTemplate.getForObject(baseURL + "/api/dashboard/maxDatapointsLimit", Long.class); + } + + public Optional getDashboardInfoById(DashboardId dashboardId) { + try { + ResponseEntity dashboardInfo = restTemplate.getForEntity(baseURL + "/api/dashboard/info/{dashboardId}", DashboardInfo.class, dashboardId.getId()); + return Optional.ofNullable(dashboardInfo.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getDashboardById(DashboardId dashboardId) { + try { + ResponseEntity dashboard = restTemplate.getForEntity(baseURL + "/api/dashboard/{dashboardId}", Dashboard.class, dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Dashboard saveDashboard(Dashboard dashboard) { + return restTemplate.postForEntity(baseURL + "/api/dashboard", dashboard, Dashboard.class).getBody(); + } + + public void deleteDashboard(DashboardId dashboardId) { + restTemplate.delete(baseURL + "/api/dashboard/{dashboardId}", dashboardId.getId()); + } + + public Optional assignDashboardToCustomer(CustomerId customerId, DashboardId dashboardId) { + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/dashboard/{dashboardId}", null, Dashboard.class, customerId.getId(), dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignDashboardFromCustomer(CustomerId customerId, DashboardId dashboardId) { + try { + ResponseEntity dashboard = restTemplate.exchange(baseURL + "/api/customer/{customerId}/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, customerId.getId(), dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional updateDashboardCustomers(DashboardId dashboardId, List customerIds) { + Object[] customerIdArray = customerIds.stream().map(customerId -> customerId.getId().toString()).toArray(); + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers", customerIdArray, Dashboard.class, dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional addDashboardCustomers(DashboardId dashboardId, List customerIds) { + Object[] customerIdArray = customerIds.stream().map(customerId -> customerId.getId().toString()).toArray(); + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/add", customerIdArray, Dashboard.class, dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional removeDashboardCustomers(DashboardId dashboardId, List customerIds) { + Object[] customerIdArray = customerIds.stream().map(customerId -> customerId.getId().toString()).toArray(); + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/dashboard/{dashboardId}/customers/remove", customerIdArray, Dashboard.class, dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignDashboardToPublicCustomer(DashboardId dashboardId) { + try { + ResponseEntity dashboard = restTemplate.postForEntity(baseURL + "/api/customer/public/dashboard/{dashboardId}", null, Dashboard.class, dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignDashboardFromPublicCustomer(DashboardId dashboardId) { + try { + ResponseEntity dashboard = restTemplate.exchange(baseURL + "/api/customer/public/dashboard/{dashboardId}", HttpMethod.DELETE, HttpEntity.EMPTY, Dashboard.class, dashboardId.getId()); + return Optional.ofNullable(dashboard.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getTenantDashboards(TenantId tenantId, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("tenantId", tenantId.getId().toString()); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/{tenantId}/dashboards?" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public TextPageData getTenantDashboards(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/dashboards?" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public TimePageData getCustomerDashboards(CustomerId customerId, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/dashboards?" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + @Deprecated + public Dashboard createDashboard(Dashboard dashboard) { + return restTemplate.postForEntity(baseURL + "/api/dashboard", dashboard, Dashboard.class).getBody(); + } + + @Deprecated + public List findTenantDashboards() { + try { + ResponseEntity> dashboards = + restTemplate.exchange(baseURL + "/api/tenant/dashboards?limit=100000", HttpMethod.GET, null, new ParameterizedTypeReference>() { + }); + return dashboards.getBody().getData(); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Collections.emptyList(); + } else { + throw exception; + } + } + } + + public Optional getDeviceById(DeviceId deviceId) { + try { + ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}", Device.class, deviceId.getId()); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Device saveDevice(Device device) { + return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); + } + + public void deleteDevice(DeviceId deviceId) { + restTemplate.delete(baseURL + "/api/device/{deviceId}", deviceId.getId()); + } + + public Optional assignDeviceToCustomer(CustomerId customerId, DeviceId deviceId) { + try { + ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, customerId.getId(), deviceId.getId()); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignDeviceFromCustomer(DeviceId deviceId) { + try { + ResponseEntity device = restTemplate.exchange(baseURL + "/api/customer/device/{deviceId}", HttpMethod.DELETE, HttpEntity.EMPTY, Device.class, deviceId.getId()); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignDeviceToPublicCustomer(DeviceId deviceId) { + try { + ResponseEntity device = restTemplate.postForEntity(baseURL + "/api/customer/public/device/{deviceId}", null, Device.class, deviceId.getId()); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getDeviceCredentialsByDeviceId(DeviceId deviceId) { + try { + ResponseEntity deviceCredentials = restTemplate.getForEntity(baseURL + "/api/device/{deviceId}/credentials", DeviceCredentials.class, deviceId.getId()); + return Optional.ofNullable(deviceCredentials.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public DeviceCredentials saveDeviceCredentials(DeviceCredentials deviceCredentials) { + return restTemplate.postForEntity(baseURL + "/api/device/credentials", deviceCredentials, DeviceCredentials.class).getBody(); + } + + public TextPageData getTenantDevices(String type, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", type); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/devices?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public Optional getTenantDevice(String deviceName) { + try { + ResponseEntity device = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, deviceName); + return Optional.ofNullable(device.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getCustomerDevices(CustomerId customerId, String deviceType, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + params.put("type", deviceType); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/devices?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public List getDevicesByIds(List deviceIds) { + return restTemplate.exchange(baseURL + "/api/devices?deviceIds={deviceIds}", + HttpMethod.GET, + HttpEntity.EMPTY, new ParameterizedTypeReference>() { + }, listIdsToString(deviceIds)).getBody(); + } + + public List findByQuery(DeviceSearchQuery query) { + return restTemplate.exchange( + baseURL + "/api/devices", + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getDeviceTypes() { + return restTemplate.exchange( + baseURL + "/api/devices", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public JsonNode claimDevice(String deviceName, ClaimRequest claimRequest) { + return restTemplate.exchange( + baseURL + "/api/customer/device/{deviceName}/claim", + HttpMethod.POST, + new HttpEntity<>(claimRequest), + new ParameterizedTypeReference() { + }, deviceName).getBody(); + } + + public void reClaimDevice(String deviceName) { + restTemplate.delete(baseURL + "/api/customer/device/{deviceName}/claim", deviceName); + } + + @Deprecated + public Device createDevice(String name, String type) { + Device device = new Device(); + device.setName(name); + device.setType(type); + return doCreateDevice(device, null); + } + + @Deprecated + public Device createDevice(Device device) { + return doCreateDevice(device, null); + } + + @Deprecated + public Device createDevice(Device device, String accessToken) { + return doCreateDevice(device, accessToken); + } + + @Deprecated + private Device doCreateDevice(Device device, String accessToken) { + Map params = new HashMap<>(); + String deviceCreationUrl = "/api/device"; + if (!StringUtils.isEmpty(accessToken)) { + deviceCreationUrl = deviceCreationUrl + "?accessToken={accessToken}"; + params.put("accessToken", accessToken); + } + return restTemplate.postForEntity(baseURL + deviceCreationUrl, device, Device.class, params).getBody(); + } + + @Deprecated + public DeviceCredentials getCredentials(DeviceId id) { + return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); + } + + @Deprecated + public Optional findDevice(String name) { + Map params = new HashMap<>(); + params.put("deviceName", name); + try { + ResponseEntity deviceEntity = restTemplate.getForEntity(baseURL + "/api/tenant/devices?deviceName={deviceName}", Device.class, params); + return Optional.of(deviceEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + @Deprecated + public DeviceCredentials updateDeviceCredentials(DeviceId deviceId, String token) { + DeviceCredentials deviceCredentials = getCredentials(deviceId); + deviceCredentials.setCredentialsType(DeviceCredentialsType.ACCESS_TOKEN); + deviceCredentials.setCredentialsId(token); + return saveDeviceCredentials(deviceCredentials); + } + + @Deprecated + public Device assignDevice(CustomerId customerId, DeviceId deviceId) { + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class, + customerId.toString(), deviceId.toString()).getBody(); + } + + public void saveRelation(EntityRelation relation) { + restTemplate.postForLocation(baseURL + "/api/relation", relation); + } + + public void deleteRelation(EntityId fromId, String relationType, RelationTypeGroup relationTypeGroup, EntityId toId) { + Map params = new HashMap<>(); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup.name()); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); + restTemplate.delete(baseURL + "/api/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", params); + } + + public void deleteRelations(EntityId entityId) { + restTemplate.delete(baseURL + "/api/relations?entityId={entityId}&entityType={entityType}", entityId.getId().toString(), entityId.getEntityType().name()); + } + + public Optional getRelation(EntityId fromId, String relationType, RelationTypeGroup relationTypeGroup, EntityId toId) { + Map params = new HashMap<>(); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup.name()); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); + + try { + ResponseEntity entityRelation = restTemplate.getForEntity( + baseURL + "/api/relation?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}&toId={toId}&toType={toType}", + EntityRelation.class, + params); + return Optional.ofNullable(entityRelation.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public List findByFrom(EntityId fromId, RelationTypeGroup relationTypeGroup) { + Map params = new HashMap<>(); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); + + return restTemplate.exchange( + baseURL + "/api/relations?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findInfoByFrom(EntityId fromId, RelationTypeGroup relationTypeGroup) { + Map params = new HashMap<>(); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); + + return restTemplate.exchange( + baseURL + "/api/relations/info?fromId={fromId}&fromType={fromType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByFrom(EntityId fromId, String relationType, RelationTypeGroup relationTypeGroup) { + Map params = new HashMap<>(); + params.put("fromId", fromId.getId().toString()); + params.put("fromType", fromId.getEntityType().name()); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup.name()); + + return restTemplate.exchange( + baseURL + "/api/relations?fromId={fromId}&fromType={fromType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByTo(EntityId toId, RelationTypeGroup relationTypeGroup) { + Map params = new HashMap<>(); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); + + return restTemplate.exchange( + baseURL + "/api/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findInfoByTo(EntityId toId, RelationTypeGroup relationTypeGroup) { + Map params = new HashMap<>(); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); + params.put("relationTypeGroup", relationTypeGroup.name()); + + return restTemplate.exchange( + baseURL + "/api/relations?toId={toId}&toType={toType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByTo(EntityId toId, String relationType, RelationTypeGroup relationTypeGroup) { + Map params = new HashMap<>(); + params.put("toId", toId.getId().toString()); + params.put("toType", toId.getEntityType().name()); + params.put("relationType", relationType); + params.put("relationTypeGroup", relationTypeGroup.name()); + + return restTemplate.exchange( + baseURL + "/api/relations?toId={toId}&toType={toType}&relationType={relationType}&relationTypeGroup={relationTypeGroup}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public List findByQuery(EntityRelationsQuery query) { + return restTemplate.exchange( + baseURL + "/api/relations", + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + public List findInfoByQuery(EntityRelationsQuery query) { + return restTemplate.exchange( + baseURL + "/api/relations", + HttpMethod.POST, + new HttpEntity<>(query), + new ParameterizedTypeReference>() { + }).getBody(); + } + + @Deprecated + 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 Optional getEntityViewById(EntityViewId entityViewId) { + try { + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/entityView/{entityViewId}", EntityView.class, entityViewId.getId()); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public EntityView saveEntityView(EntityView entityView) { + return restTemplate.postForEntity(baseURL + "/api/entityView", entityView, EntityView.class).getBody(); + } + + public void deleteEntityView(EntityViewId entityViewId) { + restTemplate.delete(baseURL + "/api/entityView/{entityViewId}", entityViewId.getId()); + } + + public Optional getTenantEntityView(String entityViewName) { + try { + ResponseEntity entityView = restTemplate.getForEntity(baseURL + "/api/tenant/entityViews?entityViewName={entityViewName}", EntityView.class, entityViewName); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional assignEntityViewToCustomer(CustomerId customerId, EntityViewId entityViewId) { + try { + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/entityView/{entityViewId}", null, EntityView.class, customerId.getId(), entityViewId.getId()); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional unassignEntityViewFromCustomer(EntityViewId entityViewId) { + try { + ResponseEntity entityView = restTemplate.exchange(baseURL + "/api/customer/entityView/{entityViewId}", HttpMethod.DELETE, HttpEntity.EMPTY, EntityView.class, entityViewId.getId()); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TextPageData getCustomerEntityViews(CustomerId customerId, String entityViewType, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + params.put("type", entityViewType); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/entityViews?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public TextPageData getTenantEntityViews(String entityViewType, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("type", entityViewType); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenant/entityViews?type={type}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public List findByQuery(EntityViewSearchQuery query) { + return restTemplate.exchange(baseURL + "/api/entityViews", HttpMethod.POST, new HttpEntity<>(query), new ParameterizedTypeReference>() { + }).getBody(); + } + + public List getEntityViewTypes() { + return restTemplate.exchange(baseURL + "/api/entityView/types", HttpMethod.GET, HttpEntity.EMPTY, new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional assignEntityViewToPublicCustomer(EntityViewId entityViewId) { + try { + ResponseEntity entityView = restTemplate.postForEntity(baseURL + "/api/customer/public/entityView/{entityViewId}", null, EntityView.class, entityViewId.getId()); + return Optional.ofNullable(entityView.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public TimePageData getEvents(EntityId entityId, String eventType, TenantId tenantId, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("eventType", eventType); + params.put("tenantId", tenantId.getId().toString()); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/events/{entityType}/{entityId}/{eventType}?tenantId={tenantId}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public TimePageData getEvents(EntityId entityId, TenantId tenantId, TimePageLink pageLink) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("tenantId", tenantId.getId().toString()); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/events/{entityType}/{entityId}?tenantId={tenantId}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public void handleOneWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { + restTemplate.postForLocation(baseURL + "/api/plugins/rpc/oneway/{deviceId}", requestBody, deviceId.getId()); + } + + public JsonNode handleTwoWayDeviceRPCRequest(DeviceId deviceId, JsonNode requestBody) { + return restTemplate.exchange( + baseURL + "/api/plugins/rpc/twoway/{deviceId}", + HttpMethod.POST, + new HttpEntity<>(requestBody), + new ParameterizedTypeReference() { + }, + deviceId.getId()).getBody(); + } + + public Optional getRuleChainById(RuleChainId ruleChainId) { + try { + ResponseEntity ruleChain = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}", RuleChain.class, ruleChainId.getId()); + return Optional.ofNullable(ruleChain.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional getRuleChainMetaData(RuleChainId ruleChainId) { + try { + ResponseEntity ruleChainMetaData = restTemplate.getForEntity(baseURL + "/api/ruleChain/{ruleChainId}/metadata", RuleChainMetaData.class, ruleChainId.getId()); + return Optional.ofNullable(ruleChainMetaData.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public RuleChain saveRuleChain(RuleChain ruleChain) { + return restTemplate.postForEntity(baseURL + "/api/ruleChain", ruleChain, RuleChain.class).getBody(); + } + + public Optional setRootRuleChain(RuleChainId ruleChainId) { + try { + ResponseEntity ruleChain = restTemplate.postForEntity(baseURL + "/api/ruleChain/{ruleChainId}/root", null, RuleChain.class, ruleChainId.getId()); + return Optional.ofNullable(ruleChain.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public RuleChainMetaData saveRuleChainMetaData(RuleChainMetaData ruleChainMetaData) { + return restTemplate.postForEntity(baseURL + "/api/ruleChain/metadata", ruleChainMetaData, RuleChainMetaData.class).getBody(); + } + + public TextPageData getRuleChains(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/ruleChains?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + params).getBody(); + } + + public void deleteRuleChain(RuleChainId ruleChainId) { + restTemplate.delete(baseURL + "/api/ruleChain/{ruleChainId}", ruleChainId.getId()); + } + + public Optional getLatestRuleNodeDebugInput(RuleNodeId ruleNodeId) { + try { + ResponseEntity jsonNode = restTemplate.getForEntity(baseURL + "/api/ruleNode/{ruleNodeId}/debugIn", JsonNode.class, ruleNodeId.getId()); + return Optional.ofNullable(jsonNode.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Optional testScript(JsonNode inputParams) { + try { + ResponseEntity jsonNode = restTemplate.postForEntity(baseURL + "/api/ruleChain/testScript", inputParams, JsonNode.class); + return Optional.ofNullable(jsonNode.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public List getAttributeKeys(EntityId entityId) { + return restTemplate.exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityId.getEntityType().name(), + entityId.getId().toString()).getBody(); + } + + public List getAttributeKeysByScope(EntityId entityId, String scope) { + return restTemplate.exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/attributes/{scope}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope).getBody(); + } + + public List getAttributeKvEntries(EntityId entityId, List keys) { + List attributes = restTemplate.exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/attributes?keys={keys}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityId.getEntityType().name(), + entityId.getId(), + listToString(keys)).getBody(); + + return RestJsonConverter.toAttributes(attributes); + } + + public Future> getAttributeKvEntriesAsync(EntityId entityId, List keys) { + return service.submit(() -> getAttributeKvEntries(entityId, keys)); + } + + public List getAttributesByScope(EntityId entityId, String scope, List keys) { + List attributes = restTemplate.exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/attributes/{scope}?keys={keys}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope, + listToString(keys)).getBody(); + + return RestJsonConverter.toAttributes(attributes); + } + + public List getTimeseriesKeys(EntityId entityId) { + return restTemplate.exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/keys/timeseries", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + entityId.getEntityType().name(), + entityId.getId().toString()).getBody(); + } + + public List getLatestTimeseries(EntityId entityId, List keys) { + return getLatestTimeseries(entityId, keys, true); + } + + public List getLatestTimeseries(EntityId entityId, List keys, boolean useStrictDataTypes) { + Map> timeseries = restTemplate.exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&useStrictDataTypes={useStrictDataTypes}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>>() { + }, + entityId.getEntityType().name(), + entityId.getId().toString(), + listToString(keys), + useStrictDataTypes).getBody(); + + return RestJsonConverter.toTimeseries(timeseries); + } + + public List getTimeseries(EntityId entityId, List keys, Long interval, Aggregation agg, TimePageLink pageLink) { + return getTimeseries(entityId, keys, interval, agg, pageLink, true); + } + + public List getTimeseries(EntityId entityId, List keys, Long interval, Aggregation agg, TimePageLink pageLink, boolean useStrictDataTypes) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("keys", listToString(keys)); + params.put("interval", interval == null ? "0" : interval.toString()); + params.put("agg", agg == null ? "NONE" : agg.name()); + params.put("useStrictDataTypes", Boolean.toString(useStrictDataTypes)); + addPageLinkToParam(params, pageLink); + + Map> timeseries = restTemplate.exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/values/timeseries?keys={keys}&interval={interval}&agg={agg}&useStrictDataTypes={useStrictDataTypes}&" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>>() { + }, + params).getBody(); + + return RestJsonConverter.toTimeseries(timeseries); + } + + public boolean saveDeviceAttributes(DeviceId deviceId, String scope, JsonNode request) { + return restTemplate + .postForEntity(baseURL + "/api/plugins/telemetry/{deviceId}/{scope}", request, Object.class, deviceId.getId().toString(), scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityAttributesV1(EntityId entityId, String scope, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityAttributesV2(EntityId entityId, String scope, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/attributes/{scope}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityTelemetry(EntityId entityId, String scope, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean saveEntityTelemetryWithTTL(EntityId entityId, String scope, Long ttl, JsonNode request) { + return restTemplate + .postForEntity( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/{scope}/{ttl}", + request, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope, + ttl) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean deleteEntityTimeseries(EntityId entityId, + List keys, + boolean deleteAllDataForKeys, + Long startTs, + Long endTs, + boolean rewriteLatestIfDeleted) { + Map params = new HashMap<>(); + params.put("entityType", entityId.getEntityType().name()); + params.put("entityId", entityId.getId().toString()); + params.put("keys", listToString(keys)); + params.put("deleteAllDataForKeys", String.valueOf(deleteAllDataForKeys)); + params.put("startTs", startTs.toString()); + params.put("endTs", endTs.toString()); + params.put("rewriteLatestIfDeleted", String.valueOf(rewriteLatestIfDeleted)); + + return restTemplate + .exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/timeseries/delete?keys={keys}&deleteAllDataForKeys={deleteAllDataForKeys}&startTs={startTs}&endTs={endTs}&rewriteLatestIfDeleted={rewriteLatestIfDeleted}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + Object.class, + params) + .getStatusCode() + .is2xxSuccessful(); + + } + + public boolean deleteEntityAttributes(DeviceId deviceId, String scope, List keys) { + return restTemplate + .exchange( + baseURL + "/api/plugins/telemetry/{deviceId}/{scope}?keys={keys}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + Object.class, + deviceId.getId().toString(), + scope, + listToString(keys)) + .getStatusCode() + .is2xxSuccessful(); + } + + public boolean deleteEntityAttributes(EntityId entityId, String scope, List keys) { + return restTemplate + .exchange( + baseURL + "/api/plugins/telemetry/{entityType}/{entityId}/{scope}?keys={keys}", + HttpMethod.DELETE, + HttpEntity.EMPTY, + Object.class, + entityId.getEntityType().name(), + entityId.getId().toString(), + scope, + listToString(keys)) + .getStatusCode() + .is2xxSuccessful(); + + } + + public Optional getTenantById(TenantId tenantId) { + try { + ResponseEntity tenant = restTemplate.getForEntity(baseURL + "/api/tenant/{tenantId}", Tenant.class, tenantId.getId()); + return Optional.ofNullable(tenant.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Tenant saveTenant(Tenant tenant) { + return restTemplate.postForEntity(baseURL + "/api/tenant", tenant, Tenant.class).getBody(); + } + + public void deleteTenant(TenantId tenantId) { + restTemplate.delete(baseURL + "/api/tenant/{tenantId}", tenantId.getId()); + } + + public TextPageData getTenants(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/tenants?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public Optional getUserById(UserId userId) { + try { + ResponseEntity user = restTemplate.getForEntity(baseURL + "/api/user/{userId}", User.class, userId.getId()); + return Optional.ofNullable(user.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public Boolean isUserTokenAccessEnabled() { + return restTemplate.getForEntity(baseURL + "/api/user/tokenAccessEnabled", Boolean.class).getBody(); + } + + public Optional getUserToken(UserId userId) { + try { + ResponseEntity userToken = restTemplate.getForEntity(baseURL + "/api/user/{userId}/token", JsonNode.class, userId.getId()); + return Optional.ofNullable(userToken.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public User saveUser(User user, boolean sendActivationMail) { + return restTemplate.postForEntity(baseURL + "/api/user?sendActivationMail={sendActivationMail}", user, User.class, sendActivationMail).getBody(); + } + + public void sendActivationEmail(String email) { + restTemplate.postForLocation(baseURL + "/api/user/sendActivationMail?email={email}", null, email); + } + + public String getActivationLink(UserId userId) { + return restTemplate.getForEntity(baseURL + "/api/user/{userId}/activationLink", String.class, userId.getId()).getBody(); + } + + public void deleteUser(UserId userId) { + restTemplate.delete(baseURL + "/api/user/{userId}", userId.getId()); + } + + public TextPageData getTenantAdmins(TenantId tenantId, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("tenantId", tenantId.getId().toString()); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/tenant/{tenantId}/users?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public TextPageData getCustomerUsers(CustomerId customerId, TextPageLink pageLink) { + Map params = new HashMap<>(); + params.put("customerId", customerId.getId().toString()); + addPageLinkToParam(params, pageLink); + + return restTemplate.exchange( + baseURL + "/api/customer/{customerId}/users?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public void setUserCredentialsEnabled(UserId userId, boolean userCredentialsEnabled) { + restTemplate.postForLocation( + baseURL + "/api/user/{userId}/userCredentialsEnabled?serCredentialsEnabled={serCredentialsEnabled}", + null, + userId.getId(), + userCredentialsEnabled); + } + + public Optional getWidgetsBundleById(WidgetsBundleId widgetsBundleId) { + try { + ResponseEntity widgetsBundle = + restTemplate.getForEntity(baseURL + "/api/widgetsBundle/{widgetsBundleId}", WidgetsBundle.class, widgetsBundleId.getId()); + return Optional.ofNullable(widgetsBundle.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public WidgetsBundle saveWidgetsBundle(WidgetsBundle widgetsBundle) { + return restTemplate.postForEntity(baseURL + "/api/widgetsBundle", widgetsBundle, WidgetsBundle.class).getBody(); + } + + public void deleteWidgetsBundle(WidgetsBundleId widgetsBundleId) { + restTemplate.delete(baseURL + "/api/widgetsBundle/{widgetsBundleId}", widgetsBundleId.getId()); + } + + public TextPageData getWidgetsBundles(TextPageLink pageLink) { + Map params = new HashMap<>(); + addPageLinkToParam(params, pageLink); + return restTemplate.exchange( + baseURL + "/api/widgetsBundles?" + getUrlParams(pageLink), + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, params).getBody(); + } + + public List getWidgetsBundles() { + return restTemplate.exchange( + baseURL + "/api/widgetsBundles", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }).getBody(); + } + + public Optional getWidgetTypeById(WidgetTypeId widgetTypeId) { + try { + ResponseEntity widgetType = + restTemplate.getForEntity(baseURL + "/api/widgetType/{widgetTypeId}", WidgetType.class, widgetTypeId.getId()); + return Optional.ofNullable(widgetType.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + public WidgetType saveWidgetType(WidgetType widgetType) { + return restTemplate.postForEntity(baseURL + "/api/widgetType", widgetType, WidgetType.class).getBody(); + } + + public void deleteWidgetType(WidgetTypeId widgetTypeId) { + restTemplate.delete(baseURL + "/api/widgetType/{widgetTypeId}", widgetTypeId.getId()); + } + + public List getBundleWidgetTypes(boolean isSystem, String bundleAlias) { + return restTemplate.exchange( + baseURL + "/api/widgetTypes?isSystem={isSystem}&bundleAlias={bundleAlias}", + HttpMethod.GET, + HttpEntity.EMPTY, + new ParameterizedTypeReference>() { + }, + isSystem, + bundleAlias).getBody(); + } + + public Optional getWidgetType(boolean isSystem, String bundleAlias, String alias) { + try { + ResponseEntity widgetType = + restTemplate.getForEntity( + baseURL + "/api/widgetType?isSystem={isSystem}&bundleAlias={bundleAlias}&alias={alias}", + WidgetType.class, + isSystem, + bundleAlias, + alias); + return Optional.ofNullable(widgetType.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + @Deprecated + public Optional getAttributes(String accessToken, String clientKeys, String sharedKeys) { + Map params = new HashMap<>(); + params.put("accessToken", accessToken); + params.put("clientKeys", clientKeys); + params.put("sharedKeys", sharedKeys); + try { + ResponseEntity telemetryEntity = restTemplate.getForEntity(baseURL + "/api/v1/{accessToken}/attributes?clientKeys={clientKeys}&sharedKeys={sharedKeys}", JsonNode.class, params); + return Optional.of(telemetryEntity.getBody()); + } catch (HttpClientErrorException exception) { + if (exception.getStatusCode() == HttpStatus.NOT_FOUND) { + return Optional.empty(); + } else { + throw exception; + } + } + } + + private String getUrlParams(TimePageLink pageLink) { + String urlParams = "limit={limit}&ascOrder={ascOrder}"; + if (pageLink.getStartTime() != null) { + urlParams += "&startTime={startTime}"; + } + if (pageLink.getEndTime() != null) { + urlParams += "&endTime={endTime}"; + } + if (pageLink.getIdOffset() != null) { + urlParams += "&offset={offset}"; + } + return urlParams; + } + + private String getUrlParams(TextPageLink pageLink) { + String urlParams = "limit={limit}"; + if (!isEmpty(pageLink.getTextSearch())) { + urlParams += "&textSearch={textSearch}"; + } + if (!isEmpty(pageLink.getIdOffset())) { + urlParams += "&idOffset={idOffset}"; + } + if (!isEmpty(pageLink.getTextOffset())) { + urlParams += "&textOffset={textOffset}"; + } + return urlParams; + } + + private void addPageLinkToParam(Map params, TimePageLink pageLink) { + params.put("limit", String.valueOf(pageLink.getLimit())); + if (pageLink.getStartTime() != null) { + params.put("startTime", String.valueOf(pageLink.getStartTime())); + } + if (pageLink.getEndTime() != null) { + params.put("endTime", String.valueOf(pageLink.getEndTime())); + } + params.put("ascOrder", String.valueOf(pageLink.isAscOrder())); + if (pageLink.getIdOffset() != null) { + params.put("offset", pageLink.getIdOffset().toString()); + } + } + + private void addPageLinkToParam(Map params, TextPageLink pageLink) { + params.put("limit", String.valueOf(pageLink.getLimit())); + if (pageLink.getTextSearch() != null) { + params.put("textSearch", pageLink.getTextSearch()); + } + + if (pageLink.getIdOffset() != null) { + params.put("idOffset", pageLink.getIdOffset().toString()); + + } + if (pageLink.getTextOffset() != null) { + params.put("textOffset", pageLink.getTextOffset()); + } + } + + private String listToString(List list) { + return String.join(",", list); + } + + private String listIdsToString(List list) { + return listToString(list.stream().map(id -> id.getId().toString()).collect(Collectors.toList())); + } + + private String listEnumToString(List list) { + return listToString(list.stream().map(Enum::name).collect(Collectors.toList())); + } + + @Override + public void close() { + if (service != null) { + service.shutdown(); + } + } + +} diff --git a/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java b/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java new file mode 100644 index 0000000000..65838fb5bd --- /dev/null +++ b/rest-client/src/main/java/org/thingsboard/rest/client/utils/RestJsonConverter.java @@ -0,0 +1,101 @@ +/** + * Copyright © 2016-2020 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.rest.client.utils; + +import com.fasterxml.jackson.databind.JsonNode; +import org.springframework.util.CollectionUtils; +import org.thingsboard.server.common.data.kv.AttributeKvEntry; +import org.thingsboard.server.common.data.kv.BaseAttributeKvEntry; +import org.thingsboard.server.common.data.kv.BasicTsKvEntry; +import org.thingsboard.server.common.data.kv.BooleanDataEntry; +import org.thingsboard.server.common.data.kv.DoubleDataEntry; +import org.thingsboard.server.common.data.kv.JsonDataEntry; +import org.thingsboard.server.common.data.kv.KvEntry; +import org.thingsboard.server.common.data.kv.LongDataEntry; +import org.thingsboard.server.common.data.kv.StringDataEntry; +import org.thingsboard.server.common.data.kv.TsKvEntry; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class RestJsonConverter { + private static final String KEY = "key"; + private static final String VALUE = "value"; + private static final String LAST_UPDATE_TS = "lastUpdateTs"; + private static final String TS = "ts"; + + private static final String CAN_T_PARSE_VALUE = "Can't parse value: "; + + public static List toAttributes(List attributes) { + if (!CollectionUtils.isEmpty(attributes)) { + return attributes.stream().map(attr -> { + KvEntry entry = parseValue(attr.get(KEY).asText(), attr.get(VALUE)); + return new BaseAttributeKvEntry(entry, attr.get(LAST_UPDATE_TS).asLong()); + } + ).collect(Collectors.toList()); + } else { + return Collections.emptyList(); + } + } + + public static List toTimeseries(Map> timeseries) { + if (!CollectionUtils.isEmpty(timeseries)) { + List result = new ArrayList<>(); + timeseries.forEach((key, values) -> + result.addAll(values.stream().map(ts -> { + KvEntry entry = parseValue(key, ts.get(VALUE)); + return new BasicTsKvEntry(ts.get(TS).asLong(), entry); + } + ).collect(Collectors.toList())) + ); + return result; + } else { + return Collections.emptyList(); + } + } + + private static KvEntry parseValue(String key, JsonNode value) { + if (!value.isObject()) { + if (value.isBoolean()) { + return new BooleanDataEntry(key, value.asBoolean()); + } else if (value.isNumber()) { + return parseNumericValue(key, value); + } else if (value.isTextual()) { + return new StringDataEntry(key, value.asText()); + } else { + throw new RuntimeException(CAN_T_PARSE_VALUE + value); + } + } else { + return new JsonDataEntry(key, value.toString()); + } + } + + private static KvEntry parseNumericValue(String key, JsonNode value) { + if (value.isFloatingPointNumber()) { + return new DoubleDataEntry(key, value.asDouble()); + } else { + try { + long longValue = Long.parseLong(value.toString()); + return new LongDataEntry(key, longValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("Big integer values are not supported!"); + } + } + } +} diff --git a/rule-engine/pom.xml b/rule-engine/pom.xml index 1fe931962e..8cfefd546a 100644 --- a/rule-engine/pom.xml +++ b/rule-engine/pom.xml @@ -1,6 +1,6 @@ - - \ No newline at end of file + diff --git a/ui/src/app/components/datasource-entity.directive.js b/ui/src/app/components/datasource-entity.directive.js index afde04e2d2..edc891b2c8 100644 --- a/ui/src/app/components/datasource-entity.directive.js +++ b/ui/src/app/components/datasource-entity.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. @@ -124,7 +124,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc var alarmDataKeys = []; for (var d in ngModelCtrl.$viewValue.dataKeys) { var dataKey = ngModelCtrl.$viewValue.dataKeys[d]; - if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute)) { + if ((dataKey.type === types.dataKeyType.timeseries) || (dataKey.type === types.dataKeyType.attribute) || (dataKey.type === types.dataKeyType.entityField)) { dataKeys.push(dataKey); } else if (dataKey.type === types.dataKeyType.alarm) { alarmDataKeys.push(dataKey); @@ -165,7 +165,11 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc }; scope.transformAlarmDataKeyChip = function (chip) { - return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); + if (chip.type) { + return scope.generateDataKey({chip: chip.name, type: chip.type}); + } else { + return scope.generateDataKey({chip: chip, type: types.dataKeyType.alarm}); + } }; scope.showColorPicker = function (event, dataKey) { @@ -215,7 +219,7 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc w.triggerHandler('resize'); } }).then(function (newDataKey) { - if ((newDataKey.type === types.dataKeyType.timeseries) || (newDataKey.type === types.dataKeyType.attribute)) { + if ((newDataKey.type === types.dataKeyType.timeseries) || (newDataKey.type === types.dataKeyType.attribute) || (newDataKey.type === types.dataKeyType.entityField)) { let index = scope.dataKeys.indexOf(dataKey); scope.dataKeys[index] = newDataKey; } else if (newDataKey.type === types.dataKeyType.alarm) { @@ -242,10 +246,16 @@ function DatasourceEntity($compile, $templateCache, $q, $mdDialog, $window, $doc items.push({ name: dataKeys[i], type: types.dataKeyType.timeseries }); } if (scope.widgetType == types.widgetType.latest.value) { - scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: types.dataKeyType.attribute}) - .then(function (dataKeys) { + var keysType = [types.dataKeyType.attribute, types.dataKeyType.entityField]; + var promises = []; + keysType.forEach((type) => { + promises.push(scope.fetchEntityKeys({entityAliasId: scope.entityAlias.id, query: searchText, type: type})); + }); + $q.all(promises).then(function (dataKeys) { for (var i = 0; i < dataKeys.length; i++) { - items.push({ name: dataKeys[i], type: types.dataKeyType.attribute }); + for (var j = 0; j < dataKeys[i].length; j++) { + items.push({name: dataKeys[i][j], type: keysType[i]}); + } } deferred.resolve(items); }, function (e) { diff --git a/ui/src/app/components/datasource-entity.scss b/ui/src/app/components/datasource-entity.scss index 086f6f691f..41465df44d 100644 --- a/ui/src/app/components/datasource-entity.scss +++ b/ui/src/app/components/datasource-entity.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/datasource-entity.tpl.html b/ui/src/app/components/datasource-entity.tpl.html index 9a58ecdb66..645e326360 100644 --- a/ui/src/app/components/datasource-entity.tpl.html +++ b/ui/src/app/components/datasource-entity.tpl.html @@ -1,6 +1,6 @@ + +
+ +
+

+ gateway.title-connectors-json +

+ + + + +
+
+ +
+
+
+ + + + {{'gateway.tidy'|translate}} + {{'gateway.tidy-tip' | translate }} + + +
+
+
+
+
+ +
+
+
+ + + {{'action.save'|translate}} + + + {{'action.cancel'|translate }} + + +
+
diff --git a/ui/src/app/components/gateway/gateway-config-select.directive.js b/ui/src/app/components/gateway/gateway-config-select.directive.js new file mode 100644 index 0000000000..79ff454475 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config-select.directive.js @@ -0,0 +1,141 @@ +/* + * Copyright © 2016-2020 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 './gateway-config-select.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import gatewaySelectTemplate from './gateway-config-select.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + + +/* eslint-disable angular/angularelement */ + +export default angular.module('thingsboard.directives.gatewayConfigSelect', []) + .directive('tbGatewayConfigSelect', GatewayConfigSelect) + .name; + +/*@ngInject*/ +function GatewayConfigSelect($compile, $templateCache, $mdConstant, $translate, $mdDialog) { + + var linker = function (scope, element, attrs, ngModelCtrl) { + const template = $templateCache.get(gatewaySelectTemplate); + element.html(template); + + scope.tbRequired = angular.isDefined(scope.tbRequired) ? scope.tbRequired : false; + scope.gateway = null; + scope.gatewaySearchText = ''; + + scope.updateValidity = function () { + var value = ngModelCtrl.$viewValue; + var valid = angular.isDefined(value) && value != null || !scope.tbRequired; + ngModelCtrl.$setValidity('gateway', valid); + }; + + function startWatchers() { + scope.$watch('gateway', function (newVal, prevVal) { + if (!angular.equals(newVal, prevVal) && newVal !== null) { + scope.updateView(); + } + }); + } + + scope.gatewayNameSearch = function (gatewaySearchText) { + return gatewaySearchText ? scope.gatewayList.filter( + scope.createFilterForGatewayName(gatewaySearchText)) : scope.gatewayList; + }; + + scope.createFilterForGatewayName = function (query) { + var lowercaseQuery = query.toLowerCase(); + return function filterFn(device) { + return (device.name.toLowerCase().indexOf(lowercaseQuery) === 0); + }; + }; + + scope.updateView = function () { + ngModelCtrl.$setViewValue(scope.gateway); + scope.updateValidity(); + scope.getAccessToken(scope.gateway.id); + }; + + ngModelCtrl.$render = function () { + if (ngModelCtrl.$viewValue) { + scope.gateway = ngModelCtrl.$viewValue; + startWatchers(); + } + }; + + scope.textIsEmpty = function (str) { + return (!str || 0 === str.length); + }; + + scope.gatewayNameEnter = function ($event) { + if ($event.keyCode === $mdConstant.KEY_CODE.ENTER) { + $event.preventDefault(); + let indexRes = scope.gatewayList.findIndex((element) => element.key === scope.gatewaySearchText); + if (indexRes === -1) { + scope.createNewGatewayDialog($event, scope.gatewaySearchText); + } + } + }; + + scope.createNewGatewayDialog = function ($event, deviceName) { + if ($event) { + $event.stopPropagation(); + } + var title = $translate.instant('gateway.create-new-gateway'); + var content = $translate.instant('gateway.create-new-gateway-text', {gatewayName: deviceName}); + var confirm = $mdDialog.confirm() + .targetEvent($event) + .title(title) + .htmlContent(content) + .ariaLabel(title) + .cancel($translate.instant('action.no')) + .ok($translate.instant('action.yes')); + $mdDialog.show(confirm).then( + () => { + let deviceObj = { + name: deviceName, + type: "Gateway", + additionalInfo: { + gateway: true + } + }; + scope.createDevice(deviceObj); + }, + () => { + scope.gatewaySearchText = ""; + } + ); + }; + $compile(element.contents())(scope); + }; + + return { + restrict: "E", + require: "^ngModel", + link: linker, + scope: { + tbRequired: '=?', + gatewayList: '=?', + getAccessToken: '=', + createDevice: '=', + theForm: '=' + } + }; +} + +/* eslint-enable angular/angularelement */ diff --git a/ui/src/app/components/gateway/gateway-config-select.scss b/ui/src/app/components/gateway/gateway-config-select.scss new file mode 100644 index 0000000000..1c189a7279 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config-select.scss @@ -0,0 +1,35 @@ +/** + * Copyright © 2016-2020 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. + */ +.tb-gateway-autocomplete { + .tb-not-found { + line-height: 1.5; + white-space: normal; + + .tb-no-gateway { + line-height: 48px; + } + } +} + +.tb-gateway-autocomplete-container.md-virtual-repeat-container.md-autocomplete-suggestions-container{ + z-index: 70; +} + +md-autocomplete{ + md-input-container{ + margin-bottom: 0; + } +} diff --git a/ui/src/app/components/gateway/gateway-config-select.tpl.html b/ui/src/app/components/gateway/gateway-config-select.tpl.html new file mode 100644 index 0000000000..9a7f6ed655 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config-select.tpl.html @@ -0,0 +1,53 @@ + +
+ + + {{item.name}} + + +
+
+ gateway.no-gateway-found +
+
+ gateway.no-gateway-matching + gateway.create-new-gateway +
+
+
+
+
gateway.gateway-name-required
+
+
+
diff --git a/ui/src/app/components/gateway/gateway-config.directive.js b/ui/src/app/components/gateway/gateway-config.directive.js new file mode 100644 index 0000000000..120a6d3e15 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config.directive.js @@ -0,0 +1,170 @@ +/* + * Copyright © 2016-2020 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 './gateway-config.scss'; + +/* eslint-disable import/no-unresolved, import/default */ + +import gatewayConfigTemplate from './gateway-config.tpl.html'; +import gatewayConfigDialogTemplate from './gateway-config-dialog.tpl.html'; +import beautify from "js-beautify"; + +/* eslint-enable import/no-unresolved, import/default */ +const js_beautify = beautify.js; + +export default angular.module('thingsboard.directives.gatewayConfig', []) + .directive('tbGatewayConfig', GatewayConfig) + .name; + +/*@ngInject*/ +function GatewayConfig() { + return { + restrict: "E", + scope: true, + bindToController: { + disabled: '=ngDisabled', + gatewayConfig: '=', + changeAlignment: '=', + theForm: '=' + }, + controller: GatewayConfigController, + controllerAs: 'vm', + templateUrl: gatewayConfigTemplate + }; +} + +/*@ngInject*/ +function GatewayConfigController($scope, $document, $mdDialog, $mdUtil, $window, types) { + let vm = this; + vm.types = types; + + vm.removeConnector = (index) => { + if (index > -1) { + vm.gatewayConfig.splice(index, 1); + } + }; + + vm.addNewConnector = () => { + vm.gatewayConfig.push({ + enabled: false, + configType: '', + config: {}, + name: '' + }); + }; + + vm.openConfigDialog = ($event, index, config, typeName) => { + if ($event) { + $event.stopPropagation(); + } + $mdDialog.show({ + controller: GatewayDialogController, + controllerAs: 'vm', + templateUrl: gatewayConfigDialogTemplate, + parent: angular.element($document[0].body), + locals: { + config: config, + typeName: typeName + }, + targetEvent: $event, + fullscreen: true, + multiple: true, + }).then(function (config) { + if (config && index > -1) { + vm.gatewayConfig[index].config = config; + } + }); + + }; + + vm.changeConnectorType = (connector) => { + for (let gatewayConfigTypeKey in types.gatewayConfigType) { + if (types.gatewayConfigType[gatewayConfigTypeKey].value === connector.configType) { + if (!connector.name) { + connector.name = generateConnectorName(types.gatewayConfigType[gatewayConfigTypeKey].name, 0); + break; + } + } + } + }; + + vm.changeConnectorName = (connector, currentConnectorIndex) => { + connector.name = validateConnectorName(connector.name, 0, currentConnectorIndex); + }; + + function generateConnectorName(name, index) { + let newKeyName = index ? name + index : name; + let indexRes = vm.gatewayConfig.findIndex((element) => element.name === newKeyName); + return indexRes === -1 ? newKeyName : generateConnectorName(name, ++index); + } + + function validateConnectorName(name, index, currentConnectorIndex) { + for (let i = 0; i < vm.gatewayConfig.length; i++) { + let nameEq = (index === 0) ? name : name + index; + if (i !== currentConnectorIndex && vm.gatewayConfig[i].name === nameEq) { + index++; + validateConnectorName(name, index, currentConnectorIndex); + } + } + return (index === 0) ? name : name + index; + } + + vm.validateJSON = (config) => { + return angular.equals({}, config); + }; +} + +/*@ngInject*/ +function GatewayDialogController($scope, $mdDialog, $document, $window, config, typeName) { + let vm = this; + vm.config = js_beautify(angular.toJson(config), {indent_size: 4}); + vm.typeName = typeName; + vm.configAreaOptions = { + useWrapMode: true, + mode: 'json', + advanced: { + enableSnippets: true, + enableBasicAutocompletion: true, + enableLiveAutocompletion: true + }, + onLoad: function (_ace) { + _ace.$blockScrolling = 1; + } + }; + + vm.validateConfig = (model, editorName) => { + if (model && model.length) { + try { + angular.fromJson(model); + $scope.theForm[editorName].$setValidity('config', true); + } catch (e) { + $scope.theForm[editorName].$setValidity('config', false); + } + } + }; + + vm.save = () => { + $mdDialog.hide(angular.fromJson(vm.config)); + }; + + vm.cancel = () => { + $mdDialog.hide(); + }; + + vm.beautifyJson = () => { + vm.config = js_beautify(vm.config, {indent_size: 4}); + }; +} + diff --git a/ui/src/app/components/gateway/gateway-config.scss b/ui/src/app/components/gateway/gateway-config.scss new file mode 100644 index 0000000000..45d9532927 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config.scss @@ -0,0 +1,85 @@ +/** + * Copyright © 2016-2020 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. + */ +.gateway-config { + span.no-data-found { + position: relative; + display: flex; + height: 40px; + text-transform: uppercase; + + &.disabled { + color: rgba(0, 0, 0, .38); + } + } + + .gateway-config-row{ + md-input-container{ + margin-bottom: 0; + } + + &.gateway-config-row-vertical { + flex-direction: column; + } + } + + .action-buttons.gateway-config-row-vertical { + flex-direction: column; + justify-content: space-evenly; + } +} + +.gateway-config-dialog{ + .md-button.tidy{ + min-width: 32px; + min-height: 15px; + padding: 4px; + margin: 0 5px 0 0; + font-size: .8rem; + line-height: 15px; + color: #7b7b7b; + background: rgba(220, 220, 220, .35); + } + + .tb-json-toolbar{ + height: 40px; + } +} + +.tb-json-panel { + height: calc(100% - 80px); + margin-left: 15px; + border: 1px solid #c0c0c0; + + .tb-json-input { + width: 100%; + min-width: 400px; + height: 100%; + + &:not(.fill-height) { + min-height: 200px; + } + } +} + +@media (max-width: 425px){ + .gateway-config-dialog{ + .tb-json-panel { + .tb-json-input { + min-width: 200px; + } + } + } +} diff --git a/ui/src/app/components/gateway/gateway-config.tpl.html b/ui/src/app/components/gateway/gateway-config.tpl.html new file mode 100644 index 0000000000..eef7c737e4 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-config.tpl.html @@ -0,0 +1,81 @@ + +
+
+
+ + +
+
+ + + + + {{configType.value}} + + +
+
gateway.connector-type-required
+
+
+ + +
+
gateway.connector-name-required
+
+
+
+
+ + more_horiz + + {{ 'gateway.update-config' | translate }} + + + + close + + {{ 'gateway.delete' | translate }} + + +
+
+ {{'gateway.no-connectors'}} +
+ + + {{ 'gateway.connector-add' | translate }} + + action.add + +
+
diff --git a/ui/src/app/components/gateway/gateway-form.directive.js b/ui/src/app/components/gateway/gateway-form.directive.js new file mode 100644 index 0000000000..1e2e02ce24 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-form.directive.js @@ -0,0 +1,467 @@ +/* + * Copyright © 2016-2020 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 './gateway-form.scss'; +/* eslint-disable import/no-unresolved, import/default */ + +import gatewayFormTemplate from './gateway-form.tpl.html'; + +/* eslint-enable import/no-unresolved, import/default */ + +export default angular.module('thingsboard.directives.gatewayForm', []) + .directive('tbGatewayForm', GatewayForm) + .name; + +/*@ngInject*/ +function GatewayForm() { + return { + restrict: "E", + scope: true, + bindToController: { + formId: '=', + ctx: '=' + }, + controller: GatewayFormController, + controllerAs: 'vm', + templateUrl: gatewayFormTemplate + }; +} + +/*@ngInject*/ +function GatewayFormController($scope, $injector, $document, $mdExpansionPanel, toast, importExport, attributeService, deviceService, userService, $mdDialog, $mdUtil, types, $window, $q, entityService, utils, $translate) { + let vm = this; + const currentConfigurationAttribute = "current_configuration"; + const configurationDraftsAttribute = "configuration_drafts"; + const configurationAttribute = "configuration"; + const remoteLoggingLevelAttribute = "RemoteLoggingLevel"; + + const templateLogsConfig = '[loggers]}}keys=root, service, connector, converter, tb_connection, storage, extension}}[handlers]}}keys=consoleHandler, serviceHandler, connectorHandler, converterHandler, tb_connectionHandler, storageHandler, extensionHandler}}[formatters]}}keys=LogFormatter}}[logger_root]}}level=ERROR}}handlers=consoleHandler}}[logger_connector]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=connector}}[logger_storage]}}level={ERROR}}}handlers=storageHandler}}formatter=LogFormatter}}qualname=storage}}[logger_tb_connection]}}level={ERROR}}}handlers=tb_connectionHandler}}formatter=LogFormatter}}qualname=tb_connection}}[logger_service]}}level={ERROR}}}handlers=serviceHandler}}formatter=LogFormatter}}qualname=service}}[logger_converter]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=converter}}[logger_extension]}}level={ERROR}}}handlers=connectorHandler}}formatter=LogFormatter}}qualname=extension}}[handler_consoleHandler]}}class=StreamHandler}}level={ERROR}}}formatter=LogFormatter}}args=(sys.stdout,)}}[handler_connectorHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}connector.log", "d", 1, 7,)}}[handler_storageHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}storage.log", "d", 1, 7,)}}[handler_serviceHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}service.log", "d", 1, 7,)}}[handler_converterHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}converter.log", "d", 1, 3,)}}[handler_extensionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}extension.log", "d", 1, 3,)}}[handler_tb_connectionHandler]}}level={ERROR}}}class=logging.handlers.TimedRotatingFileHandler}}formatter=LogFormatter}}args=("{./logs/}tb_connection.log", "d", 1, 3,)}}[formatter_LogFormatter]}}format="%(asctime)s - %(levelname)s - [%(filename)s] - %(module)s - %(lineno)d - %(message)s" }}datefmt="%Y-%m-%d %H:%M:%S"'; + + vm.types = types; + + vm.configurations = { + gateway: '', + host: $document[0].domain, + port: 1883, + remoteConfiguration: true, + accessToken: '', + storageType: "memoryStorage", + readRecordsCount: 100, + maxRecordsCount: 10000, + dataFolderPath: './data/', + maxFilesCount: 5, + securityType: "accessToken", + caCertPath: '/etc/thingsboard-gateway/ca.pem', + privateKeyPath: '/etc/thingsboard-gateway/privateKey.pem', + certPath: '/etc/thingsboard-gateway/certificate.pem', + connectors: [], + remoteLoggingLevel: "DEBUG", + remoteLoggingPathToLogs: './logs/' + }; + + let archiveFileName = ''; + let gatewayNameExists = ''; + let successfulSaved = ''; + + vm.securityTypes = [{ + name: 'gateway.security-types.access-token', + value: 'accessToken' + }, { + name: 'gateway.security-types.tls', + value: 'tls' + }]; + + vm.storageTypes = [{ + name: 'gateway.storage-types.memory-storage', + value: 'memoryStorage' + }, { + name: 'gateway.storage-types.file-storage', + value: 'fileStorage' + }]; + + $scope.$watch('vm.ctx', function () { + if (vm.ctx ) { + vm.settings = vm.ctx.settings; + vm.widgetConfig = vm.ctx.widgetConfig; + initializeConfig(); + } + }); + + $scope.$on('gateway-form-resize', function (event, formId) { + if (vm.formId == formId) { + updateWidgetDisplaying(); + } + }); + + function updateWidgetDisplaying() { + vm.changeAlignment = (vm.ctx.$container[0].offsetWidth <= 425); + } + + function initWidgetSettings() { + let widgetTitle; + if (vm.settings.widgetTitle && vm.settings.widgetTitle.length) { + widgetTitle = utils.customTranslation(vm.settings.widgetTitle, vm.settings.widgetTitle); + } else { + widgetTitle = $translate.instant('gateway.gateway'); + } + vm.ctx.widgetTitle = widgetTitle; + + archiveFileName = vm.settings.archiveFileName && vm.settings.archiveFileName.length ? vm.settings.archiveFileName : 'gatewayConfiguration'; + gatewayNameExists = utils.customTranslation(vm.settings.deviceNameExist, vm.settings.deviceNameExist) || $translate.instant('gateway.gateway-exists'); + successfulSaved = utils.customTranslation(vm.settings.successfulSave, vm.settings.successfulSave) || $translate.instant('gateway.gateway-saved'); + } + + function initializeConfig() { + updateWidgetDisplaying(); + initWidgetSettings(); + getGatewaysList(true); + } + + vm.getAccessToken = (deviceId) => { + if (deviceId.id) { + getDeviceCredentials(deviceId.id); + } + }; + + vm.collapsePanel = function (panelId) { + $mdExpansionPanel(panelId).collapse(); + }; + + function getDeviceCredentials(deviceId) { + return deviceService.getDeviceCredentials(deviceId).then( + (deviceCredentials) => { + vm.configurations.accessToken = deviceCredentials.credentialsId; + getAttributes(); + } + ); + } + + vm.createDevice = (deviceObj) => { + deviceService.findByName(deviceObj.name, {ignoreErrors: true}) + .then( + function () { + toast.showError(gatewayNameExists, angular.element('.gateway-form'),'top left'); + }, + function () { + if(vm.settings.gatewayType && vm.settings.gatewayType.length){ + deviceObj.type = vm.settings.gatewayType; + } + deviceService.saveDevice(deviceObj).then( + (device) => { + getDeviceCredentials(device.id.id).then(() =>{ + getGatewaysList(); + }); + } + ); + }); + }; + + vm.saveAttributeConfig = () => { + $q.all([ + saveAttribute(configurationAttribute, $window.btoa(angular.toJson(getGatewayConfigJSON())), types.attributesScope.shared.value), + saveAttribute(configurationDraftsAttribute, $window.btoa(angular.toJson(getDraftConnectorJSON())), types.attributesScope.server.value), + saveAttribute(remoteLoggingLevelAttribute, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value) + ]).then(() =>{ + toast.showSuccess(successfulSaved, 2000, angular.element('.gateway-form'),'top left'); + }) + }; + + function getAttributes() { + let promises = []; + promises.push(getAttribute(currentConfigurationAttribute, types.attributesScope.client.value)); + promises.push(getAttribute(configurationDraftsAttribute, types.attributesScope.server.value)); + promises.push(getAttribute(remoteLoggingLevelAttribute, types.attributesScope.shared.value)); + $q.all(promises).then((response) => { + processCurrentConfiguration(response[0]); + processConfigurationDrafts(response[1]); + processLoggingLevel(response[2]); + }); + } + + function getAttribute(attributeName, attributeScope) { + return attributeService.getEntityAttributesValues(vm.configurations.gateway.id.entityType, vm.configurations.gateway.id.id, attributeScope, attributeName); + } + + function saveAttribute(attributeName, attributeValue, attributeScope) { + let attributes = [{ + key: attributeName, + value: attributeValue + }]; + return attributeService.saveEntityAttributes(vm.configurations.gateway.id.entityType, vm.configurations.gateway.id.id, attributeScope, attributes); + } + + vm.exportConfig = () => { + let filesZip = {}; + filesZip["tb_gateway.yaml"] = generateYAMLConfigurationFile(); + generateConfigConnectorFiles(filesZip); + generateLogConfigFile(filesZip); + importExport.exportJSZip(filesZip, archiveFileName); + saveAttribute(remoteLoggingLevelAttribute, vm.configurations.remoteLoggingLevel.toUpperCase(), types.attributesScope.shared.value); + }; + + function generateYAMLConfigurationFile() { + let config; + config = 'thingsboard:\n'; + config += ' host: ' + vm.configurations.host + '\n'; + config += ' remoteConfiguration: ' + vm.configurations.remoteConfiguration + '\n'; + config += ' port: ' + vm.configurations.port + '\n'; + config += ' security:\n'; + if (vm.configurations.securityType === 'accessToken') { + config += ' access-token: ' + vm.configurations.accessToken + '\n'; + } else if (vm.configurations.securityType === 'tls') { + config += ' ca_cert: ' + vm.configurations.caCertPath + '\n'; + config += ' privateKey: ' + vm.configurations.privateKeyPath + '\n'; + config += ' cert: ' + vm.configurations.certPath + '\n'; + } + config += 'storage:\n'; + if (vm.configurations.storageType === 'memoryStorage') { + config += ' type: memory\n'; + config += ' read_records_count: ' + vm.configurations.readRecordsCount + '\n'; + config += ' max_records_count: ' + vm.configurations.maxRecordsCount + '\n'; + } else if (vm.configurations.storageType === 'fileStorage') { + config += ' type: file\n'; + config += ' data_folder_path: ' + vm.configurations.dataFolderPath + '\n'; + config += ' max_file_count: ' + vm.configurations.maxFilesCount + '\n'; + config += ' max_read_records_count: ' + vm.configurations.readRecordsCount + '\n'; + config += ' max_records_per_file: ' + vm.configurations.maxRecordsCount + '\n'; + } + config += 'connectors:\n'; + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (vm.configurations.connectors[i].enabled) { + config += ' -\n'; + config += ' name: ' + vm.configurations.connectors[i].name + '\n'; + config += ' type: ' + vm.configurations.connectors[i].configType + '\n'; + config += ' configuration: ' + generateFileName(vm.configurations.connectors[i].name) + '\n'; + } + } + return config; + } + + function generateConfigConnectorFiles(fileZipAdd) { + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (vm.configurations.connectors[i].enabled) { + fileZipAdd[generateFileName(vm.configurations.connectors[i].name)] = angular.toJson(vm.configurations.connectors[i].config); + } + } + } + + function generateLogConfigFile(fileZipAdd) { + fileZipAdd["logs.conf"] = getLogsConfig(); + } + + function getLogsConfig() { + return templateLogsConfig + .replace(/{ERROR}/g, vm.configurations.remoteLoggingLevel) + .replace(/{.\/logs\/}/g, vm.configurations.remoteLoggingPathToLogs); + } + + function getGatewayConfigJSON() { + let gatewayConfig = {}; + gatewayConfig["thingsboard"] = gatewayMainConfigJSON(); + gatewayConnectorConfigJSON(gatewayConfig); + return gatewayConfig; + } + + function gatewayMainConfigJSON() { + let configuration = {}; + + let thingsBoard = {}; + thingsBoard.host = vm.configurations.host; + thingsBoard.remoteConfiguration = vm.configurations.remoteConfiguration; + thingsBoard.port = vm.configurations.port; + let security = {}; + if (vm.configurations.securityType === 'accessToken') { + security.accessToken = (vm.configurations.accessToken) ? vm.configurations.accessToken : "" + } else { + security.caCert = vm.configurations.caCertPath; + security.privateKey = vm.configurations.privateKeyPath; + security.cert = vm.configurations.certPath; + } + thingsBoard.security = security; + configuration.thingsboard = thingsBoard; + + let storage = {}; + if (vm.configurations.storageType === 'memoryStorage') { + storage.type = "memory"; + storage.read_records_count = vm.configurations.readRecordsCount; + storage.max_records_count = vm.configurations.maxRecordsCount; + } else if (vm.configurations.storageType === 'fileStorage') { + storage.type = "file"; + storage.data_folder_path = vm.configurations.dataFolderPath; + storage.max_file_count = vm.configurations.maxFilesCount; + storage.max_read_records_count = vm.configurations.readRecordsCount; + storage.max_records_per_file = vm.configurations.maxRecordsCount; + } + configuration.storage = storage; + + let connectors = []; + for (let i = 0; i < vm.configurations.connectors.length; i++) { + if (vm.configurations.connectors[i].enabled) { + let connector = { + configuration: generateFileName(vm.configurations.connectors[i].name), + name: vm.configurations.connectors[i].name, + type: vm.configurations.connectors[i].configType + }; + connectors.push(connector); + } + } + configuration.connectors = connectors; + + configuration.logs = $window.btoa(getLogsConfig()); + + return configuration; + } + + function gatewayConnectorConfigJSON(gatewayConfiguration) { + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (vm.configurations.connectors[i].enabled) { + let typeConnector = vm.configurations.connectors[i].configType; + if(!angular.isArray(gatewayConfiguration[typeConnector])){ + gatewayConfiguration[typeConnector] = []; + } + + let connectorConfig = { + name: vm.configurations.connectors[i].name, + config: vm.configurations.connectors[i].config + }; + gatewayConfiguration[typeConnector].push(connectorConfig); + } + } + } + + function getDraftConnectorJSON() { + let draftConnector = {}; + for(let i = 0; i < vm.configurations.connectors.length; i++){ + if (!vm.configurations.connectors[i].enabled) { + let connector = { + connector: vm.configurations.connectors[i].configType, + config: vm.configurations.connectors[i].config + }; + draftConnector[vm.configurations.connectors[i].name] = connector; + } + } + return draftConnector; + } + + function getGatewaysList(firstInit) { + vm.gateways = []; + entityService.getEntitiesByNameFilter(types.entityType.device, "", -1).then((devices) => { + for (let i = 0; i < devices.length; i++) { + const device = devices[i]; + if (device.additionalInfo !== null && device.additionalInfo.gateway === true) { + vm.gateways.push(device); + if (firstInit && vm.gateways.length && device.name === vm.gateways[0].name) { + vm.configurations.gateway = device; + vm.getAccessToken(device.id); + } + } + } + }); + } + + function processCurrentConfiguration(response) { + if (response.length > 0) { + vm.configurations.connectors = []; + let attribute = angular.fromJson($window.atob(response[0].value)); + for (var attributeKey in attribute) { + let keyValue = attribute[attributeKey]; + if (attributeKey === "thingsboard") { + if (keyValue !== null && Object.keys(keyValue).length > 0) { + setConfigGateway(keyValue); + } + } else { + for (let connectorType in keyValue) { + let name = "No name"; + if (Object.prototype.hasOwnProperty.call(keyValue[connectorType], 'name')) { + name = keyValue[connectorType].name ; + } + let connector = { + enabled: true, + configType: attributeKey, + config: keyValue[connectorType].config, + name: name + }; + vm.configurations.connectors.push(connector); + } + } + } + } + } + + function processConfigurationDrafts(response) { + if (response.length > 0) { + let attribute = angular.fromJson($window.atob(response[0].value)); + for (let key in attribute) { + let connector = { + enabled: false, + configType: attribute[key].connector, + config: attribute[key].config, + name: key + }; + vm.configurations.connectors.push(connector); + } + } + } + + function processLoggingLevel(response) { + if (response.length > 0) { + if (vm.types.gatewayLogLevel[response[0].value.toLowerCase()]) { + vm.configurations.remoteLoggingLevel = response[0].value.toUpperCase(); + } + } else { + vm.configurations.remoteLoggingLevel = vm.types.gatewayLogLevel.debug; + } + } + + function setConfigGateway(keyValue) { + if (Object.prototype.hasOwnProperty.call(keyValue, 'thingsboard')) { + vm.configurations.host = keyValue.thingsboard.host; + vm.configurations.port = keyValue.thingsboard.port; + vm.configurations.remoteConfiguration = keyValue.thingsboard.remoteConfiguration; + if (Object.prototype.hasOwnProperty.call(keyValue.thingsboard.security, 'accessToken')) { + vm.configurations.securityType = 'accessToken'; + vm.configurations.accessToken = keyValue.thingsboard.security.accessToken; + } else { + vm.configurations.securityType = 'tls'; + vm.configurations.caCertPath = keyValue.thingsboard.security.caCert; + vm.configurations.privateKeyPath = keyValue.thingsboard.security.private_key; + vm.configurations.certPath = keyValue.thingsboard.security.cert; + } + } + + if (Object.prototype.hasOwnProperty.call(keyValue, 'storage') && Object.prototype.hasOwnProperty.call(keyValue.storage, 'type')) { + if (keyValue.storage.type === 'memory') { + vm.configurations.storageType = 'memoryStorage'; + vm.configurations.readRecordsCount = keyValue.storage.read_records_count; + vm.configurations.maxRecordsCount = keyValue.storage.max_records_count; + } else if (keyValue.storage.type === 'file') { + vm.configurations.storageType = 'fileStorage'; + vm.configurations.dataFolderPath = keyValue.storage.data_folder_path; + vm.configurations.maxFilesCount = keyValue.storage.max_file_count; + vm.configurations.readRecordsCount = keyValue.storage.read_records_count; + vm.configurations.maxRecordsCount = keyValue.storage.max_records_count; + } + } + } + + function generateFileName(fileName) { + return fileName.replace("_", "") + .replace("-", "") + .replace(/^\s+|\s+/g, '') + .toLowerCase() + '.json'; + } +} + + diff --git a/ui/src/app/components/gateway/gateway-form.scss b/ui/src/app/components/gateway/gateway-form.scss new file mode 100644 index 0000000000..f6851c7688 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-form.scss @@ -0,0 +1,42 @@ +/** + * Copyright © 2016-2020 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. + */ +.gateway-form{ + height: 100%; + padding: 5px 5px 0; + background-color: transparent; + + .gateway-form-row{ + md-input-container{ + margin-bottom: 0; + } + + &.gateway-config-row-vertical{ + flex-direction: column; + + .md-select-container{ + margin-bottom: 14px; + } + } + } + + .security-type { + margin-top: 18px; + } + + .form-action-buttons{ + padding-top: 8px; + } +} diff --git a/ui/src/app/components/gateway/gateway-form.tpl.html b/ui/src/app/components/gateway/gateway-form.tpl.html new file mode 100644 index 0000000000..abe030d543 --- /dev/null +++ b/ui/src/app/components/gateway/gateway-form.tpl.html @@ -0,0 +1,227 @@ + + +
+ + + +
{{ 'gateway.thingsboard' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.thingsboard' | translate | uppercase }}
+ + +
+ + + + + + + + {{securityType.name | translate}} + + + +
+ + + +
+
gateway.thingsboard-host-required
+
+
+ + + +
+
gateway.thingsboard-port-required
+
gateway.thingsboard-port-max
+
gateway.thingsboard-port-min
+
gateway.thingsboard-port-pattern
+
+
+
+
+ + + + + + + + + + + + +
+ + {{ 'gateway.remote' | translate }} + +
+ + + + + {{logLevel}} + + + + + + +
+
gateway.path-logs-required
+
+
+
+
+
+
+ + +
{{ 'gateway.storage' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.storage' | translate | uppercase }}
+ + +
+ + + + + + {{storageType.name | translate}} + + + + +
+ + + +
+
gateway.storage-pack-size-required
+
gateway.storage-pack-size-min
+
gateway.storage-pack-size-pattern
+
+
+ + + + +
+
gateway.storage-max-records-required
+
gateway.storage-max-records-min
+
gateway.storage-max-records-pattern
+
+
+
+ +
+ + + +
+
gateway.storage-max-files-required
+
gateway.storage-max-files-min
+
gateway.storage-max-files-pattern
+
+
+ + + + +
+
gateway.storage-path-required
+
+
+
+
+
+
+ + +
{{ 'gateway.connectors' | translate | uppercase }}
+ + +
+ + +
{{ 'gateway.connectors' | translate | uppercase }}
+ + +
+ + + + +
+
+
+
+ + {{'action.download' | translate }} + {{'gateway.download-tip' | translate }} + + + + {{'action.save' | translate }} + {{'gateway.save-tip' | translate }} + +
+
+
diff --git a/ui/src/app/components/grid.directive.js b/ui/src/app/components/grid.directive.js index 1033a969b6..e22c167a48 100644 --- a/ui/src/app/components/grid.directive.js +++ b/ui/src/app/components/grid.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/grid.scss b/ui/src/app/components/grid.scss index 48e6cc6a19..8e254c40b2 100644 --- a/ui/src/app/components/grid.scss +++ b/ui/src/app/components/grid.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/grid.tpl.html b/ui/src/app/components/grid.tpl.html index 6319456de5..900f5b86b5 100644 --- a/ui/src/app/components/grid.tpl.html +++ b/ui/src/app/components/grid.tpl.html @@ -1,6 +1,6 @@
- + - {{ + {{ 'js-func.tidy' | translate }} + {{ + 'js-func.mini' | translate }} +
diff --git a/ui/src/app/components/json-form.directive.js b/ui/src/app/components/json-form.directive.js index 84f62f939a..9b3d05d850 100644 --- a/ui/src/app/components/json-form.directive.js +++ b/ui/src/app/components/json-form.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/json-form.scss b/ui/src/app/components/json-form.scss index 3cb02fcbfe..27e31bcfef 100644 --- a/ui/src/app/components/json-form.scss +++ b/ui/src/app/components/json-form.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/json-form.tpl.html b/ui/src/app/components/json-form.tpl.html index f5096c25b7..7586c00974 100644 --- a/ui/src/app/components/json-form.tpl.html +++ b/ui/src/app/components/json-form.tpl.html @@ -1,6 +1,6 @@
-
+
+ + {{'js-func.tidy' | translate }} + + + {{'js-func.mini' | translate }} +
diff --git a/ui/src/app/components/keyboard-shortcut.filter.js b/ui/src/app/components/keyboard-shortcut.filter.js index 6b5f39fe4e..27fb27e3a5 100644 --- a/ui/src/app/components/keyboard-shortcut.filter.js +++ b/ui/src/app/components/keyboard-shortcut.filter.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/kv-map.directive.js b/ui/src/app/components/kv-map.directive.js index 4d1f4ffd5d..fb93375e23 100644 --- a/ui/src/app/components/kv-map.directive.js +++ b/ui/src/app/components/kv-map.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/kv-map.scss b/ui/src/app/components/kv-map.scss index 9e18e60761..3e7aa2cde0 100644 --- a/ui/src/app/components/kv-map.scss +++ b/ui/src/app/components/kv-map.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/kv-map.tpl.html b/ui/src/app/components/kv-map.tpl.html index fd20b94660..063f0746a8 100644 --- a/ui/src/app/components/kv-map.tpl.html +++ b/ui/src/app/components/kv-map.tpl.html @@ -1,6 +1,6 @@ - + {{model.displayValue}} \ No newline at end of file diff --git a/ui/src/app/components/timewindow-panel.controller.js b/ui/src/app/components/timewindow-panel.controller.js index 092d2a9b4e..eb854d9fbc 100644 --- a/ui/src/app/components/timewindow-panel.controller.js +++ b/ui/src/app/components/timewindow-panel.controller.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/timewindow-panel.tpl.html b/ui/src/app/components/timewindow-panel.tpl.html index 98608c994a..46a1f002c1 100644 --- a/ui/src/app/components/timewindow-panel.tpl.html +++ b/ui/src/app/components/timewindow-panel.tpl.html @@ -1,6 +1,6 @@
- + {{ 'timewindow.edit' | translate }} @@ -28,7 +28,7 @@ {{model.displayValue}} - + {{ 'timewindow.edit' | translate }} diff --git a/ui/src/app/components/truncate.filter.js b/ui/src/app/components/truncate.filter.js index 809ec692de..de32bbcd2a 100644 --- a/ui/src/app/components/truncate.filter.js +++ b/ui/src/app/components/truncate.filter.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/widget/action/custom-action-pretty-editor.directive.js b/ui/src/app/components/widget/action/custom-action-pretty-editor.directive.js index 0d282af05e..65150d44ce 100644 --- a/ui/src/app/components/widget/action/custom-action-pretty-editor.directive.js +++ b/ui/src/app/components/widget/action/custom-action-pretty-editor.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/widget/action/custom-action-pretty-editor.scss b/ui/src/app/components/widget/action/custom-action-pretty-editor.scss index b32f75cd06..fa6319b44a 100644 --- a/ui/src/app/components/widget/action/custom-action-pretty-editor.scss +++ b/ui/src/app/components/widget/action/custom-action-pretty-editor.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/components/widget/action/custom-action-pretty-editor.tpl.html b/ui/src/app/components/widget/action/custom-action-pretty-editor.tpl.html index 80cdf2a786..79b0dd625c 100644 --- a/ui/src/app/components/widget/action/custom-action-pretty-editor.tpl.html +++ b/ui/src/app/components/widget/action/custom-action-pretty-editor.tpl.html @@ -1,6 +1,6 @@ - +
diff --git a/ui/src/app/dashboard/states/select-target-state.controller.js b/ui/src/app/dashboard/states/select-target-state.controller.js index 41e0ed4398..182b27560d 100644 --- a/ui/src/app/dashboard/states/select-target-state.controller.js +++ b/ui/src/app/dashboard/states/select-target-state.controller.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/dashboard/states/select-target-state.tpl.html b/ui/src/app/dashboard/states/select-target-state.tpl.html index 703dcbc638..9e3daa4828 100644 --- a/ui/src/app/dashboard/states/select-target-state.tpl.html +++ b/ui/src/app/dashboard/states/select-target-state.tpl.html @@ -1,6 +1,6 @@ + + + +
+

{{ 'details.edit-json' | translate }}

+ + + + +
+
+ +
+ + +
+
+ + + {{'action.save'|translate}} + + + {{'action.cancel'|translate }} + + + +
diff --git a/ui/src/app/entity/attribute/attribute-table.directive.js b/ui/src/app/entity/attribute/attribute-table.directive.js index 0b03fb600f..59ea4fe91d 100644 --- a/ui/src/app/entity/attribute/attribute-table.directive.js +++ b/ui/src/app/entity/attribute/attribute-table.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. @@ -39,7 +39,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS element.html(template); - var getAttributeScopeByValue = function(attributeScopeValue) { + var getAttributeScopeByValue = function (attributeScopeValue) { if (scope.types.latestTelemetry.value === attributeScopeValue) { return scope.types.latestTelemetry; } @@ -48,7 +48,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS return scope.attributeScopes[attrScope]; } } - } + }; scope.types = types; @@ -87,14 +87,14 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS search: null }; - scope.$watch("entityId", function(newVal) { + scope.$watch("entityId", function (newVal) { if (newVal) { scope.resetFilter(); scope.getEntityAttributes(false, true); } }); - scope.$watch("attributeScope", function(newVal, prevVal) { + scope.$watch("attributeScope", function (newVal, prevVal) { if (newVal && !angular.equals(newVal, prevVal)) { scope.mode = 'default'; scope.query.search = null; @@ -103,30 +103,30 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } }); - scope.resetFilter = function() { + scope.resetFilter = function () { scope.mode = 'default'; scope.query.search = null; scope.selectedAttributes = []; scope.attributeScope = getAttributeScopeByValue(attrs.defaultAttributeScope); - } + }; - scope.enterFilterMode = function(event) { + scope.enterFilterMode = function (event) { let $button = angular.element(event.currentTarget); let $toolbarsContainer = $button.closest('.toolbarsContainer'); scope.query.search = ''; - $timeout(()=>{ + $timeout(() => { $toolbarsContainer.find('.searchInput').focus(); }) - } + }; - scope.exitFilterMode = function() { + scope.exitFilterMode = function () { scope.query.search = null; scope.getEntityAttributes(); - } + }; - scope.$watch("query.search", function(newVal, prevVal) { + scope.$watch("query.search", function (newVal, prevVal) { if (!angular.equals(newVal, prevVal) && scope.query.search != null) { scope.getEntityAttributes(); } @@ -142,15 +142,15 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } } - scope.onReorder = function() { + scope.onReorder = function () { scope.getEntityAttributes(false, false); - } + }; - scope.onPaginate = function() { + scope.onPaginate = function () { scope.getEntityAttributes(false, false); - } + }; - scope.getEntityAttributes = function(forceUpdate, reset) { + scope.getEntityAttributes = function (forceUpdate, reset) { if (scope.attributesDeferred) { scope.attributesDeferred.resolve(); } @@ -163,7 +163,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } scope.checkSubscription(); scope.attributesDeferred = attributeService.getEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, - scope.query, function(attributes, update, apply) { + scope.query, function (attributes, update, apply) { success(attributes, update || forceUpdate, apply); } ); @@ -176,9 +176,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS }); deferred.resolve(); } - } + }; - scope.checkSubscription = function() { + scope.checkSubscription = function () { var newSubscriptionId = null; if (scope.entityId && scope.entityType && scope.attributeScope.clientSide && scope.mode != 'widget') { newSubscriptionId = attributeService.subscribeForEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value); @@ -187,36 +187,38 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); } scope.subscriptionId = newSubscriptionId; - } + }; - scope.$on('$destroy', function() { + scope.$on('$destroy', function () { if (scope.subscriptionId) { attributeService.unsubscribeForEntityAttributes(scope.subscriptionId); } }); - scope.editAttribute = function($event, attribute) { + scope.editAttribute = function ($event, attribute) { if (!scope.attributeScope.clientSide) { $event.stopPropagation(); $mdEditDialog.show({ controller: EditAttributeValueController, templateUrl: editAttributeValueTemplate, - locals: {attributeValue: attribute.value, - save: function (model) { - var updatedAttribute = angular.copy(attribute); - updatedAttribute.value = model.value; - attributeService.saveEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, [updatedAttribute]).then( - function success() { - scope.getEntityAttributes(); - } - ); - }}, + locals: { + attributeValue: attribute.value, + save: function (model) { + var updatedAttribute = angular.copy(attribute); + updatedAttribute.value = model.value; + attributeService.saveEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, [updatedAttribute]).then( + function success() { + scope.getEntityAttributes(); + } + ); + } + }, targetEvent: $event }); } - } + }; - scope.addAttribute = function($event) { + scope.addAttribute = function ($event) { if (!scope.attributeScope.clientSide) { $event.stopPropagation(); $mdDialog.show({ @@ -224,16 +226,20 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS controllerAs: 'vm', templateUrl: addAttributeDialogTemplate, parent: angular.element($document[0].body), - locals: {entityType: scope.entityType, entityId: scope.entityId, attributeScope: scope.attributeScope.value}, + locals: { + entityType: scope.entityType, + entityId: scope.entityId, + attributeScope: scope.attributeScope.value + }, fullscreen: true, targetEvent: $event }).then(function () { scope.getEntityAttributes(); }); } - } + }; - scope.deleteAttributes = function($event) { + scope.deleteAttributes = function ($event) { if (!scope.attributeScope.clientSide) { $event.stopPropagation(); var confirm = $mdDialog.confirm() @@ -244,33 +250,33 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS .cancel($translate.instant('action.no')) .ok($translate.instant('action.yes')); $mdDialog.show(confirm).then(function () { - attributeService.deleteEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, scope.selectedAttributes).then( - function success() { - scope.selectedAttributes = []; - scope.getEntityAttributes(); - } - ) + attributeService.deleteEntityAttributes(scope.entityType, scope.entityId, scope.attributeScope.value, scope.selectedAttributes).then( + function success() { + scope.selectedAttributes = []; + scope.getEntityAttributes(); + } + ) }); } - } + }; - scope.nextWidget = function() { + scope.nextWidget = function () { $mdUtil.nextTick(function () { if (scope.widgetsCarousel.index < scope.widgetsList.length - 1) { scope.widgetsCarousel.index++; } }); - } + }; - scope.prevWidget = function() { + scope.prevWidget = function () { $mdUtil.nextTick(function () { if (scope.widgetsCarousel.index > 0) { scope.widgetsCarousel.index--; } }); - } + }; - scope.enterWidgetMode = function() { + scope.enterWidgetMode = function () { if (scope.widgetsIndexWatch) { scope.widgetsIndexWatch(); @@ -303,7 +309,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS entitiAliases[entityAlias.id] = entityAlias; var stateController = { - getStateParams: function() { + getStateParams: function () { return {}; } }; @@ -317,9 +323,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS type: types.datasourceType.entity, entityAliasId: entityAlias.id, dataKeys: [] - } + }; var i = 0; - for (var attr =0; attr < scope.selectedAttributes.length;attr++) { + for (var attr = 0; attr < scope.selectedAttributes.length; attr++) { var attribute = scope.selectedAttributes[attr]; var dataKey = { name: attribute.key, @@ -328,12 +334,12 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS color: utils.getMaterialColor(i), settings: {}, _hash: Math.random() - } + }; datasource.dataKeys.push(dataKey); i++; } - scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function(newVal, prevVal) { + scope.widgetsIndexWatch = scope.$watch('widgetsCarousel.index', function (newVal, prevVal) { if (scope.mode === 'widget' && (newVal != prevVal)) { var index = scope.widgetsCarousel.index; for (var i = 0; i < scope.widgetsList.length; i++) { @@ -345,7 +351,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } }); - scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function(newVal, prevVal) { + scope.widgetsBundleWatch = scope.$watch('widgetsBundle', function (newVal, prevVal) { if (scope.mode === 'widget' && (scope.firstBundle === true || newVal != prevVal)) { scope.widgetsList = []; scope.widgetsListCache = []; @@ -358,7 +364,7 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS widgetService.getBundleWidgetTypes(scope.widgetsBundle.alias, isSystem).then( function success(widgetTypes) { - widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type','-createdTime']); + widgetTypes = $filter('orderBy')(widgetTypes, ['-descriptor.type', '-createdTime']); for (var i = 0; i < widgetTypes.length; i++) { var widgetType = widgetTypes[i]; @@ -398,9 +404,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS } } }); - } + }; - scope.exitWidgetMode = function() { + scope.exitWidgetMode = function () { if (scope.widgetsBundleWatch) { scope.widgetsBundleWatch(); scope.widgetsBundleWatch = null; @@ -412,9 +418,9 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS scope.selectedWidgetsBundleAlias = null; scope.mode = 'default'; scope.getEntityAttributes(true); - } + }; - scope.addWidgetToDashboard = function($event) { + scope.addWidgetToDashboard = function ($event) { if (scope.mode === 'widget' && scope.widgetsListCache.length > 0) { var widget = scope.widgetsListCache[scope.widgetsCarousel.index][0]; $event.stopPropagation(); @@ -423,21 +429,26 @@ export default function AttributeTableDirective($compile, $templateCache, $rootS controllerAs: 'vm', templateUrl: addWidgetToDashboardDialogTemplate, parent: angular.element($document[0].body), - locals: {entityId: scope.entityId, entityType: scope.entityType, entityName: scope.entityName, widget: angular.copy(widget)}, + locals: { + entityId: scope.entityId, + entityType: scope.entityType, + entityName: scope.entityName, + widget: angular.copy(widget) + }, fullscreen: true, targetEvent: $event }).then(function () { }); } - } + }; - scope.loading = function() { + scope.loading = function () { return $rootScope.loading; - } + }; $compile(element.contents())(scope); - } + }; return { restrict: "E", diff --git a/ui/src/app/entity/attribute/attribute-table.scss b/ui/src/app/entity/attribute/attribute-table.scss index dc6fdb828e..5a0bde0c68 100644 --- a/ui/src/app/entity/attribute/attribute-table.scss +++ b/ui/src/app/entity/attribute/attribute-table.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. @@ -58,3 +58,11 @@ md-toolbar.md-table-toolbar.alternate { } } } + +md-edit-dialog.tb-edit-dialog{ + z-index: 78; +} + +md-backdrop.md-edit-dialog-backdrop{ + z-index: 77; +} diff --git a/ui/src/app/entity/attribute/attribute-table.tpl.html b/ui/src/app/entity/attribute/attribute-table.tpl.html index 29fcd0f658..8844891e62 100644 --- a/ui/src/app/entity/attribute/attribute-table.tpl.html +++ b/ui/src/app/entity/attribute/attribute-table.tpl.html @@ -1,6 +1,6 @@ - -
+ +
@@ -38,7 +38,8 @@ - +
attribute.value-required
value.invalid-integer-value
@@ -52,16 +53,31 @@
- + {{ (model.value ? 'value.true' : 'value.false') | translate }}
+
+ + + +
+
attribute.value-required
+
+
+ + + {{ 'action.edit' | translate }} + + + +
- {{ 'action.cancel' | - translate }} + {{ 'action.cancel' | translate }} @@ -69,4 +85,4 @@
-
\ No newline at end of file +
diff --git a/ui/src/app/entity/entity-autocomplete.directive.js b/ui/src/app/entity/entity-autocomplete.directive.js index 8d8bcee7d0..983180c3c6 100644 --- a/ui/src/app/entity/entity-autocomplete.directive.js +++ b/ui/src/app/entity/entity-autocomplete.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/entity/entity-autocomplete.scss b/ui/src/app/entity/entity-autocomplete.scss index 7a1045bc6e..c3053f3c48 100644 --- a/ui/src/app/entity/entity-autocomplete.scss +++ b/ui/src/app/entity/entity-autocomplete.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/entity/entity-autocomplete.tpl.html b/ui/src/app/entity/entity-autocomplete.tpl.html index 675c843655..e9011ff36a 100644 --- a/ui/src/app/entity/entity-autocomplete.tpl.html +++ b/ui/src/app/entity/entity-autocomplete.tpl.html @@ -1,6 +1,6 @@ + + + {{ entity.entityLabel || entity.name }} + + diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.scss b/ui/src/app/widget/lib/alarm-status-filter-panel.scss index 3e35733641..bfc0b58387 100644 --- a/ui/src/app/widget/lib/alarm-status-filter-panel.scss +++ b/ui/src/app/widget/lib/alarm-status-filter-panel.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html index 316e3b44a4..5790ac03e9 100644 --- a/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html +++ b/ui/src/app/widget/lib/alarm-status-filter-panel.tpl.html @@ -1,6 +1,6 @@ -
-
-
-
- - - - - - - {{key.settings.icon}} - - -
-
Value must be greater than {{key.settings.min}}
-
Value must be lower than {{key.settings.max}}
-
This field is required
+ +
+
+ {{ vm.getGroupTitle(source.datasource) }} + +
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
- +
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+ + + + {{key.settings.icon}} + + +
+
{{ key.settings.requiredErrorMessage }}
+
value.invalid-integer-value
+
+
+
+
+ + {{key.label}} + +
+
+ + {{key.label}} + +
+
+ +
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
+ +
+
{{ key.settings.requiredErrorMessage }}
+
+
+
+
+
+
+
+ + {{ 'action.undo' | translate }} + + + {{ 'action.save' | translate }} +
- -
-
- No attribute is selected +
+
+
+ {{ 'widgets.input-widgets.no-entity-selected' | translate }}
-
- Timeseries parameter cannot be used in this widget +
+ {{ 'widgets.input-widgets.not-allowed-entity' | translate }}
-
- - {{ 'action.undo' | translate }} - - - {{ 'action.save' | translate }} - -
diff --git a/ui/src/app/widget/lib/openstreet-map.js b/ui/src/app/widget/lib/openstreet-map.js index 21977392bf..ad14071df3 100644 --- a/ui/src/app/widget/lib/openstreet-map.js +++ b/ui/src/app/widget/lib/openstreet-map.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. @@ -14,18 +14,22 @@ * limitations under the License. */ import 'leaflet/dist/leaflet.css'; +import 'leaflet.markercluster/dist/MarkerCluster.css' +import 'leaflet.markercluster/dist/MarkerCluster.Default.css' import * as L from 'leaflet'; import 'leaflet-providers'; +import 'leaflet.markercluster/dist/leaflet.markercluster' export default class TbOpenStreetMap { - constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, mapProvider, credentials, defaultCenterPosition) { + constructor($containerElement, utils, initCallback, defaultZoomLevel, dontFitMapBounds, disableScrollZooming, minZoomLevel, mapProvider, credentials, defaultCenterPosition, markerClusteringSetting) { this.utils = utils; this.defaultZoomLevel = defaultZoomLevel; this.dontFitMapBounds = dontFitMapBounds; this.minZoomLevel = minZoomLevel; this.tooltips = []; + this.isMarketCluster = markerClusteringSetting && markerClusteringSetting.isMarketCluster; if (!mapProvider) { mapProvider = { @@ -48,6 +52,11 @@ export default class TbOpenStreetMap { var tileLayer = mapProvider.isCustom ? L.tileLayer(mapProvider.name) : L.tileLayer.provider(mapProvider.name, credentials); tileLayer.addTo(this.map); + if (this.isMarketCluster) { + this.markersCluster = L.markerClusterGroup(markerClusteringSetting); + this.map.addLayer(this.markersCluster); + } + if (initCallback) { setTimeout(initCallback, 0); //eslint-disable-line } @@ -58,6 +67,10 @@ export default class TbOpenStreetMap { return angular.isDefined(this.map); } + getContainer() { + return this.isMarketCluster ? this.markersCluster : this.map; + } + updateMarkerLabel(marker, settings) { marker.unbindTooltip(); marker.bindTooltip('
' + settings.labelText + '
', @@ -137,8 +150,10 @@ export default class TbOpenStreetMap { onMarkerIconReady(iconInfo); } - createMarker(location, dsIndex, settings, onClickListener, markerArgs) { - var marker = L.marker(location, {}); + createMarker(location, dsIndex, settings, onClickListener, markerArgs, onDragendListener) { + var marker = L.marker(location, { + draggable: settings.drraggable + }); var opMap = this; this.createMarkerIcon(marker, settings, (iconInfo) => { marker.setIcon(iconInfo.icon); @@ -147,7 +162,7 @@ export default class TbOpenStreetMap { marker.bindTooltip('
' + settings.labelText + '
', {className: 'tb-marker-label', permanent: true, direction: 'top', offset: marker.tooltipOffset}); } - marker.addTo(opMap.map); + marker.addTo(opMap.getContainer()); }); if (settings.displayTooltip) { @@ -158,11 +173,15 @@ export default class TbOpenStreetMap { marker.on('click', onClickListener); } + if (onDragendListener) { + marker.on('dragend', onDragendListener); + } + return marker; } removeMarker(marker) { - this.map.removeLayer(marker); + this.getContainer().removeLayer(marker); } createTooltip(marker, dsIndex, settings, markerArgs) { @@ -254,9 +273,9 @@ export default class TbOpenStreetMap { polygon.redraw(); } - fitBounds(bounds) { + fitBounds(bounds, useDefaultZoom) { if (bounds.isValid()) { - if (this.dontFitMapBounds && this.defaultZoomLevel) { + if ((this.dontFitMapBounds || useDefaultZoom) && this.defaultZoomLevel) { this.map.setZoom(this.defaultZoomLevel, {animate: false}); this.map.panTo(bounds.getCenter(), {animate: false}); } else { @@ -313,4 +332,8 @@ export default class TbOpenStreetMap { return this.tooltips; } + getCenter() { + return this.map.getCenter(); + } + } diff --git a/ui/src/app/widget/lib/rpc/index.js b/ui/src/app/widget/lib/rpc/index.js index 10598000d4..788e014f50 100644 --- a/ui/src/app/widget/lib/rpc/index.js +++ b/ui/src/app/widget/lib/rpc/index.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. diff --git a/ui/src/app/widget/lib/rpc/knob.directive.js b/ui/src/app/widget/lib/rpc/knob.directive.js index 4bd4a58288..d89c13de5d 100644 --- a/ui/src/app/widget/lib/rpc/knob.directive.js +++ b/ui/src/app/widget/lib/rpc/knob.directive.js @@ -1,5 +1,5 @@ /* - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. @@ -328,6 +328,9 @@ function KnobController($element, $scope, $document) { var textWidth = measureTextWidth(text, fontSize); while (textWidth > maxWidth) { fontSize--; + if (fontSize < 0) { + break; + } textWidth = measureTextWidth(text, fontSize); } element.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'}); @@ -335,7 +338,7 @@ function KnobController($element, $scope, $document) { function measureTextWidth(text, fontSize) { textMeasure.css({'fontSize': fontSize+'px', 'lineHeight': fontSize+'px'}); - textMeasure.text(text); + textMeasure.html(text); return textMeasure.width(); } diff --git a/ui/src/app/widget/lib/rpc/knob.scss b/ui/src/app/widget/lib/rpc/knob.scss index 568994d391..2188511a95 100644 --- a/ui/src/app/widget/lib/rpc/knob.scss +++ b/ui/src/app/widget/lib/rpc/knob.scss @@ -1,5 +1,5 @@ /** - * Copyright © 2016-2019 The Thingsboard Authors + * Copyright © 2016-2020 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. @@ -26,12 +26,9 @@ $minmax-height: percentage(.04) !default; $minmax-container-margin-pct: percentage(.18) !default; $minmax-container-margin-bottom-pct: percentage(.12) !default; -$background-color: #e6e7e8 !default; - .tb-knob { width: 100%; height: 100%; - background: $background-color; .knob { position: relative; diff --git a/ui/src/app/widget/lib/rpc/knob.tpl.html b/ui/src/app/widget/lib/rpc/knob.tpl.html index 290b1920a7..3ff858293e 100644 --- a/ui/src/app/widget/lib/rpc/knob.tpl.html +++ b/ui/src/app/widget/lib/rpc/knob.tpl.html @@ -1,6 +1,6 @@