Commit 71c4e0a48c3618644a17afaec3486c5ed30e5a9b

Authored by Andrew Shvayka
Committed by GitHub
2 parents 71308c3d 6a51eda9

Merge pull request #135 from thingsboard/master

Merge 1.2.3 to release branch
Showing 83 changed files with 2236 additions and 388 deletions

Too many changes to show.

To preserve performance only 83 of 130 files are displayed.

  1 +*.toDelete
1 output/** 2 output/**
2 *.class 3 *.class
3 *~ 4 *~
@@ -10,16 +10,16 @@ Thingsboard is an open-source IoT platform for data collection, processing, visu @@ -10,16 +10,16 @@ Thingsboard is an open-source IoT platform for data collection, processing, visu
10 10
11 Thingsboard documentation is hosted on [thingsboard.io](https://thingsboard.io/docs). 11 Thingsboard documentation is hosted on [thingsboard.io](https://thingsboard.io/docs).
12 12
13 -## Sample Dashboards 13 +## IoT use cases
14 14
15 -[**Smart energy monitoring**](https://demo.thingsboard.io/demo?dashboardId=e8e409c0-f2b5-11e6-a6ee-bb0136cc33d0&source=github)  
16 -[![Smart energy monitoring demo](https://cloud.githubusercontent.com/assets/8308069/23790111/62c8a61e-0586-11e7-84eb-51febc54ec09.png "Smart energy monitoring demo")](https://demo.thingsboard.io/demo?dashboardId=e8e409c0-f2b5-11e6-a6ee-bb0136cc33d0&source=github) 15 +[**Smart energy**](https://thingsboard.io/smart-energy/)
  16 +[![Smart energy monitoring demo](https://cloud.githubusercontent.com/assets/8308069/24495682/aebd45d0-153e-11e7-8de4-7360ed5b41ae.gif "Smart energy")](https://thingsboard.io/smart-energy/)
17 17
18 -[**Silos monitoring**](https://demo.thingsboard.io/demo?dashboardId=1f9828d0-058e-11e7-87f7-bb0136cc33d0&source=github)  
19 -[![Silos monitoring demo](https://cloud.githubusercontent.com/assets/8308069/23996135/00214844-0a55-11e7-9623-d1e3be0702ca.png "Silos monitoring demo")](https://demo.thingsboard.io/demo?dashboardId=1f9828d0-058e-11e7-87f7-bb0136cc33d0&source=github) 18 +[**Smart farming**](https://thingsboard.io/smart-farming/)
  19 +[![Smart farming](https://cloud.githubusercontent.com/assets/8308069/24496824/10dc1144-1542-11e7-8aa1-5d3a281d5a1a.gif "Smart farming")](https://thingsboard.io/smart-farming/)
20 20
21 -[**Smart bus tracking**](https://demo.thingsboard.io/demo?dashboardId=3d0bf910-ee09-11e6-b619-bb0136cc33d0&source=github)  
22 -[![Smart bus tracking demo](https://cloud.githubusercontent.com/assets/8308069/23790110/62c6ecde-0586-11e7-8249-19eafd5bf8cc.png "Smart bus tracking demo")](https://demo.thingsboard.io/demo?dashboardId=3d0bf910-ee09-11e6-b619-bb0136cc33d0&source=github) 21 +[**Fleet tracking**](https://thingsboard.io/fleet-tracking/)
  22 +[![Fleet tracking](https://cloud.githubusercontent.com/assets/8308069/24497169/3a1a61e0-1543-11e7-8d55-3c8a13f35634.gif "Fleet tracking")](https://thingsboard.io/fleet-tracking/)
23 23
24 ## Getting Started 24 ## Getting Started
25 25
1 -!bin/  
  1 +!bin/
  2 +/bin/
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -51,13 +51,7 @@ import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody; @@ -51,13 +51,7 @@ import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestBody;
51 import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg; 51 import org.thingsboard.server.extensions.api.plugins.msg.ToDeviceRpcRequestPluginMsg;
52 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg; 52 import org.thingsboard.server.extensions.api.plugins.msg.ToPluginRpcResponseDeviceMsg;
53 53
54 -import java.util.HashMap;  
55 -import java.util.HashSet;  
56 -import java.util.List;  
57 -import java.util.Map;  
58 -import java.util.Optional;  
59 -import java.util.Set;  
60 -import java.util.UUID; 54 +import java.util.*;
61 import java.util.concurrent.ExecutionException; 55 import java.util.concurrent.ExecutionException;
62 import java.util.concurrent.TimeoutException; 56 import java.util.concurrent.TimeoutException;
63 import java.util.function.Consumer; 57 import java.util.function.Consumer;
@@ -205,25 +199,21 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso @@ -205,25 +199,21 @@ public class DeviceActorMessageProcessor extends AbstractContextAwareMsgProcesso
205 199
206 void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) { 200 void processAttributesUpdate(ActorContext context, DeviceAttributesEventNotificationMsg msg) {
207 refreshAttributes(msg); 201 refreshAttributes(msg);
208 - Set<AttributeKey> keys = msg.getDeletedKeys();  
209 if (attributeSubscriptions.size() > 0) { 202 if (attributeSubscriptions.size() > 0) {
210 ToDeviceMsg notification = null; 203 ToDeviceMsg notification = null;
211 if (msg.isDeleted()) { 204 if (msg.isDeleted()) {
212 - List<AttributeKey> sharedKeys = keys.stream() 205 + List<AttributeKey> sharedKeys = msg.getDeletedKeys().stream()
213 .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope())) 206 .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))
214 .collect(Collectors.toList()); 207 .collect(Collectors.toList());
215 notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys)); 208 notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromDeleted(sharedKeys));
216 } else { 209 } else {
217 - List<AttributeKvEntry> attributes = keys.stream()  
218 - .filter(key -> DataConstants.SHARED_SCOPE.equals(key.getScope()))  
219 - .map(key -> deviceAttributes.getServerPublicAttribute(key.getAttributeKey()))  
220 - .filter(Optional::isPresent)  
221 - .map(Optional::get)  
222 - .collect(Collectors.toList());  
223 - if (attributes.size() > 0) {  
224 - notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));  
225 - } else {  
226 - logger.debug("[{}] No public server side attributes changed!", deviceId); 210 + if (DataConstants.SHARED_SCOPE.equals(msg.getScope())) {
  211 + List<AttributeKvEntry> attributes = new ArrayList<>(msg.getValues());
  212 + if (attributes.size() > 0) {
  213 + notification = new AttributesUpdateNotification(BasicAttributeKVMsg.fromShared(attributes));
  214 + } else {
  215 + logger.debug("[{}] No public server side attributes changed!", deviceId);
  216 + }
227 } 217 }
228 } 218 }
229 if (notification != null) { 219 if (notification != null) {
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.config;
  17 +
  18 +import org.springframework.boot.context.properties.ConfigurationProperties;
  19 +import org.springframework.context.annotation.Configuration;
  20 +import org.springframework.web.cors.CorsConfiguration;
  21 +
  22 +import java.util.HashMap;
  23 +import java.util.Map;
  24 +
  25 +/**
  26 + * Created by yyh on 2017/5/2.
  27 + * CORS configuration
  28 + */
  29 +@Configuration
  30 +@ConfigurationProperties(prefix = "spring.mvc.cors")
  31 +public class MvcCorsProperties {
  32 +
  33 + private Map<String, CorsConfiguration> mappings = new HashMap<>();
  34 +
  35 + public MvcCorsProperties() {
  36 + }
  37 +
  38 + public Map<String, CorsConfiguration> getMappings() {
  39 + return mappings;
  40 + }
  41 +
  42 + public void setMappings(Map<String, CorsConfiguration> mappings) {
  43 + this.mappings = mappings;
  44 + }
  45 +}
@@ -18,7 +18,9 @@ package org.thingsboard.server.config; @@ -18,7 +18,9 @@ package org.thingsboard.server.config;
18 import com.fasterxml.jackson.databind.ObjectMapper; 18 import com.fasterxml.jackson.databind.ObjectMapper;
19 import org.springframework.beans.factory.annotation.Autowired; 19 import org.springframework.beans.factory.annotation.Autowired;
20 import org.springframework.beans.factory.annotation.Qualifier; 20 import org.springframework.beans.factory.annotation.Qualifier;
  21 +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
21 import org.springframework.boot.autoconfigure.security.SecurityProperties; 22 import org.springframework.boot.autoconfigure.security.SecurityProperties;
  23 +import org.springframework.boot.context.properties.EnableConfigurationProperties;
22 import org.springframework.context.annotation.Bean; 24 import org.springframework.context.annotation.Bean;
23 import org.springframework.context.annotation.Configuration; 25 import org.springframework.context.annotation.Configuration;
24 import org.springframework.core.annotation.Order; 26 import org.springframework.core.annotation.Order;
@@ -34,11 +36,15 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand @@ -34,11 +36,15 @@ import org.springframework.security.web.authentication.AuthenticationFailureHand
34 import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 36 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
35 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 37 import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
36 import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 38 import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
  39 +import org.springframework.web.cors.CorsUtils;
  40 +import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
  41 +import org.springframework.web.filter.CorsFilter;
37 import org.thingsboard.server.exception.ThingsboardErrorResponseHandler; 42 import org.thingsboard.server.exception.ThingsboardErrorResponseHandler;
38 import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider; 43 import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvider;
39 import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter; 44 import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter;
40 import org.thingsboard.server.service.security.auth.jwt.*; 45 import org.thingsboard.server.service.security.auth.jwt.*;
41 import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor; 46 import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor;
  47 +import org.thingsboard.server.service.security.auth.rest.RestPublicLoginProcessingFilter;
42 48
43 import java.util.ArrayList; 49 import java.util.ArrayList;
44 import java.util.Arrays; 50 import java.util.Arrays;
@@ -56,6 +62,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -56,6 +62,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
56 public static final String WEBJARS_ENTRY_POINT = "/webjars/**"; 62 public static final String WEBJARS_ENTRY_POINT = "/webjars/**";
57 public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**"; 63 public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**";
58 public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; 64 public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login";
  65 + public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public";
59 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; 66 public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token";
60 public static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**", "/webjars/**"}; 67 public static final String[] NON_TOKEN_BASED_AUTH_ENTRY_POINTS = new String[] {"/index.html", "/static/**", "/api/noauth/**", "/webjars/**"};
61 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; 68 public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**";
@@ -88,9 +95,17 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -88,9 +95,17 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
88 } 95 }
89 96
90 @Bean 97 @Bean
  98 + protected RestPublicLoginProcessingFilter buildRestPublicLoginProcessingFilter() throws Exception {
  99 + RestPublicLoginProcessingFilter filter = new RestPublicLoginProcessingFilter(PUBLIC_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper);
  100 + filter.setAuthenticationManager(this.authenticationManager);
  101 + return filter;
  102 + }
  103 +
  104 + @Bean
91 protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { 105 protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception {
92 List<String> pathsToSkip = new ArrayList(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); 106 List<String> pathsToSkip = new ArrayList(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS));
93 - pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT)); 107 + pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT,
  108 + PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT));
94 SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); 109 SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT);
95 JwtTokenAuthenticationProcessingFilter filter 110 JwtTokenAuthenticationProcessingFilter filter
96 = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher); 111 = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher);
@@ -136,6 +151,8 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -136,6 +151,8 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
136 protected void configure(HttpSecurity http) throws Exception { 151 protected void configure(HttpSecurity http) throws Exception {
137 http.headers().cacheControl().disable().frameOptions().disable() 152 http.headers().cacheControl().disable().frameOptions().disable()
138 .and() 153 .and()
  154 + .cors()
  155 + .and()
139 .csrf().disable() 156 .csrf().disable()
140 .exceptionHandling() 157 .exceptionHandling()
141 .and() 158 .and()
@@ -146,6 +163,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -146,6 +163,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
146 .antMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars 163 .antMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars
147 .antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API 164 .antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API
148 .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point 165 .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point
  166 + .antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point
149 .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point 167 .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point
150 .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points 168 .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points
151 .and() 169 .and()
@@ -156,8 +174,22 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt @@ -156,8 +174,22 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt
156 .exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) 174 .exceptionHandling().accessDeniedHandler(restAccessDeniedHandler)
157 .and() 175 .and()
158 .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) 176 .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
  177 + .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
159 .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) 178 .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
160 .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) 179 .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class)
161 .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class); 180 .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class);
162 } 181 }
  182 +
  183 +
  184 + @Bean
  185 + @ConditionalOnMissingBean(CorsFilter.class)
  186 + public CorsFilter corsFilter(@Autowired MvcCorsProperties mvcCorsProperties) {
  187 + if (mvcCorsProperties.getMappings().size() == 0) {
  188 + return new CorsFilter(new UrlBasedCorsConfigurationSource());
  189 + } else {
  190 + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
  191 + source.setCorsConfigurations(mvcCorsProperties.getMappings());
  192 + return new CorsFilter(source);
  193 + }
  194 + }
163 } 195 }
@@ -36,6 +36,7 @@ import org.thingsboard.server.exception.ThingsboardException; @@ -36,6 +36,7 @@ import org.thingsboard.server.exception.ThingsboardException;
36 import org.thingsboard.server.service.mail.MailService; 36 import org.thingsboard.server.service.mail.MailService;
37 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository; 37 import org.thingsboard.server.service.security.auth.jwt.RefreshTokenRepository;
38 import org.thingsboard.server.service.security.model.SecurityUser; 38 import org.thingsboard.server.service.security.model.SecurityUser;
  39 +import org.thingsboard.server.service.security.model.UserPrincipal;
39 import org.thingsboard.server.service.security.model.token.JwtToken; 40 import org.thingsboard.server.service.security.model.token.JwtToken;
40 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 41 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
41 42
@@ -167,7 +168,8 @@ public class AuthController extends BaseController { @@ -167,7 +168,8 @@ public class AuthController extends BaseController {
167 String encodedPassword = passwordEncoder.encode(password); 168 String encodedPassword = passwordEncoder.encode(password);
168 UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword); 169 UserCredentials credentials = userService.activateUserCredentials(activateToken, encodedPassword);
169 User user = userService.findUserById(credentials.getUserId()); 170 User user = userService.findUserById(credentials.getUserId());
170 - SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled()); 171 + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
  172 + SecurityUser securityUser = new SecurityUser(user, credentials.isEnabled(), principal);
171 String baseUrl = constructBaseUrl(request); 173 String baseUrl = constructBaseUrl(request);
172 String loginUrl = String.format("%s/login", baseUrl); 174 String loginUrl = String.format("%s/login", baseUrl);
173 String email = user.getEmail(); 175 String email = user.getEmail();
@@ -201,7 +203,8 @@ public class AuthController extends BaseController { @@ -201,7 +203,8 @@ public class AuthController extends BaseController {
201 userCredentials.setResetToken(null); 203 userCredentials.setResetToken(null);
202 userCredentials = userService.saveUserCredentials(userCredentials); 204 userCredentials = userService.saveUserCredentials(userCredentials);
203 User user = userService.findUserById(userCredentials.getUserId()); 205 User user = userService.findUserById(userCredentials.getUserId());
204 - SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled()); 206 + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
  207 + SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), principal);
205 String baseUrl = constructBaseUrl(request); 208 String baseUrl = constructBaseUrl(request);
206 String loginUrl = String.format("%s/login", baseUrl); 209 String loginUrl = String.format("%s/login", baseUrl);
207 String email = user.getEmail(); 210 String email = user.getEmail();
@@ -15,6 +15,9 @@ @@ -15,6 +15,9 @@
15 */ 15 */
16 package org.thingsboard.server.controller; 16 package org.thingsboard.server.controller;
17 17
  18 +import com.fasterxml.jackson.databind.JsonNode;
  19 +import com.fasterxml.jackson.databind.ObjectMapper;
  20 +import com.fasterxml.jackson.databind.node.ObjectNode;
18 import org.springframework.http.HttpStatus; 21 import org.springframework.http.HttpStatus;
19 import org.springframework.security.access.prepost.PreAuthorize; 22 import org.springframework.security.access.prepost.PreAuthorize;
20 import org.springframework.web.bind.annotation.*; 23 import org.springframework.web.bind.annotation.*;
@@ -42,6 +45,28 @@ public class CustomerController extends BaseController { @@ -42,6 +45,28 @@ public class CustomerController extends BaseController {
42 } 45 }
43 } 46 }
44 47
  48 + @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
  49 + @RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET)
  50 + @ResponseBody
  51 + public JsonNode getShortCustomerInfoById(@PathVariable("customerId") String strCustomerId) throws ThingsboardException {
  52 + checkParameter("customerId", strCustomerId);
  53 + try {
  54 + CustomerId customerId = new CustomerId(toUUID(strCustomerId));
  55 + Customer customer = checkCustomerId(customerId);
  56 + ObjectMapper objectMapper = new ObjectMapper();
  57 + ObjectNode infoObject = objectMapper.createObjectNode();
  58 + infoObject.put("title", customer.getTitle());
  59 + boolean isPublic = false;
  60 + if (customer.getAdditionalInfo() != null && customer.getAdditionalInfo().has("isPublic")) {
  61 + isPublic = customer.getAdditionalInfo().get("isPublic").asBoolean();
  62 + }
  63 + infoObject.put("isPublic", isPublic);
  64 + return infoObject;
  65 + } catch (Exception e) {
  66 + throw handleException(e);
  67 + }
  68 + }
  69 +
45 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 70 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
46 @RequestMapping(value = "/customer", method = RequestMethod.POST) 71 @RequestMapping(value = "/customer", method = RequestMethod.POST)
47 @ResponseBody 72 @ResponseBody
@@ -18,6 +18,7 @@ package org.thingsboard.server.controller; @@ -18,6 +18,7 @@ package org.thingsboard.server.controller;
18 import org.springframework.http.HttpStatus; 18 import org.springframework.http.HttpStatus;
19 import org.springframework.security.access.prepost.PreAuthorize; 19 import org.springframework.security.access.prepost.PreAuthorize;
20 import org.springframework.web.bind.annotation.*; 20 import org.springframework.web.bind.annotation.*;
  21 +import org.thingsboard.server.common.data.Customer;
21 import org.thingsboard.server.common.data.Dashboard; 22 import org.thingsboard.server.common.data.Dashboard;
22 import org.thingsboard.server.common.data.DashboardInfo; 23 import org.thingsboard.server.common.data.DashboardInfo;
23 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
@@ -117,6 +118,21 @@ public class DashboardController extends BaseController { @@ -117,6 +118,21 @@ public class DashboardController extends BaseController {
117 } 118 }
118 119
119 @PreAuthorize("hasAuthority('TENANT_ADMIN')") 120 @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  121 + @RequestMapping(value = "/customer/public/dashboard/{dashboardId}", method = RequestMethod.POST)
  122 + @ResponseBody
  123 + public Dashboard assignDashboardToPublicCustomer(@PathVariable("dashboardId") String strDashboardId) throws ThingsboardException {
  124 + checkParameter("dashboardId", strDashboardId);
  125 + try {
  126 + DashboardId dashboardId = new DashboardId(toUUID(strDashboardId));
  127 + Dashboard dashboard = checkDashboardId(dashboardId);
  128 + Customer publicCustomer = customerService.findOrCreatePublicCustomer(dashboard.getTenantId());
  129 + return checkNotNull(dashboardService.assignDashboardToCustomer(dashboardId, publicCustomer.getId()));
  130 + } catch (Exception e) {
  131 + throw handleException(e);
  132 + }
  133 + }
  134 +
  135 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
120 @RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET) 136 @RequestMapping(value = "/tenant/dashboards", params = { "limit" }, method = RequestMethod.GET)
121 @ResponseBody 137 @ResponseBody
122 public TextPageData<DashboardInfo> getTenantDashboards( 138 public TextPageData<DashboardInfo> getTenantDashboards(
@@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture; @@ -19,6 +19,7 @@ import com.google.common.util.concurrent.ListenableFuture;
19 import org.springframework.http.HttpStatus; 19 import org.springframework.http.HttpStatus;
20 import org.springframework.security.access.prepost.PreAuthorize; 20 import org.springframework.security.access.prepost.PreAuthorize;
21 import org.springframework.web.bind.annotation.*; 21 import org.springframework.web.bind.annotation.*;
  22 +import org.thingsboard.server.common.data.Customer;
22 import org.thingsboard.server.common.data.Device; 23 import org.thingsboard.server.common.data.Device;
23 import org.thingsboard.server.common.data.id.CustomerId; 24 import org.thingsboard.server.common.data.id.CustomerId;
24 import org.thingsboard.server.common.data.id.DeviceId; 25 import org.thingsboard.server.common.data.id.DeviceId;
@@ -117,6 +118,21 @@ public class DeviceController extends BaseController { @@ -117,6 +118,21 @@ public class DeviceController extends BaseController {
117 } 118 }
118 } 119 }
119 120
  121 + @PreAuthorize("hasAuthority('TENANT_ADMIN')")
  122 + @RequestMapping(value = "/customer/public/device/{deviceId}", method = RequestMethod.POST)
  123 + @ResponseBody
  124 + public Device assignDeviceToPublicCustomer(@PathVariable("deviceId") String strDeviceId) throws ThingsboardException {
  125 + checkParameter("deviceId", strDeviceId);
  126 + try {
  127 + DeviceId deviceId = new DeviceId(toUUID(strDeviceId));
  128 + Device device = checkDeviceId(deviceId);
  129 + Customer publicCustomer = customerService.findOrCreatePublicCustomer(device.getTenantId());
  130 + return checkNotNull(deviceService.assignDeviceToCustomer(deviceId, publicCustomer.getId()));
  131 + } catch (Exception e) {
  132 + throw handleException(e);
  133 + }
  134 + }
  135 +
120 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") 136 @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')")
121 @RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET) 137 @RequestMapping(value = "/device/{deviceId}/credentials", method = RequestMethod.GET)
122 @ResponseBody 138 @ResponseBody
@@ -16,32 +16,40 @@ @@ -16,32 +16,40 @@
16 package org.thingsboard.server.service.security.auth.jwt; 16 package org.thingsboard.server.service.security.auth.jwt;
17 17
18 import org.springframework.beans.factory.annotation.Autowired; 18 import org.springframework.beans.factory.annotation.Autowired;
19 -import org.springframework.security.authentication.AuthenticationProvider;  
20 -import org.springframework.security.authentication.DisabledException;  
21 -import org.springframework.security.authentication.InsufficientAuthenticationException;  
22 -import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 19 +import org.springframework.security.authentication.*;
23 import org.springframework.security.core.Authentication; 20 import org.springframework.security.core.Authentication;
24 import org.springframework.security.core.AuthenticationException; 21 import org.springframework.security.core.AuthenticationException;
25 import org.springframework.security.core.userdetails.UsernameNotFoundException; 22 import org.springframework.security.core.userdetails.UsernameNotFoundException;
26 import org.springframework.stereotype.Component; 23 import org.springframework.stereotype.Component;
27 import org.springframework.util.Assert; 24 import org.springframework.util.Assert;
  25 +import org.thingsboard.server.common.data.Customer;
28 import org.thingsboard.server.common.data.User; 26 import org.thingsboard.server.common.data.User;
  27 +import org.thingsboard.server.common.data.id.CustomerId;
  28 +import org.thingsboard.server.common.data.id.UUIDBased;
  29 +import org.thingsboard.server.common.data.id.UserId;
  30 +import org.thingsboard.server.common.data.security.Authority;
29 import org.thingsboard.server.common.data.security.UserCredentials; 31 import org.thingsboard.server.common.data.security.UserCredentials;
  32 +import org.thingsboard.server.dao.customer.CustomerService;
30 import org.thingsboard.server.dao.user.UserService; 33 import org.thingsboard.server.dao.user.UserService;
31 import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken; 34 import org.thingsboard.server.service.security.auth.RefreshAuthenticationToken;
32 import org.thingsboard.server.service.security.model.SecurityUser; 35 import org.thingsboard.server.service.security.model.SecurityUser;
  36 +import org.thingsboard.server.service.security.model.UserPrincipal;
33 import org.thingsboard.server.service.security.model.token.JwtTokenFactory; 37 import org.thingsboard.server.service.security.model.token.JwtTokenFactory;
34 import org.thingsboard.server.service.security.model.token.RawAccessJwtToken; 38 import org.thingsboard.server.service.security.model.token.RawAccessJwtToken;
35 39
  40 +import java.util.UUID;
  41 +
36 @Component 42 @Component
37 public class RefreshTokenAuthenticationProvider implements AuthenticationProvider { 43 public class RefreshTokenAuthenticationProvider implements AuthenticationProvider {
38 44
39 private final JwtTokenFactory tokenFactory; 45 private final JwtTokenFactory tokenFactory;
40 private final UserService userService; 46 private final UserService userService;
  47 + private final CustomerService customerService;
41 48
42 @Autowired 49 @Autowired
43 - public RefreshTokenAuthenticationProvider(final UserService userService, final JwtTokenFactory tokenFactory) { 50 + public RefreshTokenAuthenticationProvider(final UserService userService, final CustomerService customerService, final JwtTokenFactory tokenFactory) {
44 this.userService = userService; 51 this.userService = userService;
  52 + this.customerService = customerService;
45 this.tokenFactory = tokenFactory; 53 this.tokenFactory = tokenFactory;
46 } 54 }
47 55
@@ -50,8 +58,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide @@ -50,8 +58,18 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
50 Assert.notNull(authentication, "No authentication data provided"); 58 Assert.notNull(authentication, "No authentication data provided");
51 RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials(); 59 RawAccessJwtToken rawAccessToken = (RawAccessJwtToken) authentication.getCredentials();
52 SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken); 60 SecurityUser unsafeUser = tokenFactory.parseRefreshToken(rawAccessToken);
  61 + UserPrincipal principal = unsafeUser.getUserPrincipal();
  62 + SecurityUser securityUser;
  63 + if (principal.getType() == UserPrincipal.Type.USER_NAME) {
  64 + securityUser = authenticateByUserId(unsafeUser.getId());
  65 + } else {
  66 + securityUser = authenticateByPublicId(principal.getValue());
  67 + }
  68 + return new RefreshAuthenticationToken(securityUser);
  69 + }
53 70
54 - User user = userService.findUserById(unsafeUser.getId()); 71 + private SecurityUser authenticateByUserId(UserId userId) {
  72 + User user = userService.findUserById(userId);
55 if (user == null) { 73 if (user == null) {
56 throw new UsernameNotFoundException("User not found by refresh token"); 74 throw new UsernameNotFoundException("User not found by refresh token");
57 } 75 }
@@ -67,9 +85,44 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide @@ -67,9 +85,44 @@ public class RefreshTokenAuthenticationProvider implements AuthenticationProvide
67 85
68 if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned"); 86 if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
69 87
70 - SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled()); 88 + UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.USER_NAME, user.getEmail());
71 89
72 - return new RefreshAuthenticationToken(securityUser); 90 + SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
  91 +
  92 + return securityUser;
  93 + }
  94 +
  95 + private SecurityUser authenticateByPublicId(String publicId) {
  96 + CustomerId customerId;
  97 + try {
  98 + customerId = new CustomerId(UUID.fromString(publicId));
  99 + } catch (Exception e) {
  100 + throw new BadCredentialsException("Refresh token is not valid");
  101 + }
  102 + Customer publicCustomer = customerService.findCustomerById(customerId);
  103 + if (publicCustomer == null) {
  104 + throw new UsernameNotFoundException("Public entity not found by refresh token");
  105 + }
  106 + boolean isPublic = false;
  107 + if (publicCustomer.getAdditionalInfo() != null && publicCustomer.getAdditionalInfo().has("isPublic")) {
  108 + isPublic = publicCustomer.getAdditionalInfo().get("isPublic").asBoolean();
  109 + }
  110 + if (!isPublic) {
  111 + throw new BadCredentialsException("Refresh token is not valid");
  112 + }
  113 + User user = new User(new UserId(UUIDBased.EMPTY));
  114 + user.setTenantId(publicCustomer.getTenantId());
  115 + user.setCustomerId(publicCustomer.getId());
  116 + user.setEmail(publicId);
  117 + user.setAuthority(Authority.CUSTOMER_USER);
  118 + user.setFirstName("Public");
  119 + user.setLastName("Public");
  120 +
  121 + UserPrincipal userPrincipal = new UserPrincipal(UserPrincipal.Type.PUBLIC_ID, publicId);
  122 +
  123 + SecurityUser securityUser = new SecurityUser(user, true, userPrincipal);
  124 +
  125 + return securityUser;
73 } 126 }
74 127
75 @Override 128 @Override
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.rest;
  17 +
  18 +import com.fasterxml.jackson.annotation.JsonCreator;
  19 +import com.fasterxml.jackson.annotation.JsonProperty;
  20 +
  21 +public class PublicLoginRequest {
  22 +
  23 + private String publicId;
  24 +
  25 + @JsonCreator
  26 + public PublicLoginRequest(@JsonProperty("publicId") String publicId) {
  27 + this.publicId = publicId;
  28 + }
  29 +
  30 + public String getPublicId() {
  31 + return publicId;
  32 + }
  33 +
  34 +}
@@ -23,20 +23,31 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException; @@ -23,20 +23,31 @@ import org.springframework.security.core.userdetails.UsernameNotFoundException;
23 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 23 import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
24 import org.springframework.stereotype.Component; 24 import org.springframework.stereotype.Component;
25 import org.springframework.util.Assert; 25 import org.springframework.util.Assert;
  26 +import org.thingsboard.server.common.data.Customer;
26 import org.thingsboard.server.common.data.User; 27 import org.thingsboard.server.common.data.User;
  28 +import org.thingsboard.server.common.data.id.CustomerId;
  29 +import org.thingsboard.server.common.data.id.UUIDBased;
  30 +import org.thingsboard.server.common.data.id.UserId;
  31 +import org.thingsboard.server.common.data.security.Authority;
27 import org.thingsboard.server.common.data.security.UserCredentials; 32 import org.thingsboard.server.common.data.security.UserCredentials;
  33 +import org.thingsboard.server.dao.customer.CustomerService;
28 import org.thingsboard.server.dao.user.UserService; 34 import org.thingsboard.server.dao.user.UserService;
29 import org.thingsboard.server.service.security.model.SecurityUser; 35 import org.thingsboard.server.service.security.model.SecurityUser;
  36 +import org.thingsboard.server.service.security.model.UserPrincipal;
  37 +
  38 +import java.util.UUID;
30 39
31 @Component 40 @Component
32 public class RestAuthenticationProvider implements AuthenticationProvider { 41 public class RestAuthenticationProvider implements AuthenticationProvider {
33 42
34 private final BCryptPasswordEncoder encoder; 43 private final BCryptPasswordEncoder encoder;
35 private final UserService userService; 44 private final UserService userService;
  45 + private final CustomerService customerService;
36 46
37 @Autowired 47 @Autowired
38 - public RestAuthenticationProvider(final UserService userService, final BCryptPasswordEncoder encoder) { 48 + public RestAuthenticationProvider(final UserService userService, final CustomerService customerService, final BCryptPasswordEncoder encoder) {
39 this.userService = userService; 49 this.userService = userService;
  50 + this.customerService = customerService;
40 this.encoder = encoder; 51 this.encoder = encoder;
41 } 52 }
42 53
@@ -44,9 +55,23 @@ public class RestAuthenticationProvider implements AuthenticationProvider { @@ -44,9 +55,23 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
44 public Authentication authenticate(Authentication authentication) throws AuthenticationException { 55 public Authentication authenticate(Authentication authentication) throws AuthenticationException {
45 Assert.notNull(authentication, "No authentication data provided"); 56 Assert.notNull(authentication, "No authentication data provided");
46 57
47 - String username = (String) authentication.getPrincipal();  
48 - String password = (String) authentication.getCredentials(); 58 + Object principal = authentication.getPrincipal();
  59 + if (!(principal instanceof UserPrincipal)) {
  60 + throw new BadCredentialsException("Authentication Failed. Bad user principal.");
  61 + }
49 62
  63 + UserPrincipal userPrincipal = (UserPrincipal) principal;
  64 + if (userPrincipal.getType() == UserPrincipal.Type.USER_NAME) {
  65 + String username = userPrincipal.getValue();
  66 + String password = (String) authentication.getCredentials();
  67 + return authenticateByUsernameAndPassword(userPrincipal, username, password);
  68 + } else {
  69 + String publicId = userPrincipal.getValue();
  70 + return authenticateByPublicId(userPrincipal, publicId);
  71 + }
  72 + }
  73 +
  74 + private Authentication authenticateByUsernameAndPassword(UserPrincipal userPrincipal, String username, String password) {
50 User user = userService.findUserByEmail(username); 75 User user = userService.findUserByEmail(username);
51 if (user == null) { 76 if (user == null) {
52 throw new UsernameNotFoundException("User not found: " + username); 77 throw new UsernameNotFoundException("User not found: " + username);
@@ -67,7 +92,38 @@ public class RestAuthenticationProvider implements AuthenticationProvider { @@ -67,7 +92,38 @@ public class RestAuthenticationProvider implements AuthenticationProvider {
67 92
68 if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned"); 93 if (user.getAuthority() == null) throw new InsufficientAuthenticationException("User has no authority assigned");
69 94
70 - SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled()); 95 + SecurityUser securityUser = new SecurityUser(user, userCredentials.isEnabled(), userPrincipal);
  96 +
  97 + return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
  98 + }
  99 +
  100 + private Authentication authenticateByPublicId(UserPrincipal userPrincipal, String publicId) {
  101 + CustomerId customerId;
  102 + try {
  103 + customerId = new CustomerId(UUID.fromString(publicId));
  104 + } catch (Exception e) {
  105 + throw new BadCredentialsException("Authentication Failed. Public Id is not valid.");
  106 + }
  107 + Customer publicCustomer = customerService.findCustomerById(customerId);
  108 + if (publicCustomer == null) {
  109 + throw new UsernameNotFoundException("Public entity not found: " + publicId);
  110 + }
  111 + boolean isPublic = false;
  112 + if (publicCustomer.getAdditionalInfo() != null && publicCustomer.getAdditionalInfo().has("isPublic")) {
  113 + isPublic = publicCustomer.getAdditionalInfo().get("isPublic").asBoolean();
  114 + }
  115 + if (!isPublic) {
  116 + throw new BadCredentialsException("Authentication Failed. Public Id is not valid.");
  117 + }
  118 + User user = new User(new UserId(UUIDBased.EMPTY));
  119 + user.setTenantId(publicCustomer.getTenantId());
  120 + user.setCustomerId(publicCustomer.getId());
  121 + user.setEmail(publicId);
  122 + user.setAuthority(Authority.CUSTOMER_USER);
  123 + user.setFirstName("Public");
  124 + user.setLastName("Public");
  125 +
  126 + SecurityUser securityUser = new SecurityUser(user, true, userPrincipal);
71 127
72 return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities()); 128 return new UsernamePasswordAuthenticationToken(securityUser, null, securityUser.getAuthorities());
73 } 129 }
@@ -29,6 +29,7 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro @@ -29,6 +29,7 @@ import org.springframework.security.web.authentication.AbstractAuthenticationPro
29 import org.springframework.security.web.authentication.AuthenticationFailureHandler; 29 import org.springframework.security.web.authentication.AuthenticationFailureHandler;
30 import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 30 import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
31 import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException; 31 import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
  32 +import org.thingsboard.server.service.security.model.UserPrincipal;
32 33
33 import javax.servlet.FilterChain; 34 import javax.servlet.FilterChain;
34 import javax.servlet.ServletException; 35 import javax.servlet.ServletException;
@@ -73,7 +74,9 @@ public class RestLoginProcessingFilter extends AbstractAuthenticationProcessingF @@ -73,7 +74,9 @@ public class RestLoginProcessingFilter extends AbstractAuthenticationProcessingF
73 throw new AuthenticationServiceException("Username or Password not provided"); 74 throw new AuthenticationServiceException("Username or Password not provided");
74 } 75 }
75 76
76 - UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(loginRequest.getUsername(), loginRequest.getPassword()); 77 + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.USER_NAME, loginRequest.getUsername());
  78 +
  79 + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, loginRequest.getPassword());
77 80
78 return this.getAuthenticationManager().authenticate(token); 81 return this.getAuthenticationManager().authenticate(token);
79 } 82 }
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +package org.thingsboard.server.service.security.auth.rest;
  17 +
  18 +import com.fasterxml.jackson.databind.ObjectMapper;
  19 +import org.apache.commons.lang3.StringUtils;
  20 +import org.slf4j.Logger;
  21 +import org.slf4j.LoggerFactory;
  22 +import org.springframework.http.HttpMethod;
  23 +import org.springframework.security.authentication.AuthenticationServiceException;
  24 +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
  25 +import org.springframework.security.core.Authentication;
  26 +import org.springframework.security.core.AuthenticationException;
  27 +import org.springframework.security.core.context.SecurityContextHolder;
  28 +import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter;
  29 +import org.springframework.security.web.authentication.AuthenticationFailureHandler;
  30 +import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
  31 +import org.thingsboard.server.service.security.exception.AuthMethodNotSupportedException;
  32 +import org.thingsboard.server.service.security.model.UserPrincipal;
  33 +
  34 +import javax.servlet.FilterChain;
  35 +import javax.servlet.ServletException;
  36 +import javax.servlet.http.HttpServletRequest;
  37 +import javax.servlet.http.HttpServletResponse;
  38 +import java.io.IOException;
  39 +
  40 +public class RestPublicLoginProcessingFilter extends AbstractAuthenticationProcessingFilter {
  41 + private static Logger logger = LoggerFactory.getLogger(RestPublicLoginProcessingFilter.class);
  42 +
  43 + private final AuthenticationSuccessHandler successHandler;
  44 + private final AuthenticationFailureHandler failureHandler;
  45 +
  46 + private final ObjectMapper objectMapper;
  47 +
  48 + public RestPublicLoginProcessingFilter(String defaultProcessUrl, AuthenticationSuccessHandler successHandler,
  49 + AuthenticationFailureHandler failureHandler, ObjectMapper mapper) {
  50 + super(defaultProcessUrl);
  51 + this.successHandler = successHandler;
  52 + this.failureHandler = failureHandler;
  53 + this.objectMapper = mapper;
  54 + }
  55 +
  56 + @Override
  57 + public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
  58 + throws AuthenticationException, IOException, ServletException {
  59 + if (!HttpMethod.POST.name().equals(request.getMethod())) {
  60 + if(logger.isDebugEnabled()) {
  61 + logger.debug("Authentication method not supported. Request method: " + request.getMethod());
  62 + }
  63 + throw new AuthMethodNotSupportedException("Authentication method not supported");
  64 + }
  65 +
  66 + PublicLoginRequest loginRequest;
  67 + try {
  68 + loginRequest = objectMapper.readValue(request.getReader(), PublicLoginRequest.class);
  69 + } catch (Exception e) {
  70 + throw new AuthenticationServiceException("Invalid public login request payload");
  71 + }
  72 +
  73 + if (StringUtils.isBlank(loginRequest.getPublicId())) {
  74 + throw new AuthenticationServiceException("Public Id is not provided");
  75 + }
  76 +
  77 + UserPrincipal principal = new UserPrincipal(UserPrincipal.Type.PUBLIC_ID, loginRequest.getPublicId());
  78 +
  79 + UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(principal, "");
  80 +
  81 + return this.getAuthenticationManager().authenticate(token);
  82 + }
  83 +
  84 + @Override
  85 + protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
  86 + Authentication authResult) throws IOException, ServletException {
  87 + successHandler.onAuthenticationSuccess(request, response, authResult);
  88 + }
  89 +
  90 + @Override
  91 + protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response,
  92 + AuthenticationException failed) throws IOException, ServletException {
  93 + SecurityContextHolder.clearContext();
  94 + failureHandler.onAuthenticationFailure(request, response, failed);
  95 + }
  96 +}
@@ -30,6 +30,7 @@ public class SecurityUser extends User { @@ -30,6 +30,7 @@ public class SecurityUser extends User {
30 30
31 private Collection<GrantedAuthority> authorities; 31 private Collection<GrantedAuthority> authorities;
32 private boolean enabled; 32 private boolean enabled;
  33 + private UserPrincipal userPrincipal;
33 34
34 public SecurityUser() { 35 public SecurityUser() {
35 super(); 36 super();
@@ -39,9 +40,10 @@ public class SecurityUser extends User { @@ -39,9 +40,10 @@ public class SecurityUser extends User {
39 super(id); 40 super(id);
40 } 41 }
41 42
42 - public SecurityUser(User user, boolean enabled) { 43 + public SecurityUser(User user, boolean enabled, UserPrincipal userPrincipal) {
43 super(user); 44 super(user);
44 this.enabled = enabled; 45 this.enabled = enabled;
  46 + this.userPrincipal = userPrincipal;
45 } 47 }
46 48
47 public Collection<? extends GrantedAuthority> getAuthorities() { 49 public Collection<? extends GrantedAuthority> getAuthorities() {
@@ -57,8 +59,16 @@ public class SecurityUser extends User { @@ -57,8 +59,16 @@ public class SecurityUser extends User {
57 return enabled; 59 return enabled;
58 } 60 }
59 61
  62 + public UserPrincipal getUserPrincipal() {
  63 + return userPrincipal;
  64 + }
  65 +
60 public void setEnabled(boolean enabled) { 66 public void setEnabled(boolean enabled) {
61 this.enabled = enabled; 67 this.enabled = enabled;
62 } 68 }
63 69
  70 + public void setUserPrincipal(UserPrincipal userPrincipal) {
  71 + this.userPrincipal = userPrincipal;
  72 + }
  73 +
64 } 74 }
  1 +/**
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +package org.thingsboard.server.service.security.model;
  18 +
  19 +public class UserPrincipal {
  20 +
  21 + private final Type type;
  22 + private final String value;
  23 +
  24 + public UserPrincipal(Type type, String value) {
  25 + this.type = type;
  26 + this.value = value;
  27 + }
  28 +
  29 + public Type getType() {
  30 + return type;
  31 + }
  32 +
  33 + public String getValue() {
  34 + return value;
  35 + }
  36 +
  37 + public enum Type {
  38 + USER_NAME,
  39 + PUBLIC_ID
  40 + }
  41 +
  42 +}
@@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.UserId; @@ -29,6 +29,7 @@ import org.thingsboard.server.common.data.id.UserId;
29 import org.thingsboard.server.common.data.security.Authority; 29 import org.thingsboard.server.common.data.security.Authority;
30 import org.thingsboard.server.config.JwtSettings; 30 import org.thingsboard.server.config.JwtSettings;
31 import org.thingsboard.server.service.security.model.SecurityUser; 31 import org.thingsboard.server.service.security.model.SecurityUser;
  32 +import org.thingsboard.server.service.security.model.UserPrincipal;
32 33
33 import java.util.Arrays; 34 import java.util.Arrays;
34 import java.util.List; 35 import java.util.List;
@@ -43,6 +44,7 @@ public class JwtTokenFactory { @@ -43,6 +44,7 @@ public class JwtTokenFactory {
43 private static final String FIRST_NAME = "firstName"; 44 private static final String FIRST_NAME = "firstName";
44 private static final String LAST_NAME = "lastName"; 45 private static final String LAST_NAME = "lastName";
45 private static final String ENABLED = "enabled"; 46 private static final String ENABLED = "enabled";
  47 + private static final String IS_PUBLIC = "isPublic";
46 private static final String TENANT_ID = "tenantId"; 48 private static final String TENANT_ID = "tenantId";
47 private static final String CUSTOMER_ID = "customerId"; 49 private static final String CUSTOMER_ID = "customerId";
48 50
@@ -63,12 +65,15 @@ public class JwtTokenFactory { @@ -63,12 +65,15 @@ public class JwtTokenFactory {
63 if (securityUser.getAuthority() == null) 65 if (securityUser.getAuthority() == null)
64 throw new IllegalArgumentException("User doesn't have any privileges"); 66 throw new IllegalArgumentException("User doesn't have any privileges");
65 67
66 - Claims claims = Jwts.claims().setSubject(securityUser.getEmail()); 68 + UserPrincipal principal = securityUser.getUserPrincipal();
  69 + String subject = principal.getValue();
  70 + Claims claims = Jwts.claims().setSubject(subject);
67 claims.put(SCOPES, securityUser.getAuthorities().stream().map(s -> s.getAuthority()).collect(Collectors.toList())); 71 claims.put(SCOPES, securityUser.getAuthorities().stream().map(s -> s.getAuthority()).collect(Collectors.toList()));
68 claims.put(USER_ID, securityUser.getId().getId().toString()); 72 claims.put(USER_ID, securityUser.getId().getId().toString());
69 claims.put(FIRST_NAME, securityUser.getFirstName()); 73 claims.put(FIRST_NAME, securityUser.getFirstName());
70 claims.put(LAST_NAME, securityUser.getLastName()); 74 claims.put(LAST_NAME, securityUser.getLastName());
71 claims.put(ENABLED, securityUser.isEnabled()); 75 claims.put(ENABLED, securityUser.isEnabled());
  76 + claims.put(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID);
72 if (securityUser.getTenantId() != null) { 77 if (securityUser.getTenantId() != null) {
73 claims.put(TENANT_ID, securityUser.getTenantId().getId().toString()); 78 claims.put(TENANT_ID, securityUser.getTenantId().getId().toString());
74 } 79 }
@@ -104,6 +109,9 @@ public class JwtTokenFactory { @@ -104,6 +109,9 @@ public class JwtTokenFactory {
104 securityUser.setFirstName(claims.get(FIRST_NAME, String.class)); 109 securityUser.setFirstName(claims.get(FIRST_NAME, String.class));
105 securityUser.setLastName(claims.get(LAST_NAME, String.class)); 110 securityUser.setLastName(claims.get(LAST_NAME, String.class));
106 securityUser.setEnabled(claims.get(ENABLED, Boolean.class)); 111 securityUser.setEnabled(claims.get(ENABLED, Boolean.class));
  112 + boolean isPublic = claims.get(IS_PUBLIC, Boolean.class);
  113 + UserPrincipal principal = new UserPrincipal(isPublic ? UserPrincipal.Type.PUBLIC_ID : UserPrincipal.Type.USER_NAME, subject);
  114 + securityUser.setUserPrincipal(principal);
107 String tenantId = claims.get(TENANT_ID, String.class); 115 String tenantId = claims.get(TENANT_ID, String.class);
108 if (tenantId != null) { 116 if (tenantId != null) {
109 securityUser.setTenantId(new TenantId(UUID.fromString(tenantId))); 117 securityUser.setTenantId(new TenantId(UUID.fromString(tenantId)));
@@ -123,9 +131,11 @@ public class JwtTokenFactory { @@ -123,9 +131,11 @@ public class JwtTokenFactory {
123 131
124 DateTime currentTime = new DateTime(); 132 DateTime currentTime = new DateTime();
125 133
126 - Claims claims = Jwts.claims().setSubject(securityUser.getEmail()); 134 + UserPrincipal principal = securityUser.getUserPrincipal();
  135 + Claims claims = Jwts.claims().setSubject(principal.getValue());
127 claims.put(SCOPES, Arrays.asList(Authority.REFRESH_TOKEN.name())); 136 claims.put(SCOPES, Arrays.asList(Authority.REFRESH_TOKEN.name()));
128 claims.put(USER_ID, securityUser.getId().getId().toString()); 137 claims.put(USER_ID, securityUser.getId().getId().toString());
  138 + claims.put(IS_PUBLIC, principal.getType() == UserPrincipal.Type.PUBLIC_ID);
129 139
130 String token = Jwts.builder() 140 String token = Jwts.builder()
131 .setClaims(claims) 141 .setClaims(claims)
@@ -150,8 +160,10 @@ public class JwtTokenFactory { @@ -150,8 +160,10 @@ public class JwtTokenFactory {
150 if (!scopes.get(0).equals(Authority.REFRESH_TOKEN.name())) { 160 if (!scopes.get(0).equals(Authority.REFRESH_TOKEN.name())) {
151 throw new IllegalArgumentException("Invalid Refresh Token scope"); 161 throw new IllegalArgumentException("Invalid Refresh Token scope");
152 } 162 }
  163 + boolean isPublic = claims.get(IS_PUBLIC, Boolean.class);
  164 + UserPrincipal principal = new UserPrincipal(isPublic ? UserPrincipal.Type.PUBLIC_ID : UserPrincipal.Type.USER_NAME, subject);
153 SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class)))); 165 SecurityUser securityUser = new SecurityUser(new UserId(UUID.fromString(claims.get(USER_ID, String.class))));
154 - securityUser.setEmail(subject); 166 + securityUser.setUserPrincipal(principal);
155 return securityUser; 167 return securityUser;
156 } 168 }
157 169
@@ -188,3 +188,25 @@ cache: @@ -188,3 +188,25 @@ cache:
188 updates: 188 updates:
189 # Enable/disable updates checking. 189 # Enable/disable updates checking.
190 enabled: "${UPDATES_ENABLED:true}" 190 enabled: "${UPDATES_ENABLED:true}"
  191 +
  192 + # spring CORS configuration
  193 +spring.mvc.cors:
  194 + mappings:
  195 + # Intercept path
  196 + "/api/auth/**":
  197 + #Comma-separated list of origins to allow. '*' allows all origins. When not set,CORS support is disabled.
  198 + allowed-origins: "*"
  199 + #Comma-separated list of methods to allow. '*' allows all methods.
  200 + allowed-methods: "POST,GET,OPTIONS"
  201 + #Comma-separated list of headers to allow in a request. '*' allows all headers.
  202 + allowed-headers: "*"
  203 + #How long, in seconds, the response from a pre-flight request can be cached by clients.
  204 + max-age: "1800"
  205 + #Set whether credentials are supported. When not set, credentials are not supported.
  206 + allow-credentials: "true"
  207 + "/api/v1/**":
  208 + allowed-origins: "*"
  209 + allowed-methods: "*"
  210 + allowed-headers: "*"
  211 + max-age: "1800"
  212 + allow-credentials: "true"
@@ -2,9 +2,6 @@ @@ -2,9 +2,6 @@
2 2
3 setlocal ENABLEEXTENSIONS 3 setlocal ENABLEEXTENSIONS
4 4
5 -IF %PROCESSOR_ARCHITECTURE%==AMD64 GOTO CHECK_JAVA_64  
6 -IF %PROCESSOR_ARCHITECTURE%==x86 GOTO CHECK_JAVA_32  
7 -  
8 @ECHO Detecting Java version installed. 5 @ECHO Detecting Java version installed.
9 :CHECK_JAVA_64 6 :CHECK_JAVA_64
10 @ECHO Detecting if it is 64 bit machine 7 @ECHO Detecting if it is 64 bit machine
@@ -36,7 +33,6 @@ if defined ValueName ( @@ -36,7 +33,6 @@ if defined ValueName (
36 ) 33 )
37 34
38 IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED 35 IF NOT "%JRE_PATH2%" == "" GOTO JAVA_INSTALLED
39 -IF "%JRE_PATH2%" == "" GOTO JAVA_NOT_INSTALLED  
40 36
41 :CHECK_JAVA_32 37 :CHECK_JAVA_32
42 @ECHO Detecting if it is 32 bit machine 38 @ECHO Detecting if it is 32 bit machine
@@ -288,7 +288,7 @@ public class CustomerControllerTest extends AbstractControllerTest { @@ -288,7 +288,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
288 for (int i=0;i<143;i++) { 288 for (int i=0;i<143;i++) {
289 Customer customer = new Customer(); 289 Customer customer = new Customer();
290 customer.setTenantId(tenantId); 290 customer.setTenantId(tenantId);
291 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); 291 + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
292 String title = title1+suffix; 292 String title = title1+suffix;
293 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); 293 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
294 customer.setTitle(title); 294 customer.setTitle(title);
@@ -299,7 +299,7 @@ public class CustomerControllerTest extends AbstractControllerTest { @@ -299,7 +299,7 @@ public class CustomerControllerTest extends AbstractControllerTest {
299 for (int i=0;i<175;i++) { 299 for (int i=0;i<175;i++) {
300 Customer customer = new Customer(); 300 Customer customer = new Customer();
301 customer.setTenantId(tenantId); 301 customer.setTenantId(tenantId);
302 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); 302 + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
303 String title = title2+suffix; 303 String title = title2+suffix;
304 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); 304 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
305 customer.setTitle(title); 305 customer.setTitle(title);
@@ -149,7 +149,7 @@ public class TenantControllerTest extends AbstractControllerTest { @@ -149,7 +149,7 @@ public class TenantControllerTest extends AbstractControllerTest {
149 List<Tenant> tenantsTitle1 = new ArrayList<>(); 149 List<Tenant> tenantsTitle1 = new ArrayList<>();
150 for (int i=0;i<134;i++) { 150 for (int i=0;i<134;i++) {
151 Tenant tenant = new Tenant(); 151 Tenant tenant = new Tenant();
152 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); 152 + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
153 String title = title1+suffix; 153 String title = title1+suffix;
154 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); 154 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
155 tenant.setTitle(title); 155 tenant.setTitle(title);
@@ -159,7 +159,7 @@ public class TenantControllerTest extends AbstractControllerTest { @@ -159,7 +159,7 @@ public class TenantControllerTest extends AbstractControllerTest {
159 List<Tenant> tenantsTitle2 = new ArrayList<>(); 159 List<Tenant> tenantsTitle2 = new ArrayList<>();
160 for (int i=0;i<127;i++) { 160 for (int i=0;i<127;i++) {
161 Tenant tenant = new Tenant(); 161 Tenant tenant = new Tenant();
162 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15)); 162 + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
163 String title = title2+suffix; 163 String title = title2+suffix;
164 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); 164 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
165 tenant.setTitle(title); 165 tenant.setTitle(title);
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>common</artifactId> 24 <artifactId>common</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.common</groupId> 26 <groupId>org.thingsboard.common</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -16,12 +16,14 @@ @@ -16,12 +16,14 @@
16 package org.thingsboard.server.dao.customer; 16 package org.thingsboard.server.dao.customer;
17 17
18 import java.util.List; 18 import java.util.List;
  19 +import java.util.Optional;
19 import java.util.UUID; 20 import java.util.UUID;
20 21
21 import org.thingsboard.server.common.data.Customer; 22 import org.thingsboard.server.common.data.Customer;
22 import org.thingsboard.server.common.data.page.TextPageLink; 23 import org.thingsboard.server.common.data.page.TextPageLink;
23 import org.thingsboard.server.dao.Dao; 24 import org.thingsboard.server.dao.Dao;
24 import org.thingsboard.server.dao.model.CustomerEntity; 25 import org.thingsboard.server.dao.model.CustomerEntity;
  26 +import org.thingsboard.server.dao.model.DeviceEntity;
25 27
26 /** 28 /**
27 * The Interface CustomerDao. 29 * The Interface CustomerDao.
@@ -44,5 +46,14 @@ public interface CustomerDao extends Dao<CustomerEntity> { @@ -44,5 +46,14 @@ public interface CustomerDao extends Dao<CustomerEntity> {
44 * @return the list of customer objects 46 * @return the list of customer objects
45 */ 47 */
46 List<CustomerEntity> findCustomersByTenantId(UUID tenantId, TextPageLink pageLink); 48 List<CustomerEntity> findCustomersByTenantId(UUID tenantId, TextPageLink pageLink);
  49 +
  50 + /**
  51 + * Find customers by tenantId and customer title.
  52 + *
  53 + * @param tenantId the tenantId
  54 + * @param title the customer title
  55 + * @return the optional customer object
  56 + */
  57 + Optional<CustomerEntity> findCustomersByTenantIdAndTitle(UUID tenantId, String title);
47 58
48 } 59 }
@@ -16,11 +16,18 @@ @@ -16,11 +16,18 @@
16 package org.thingsboard.server.dao.customer; 16 package org.thingsboard.server.dao.customer;
17 17
18 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq; 18 import static com.datastax.driver.core.querybuilder.QueryBuilder.eq;
  19 +import static com.datastax.driver.core.querybuilder.QueryBuilder.select;
  20 +import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME;
  21 +import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TITLE_PROPERTY;
  22 +import static org.thingsboard.server.dao.model.ModelConstants.CUSTOMER_TENANT_ID_PROPERTY;
  23 +
19 24
20 import java.util.Arrays; 25 import java.util.Arrays;
21 import java.util.List; 26 import java.util.List;
  27 +import java.util.Optional;
22 import java.util.UUID; 28 import java.util.UUID;
23 29
  30 +import com.datastax.driver.core.querybuilder.Select;
24 import lombok.extern.slf4j.Slf4j; 31 import lombok.extern.slf4j.Slf4j;
25 import org.springframework.stereotype.Component; 32 import org.springframework.stereotype.Component;
26 import org.thingsboard.server.common.data.Customer; 33 import org.thingsboard.server.common.data.Customer;
@@ -60,4 +67,13 @@ public class CustomerDaoImpl extends AbstractSearchTextDao<CustomerEntity> imple @@ -60,4 +67,13 @@ public class CustomerDaoImpl extends AbstractSearchTextDao<CustomerEntity> imple
60 return customerEntities; 67 return customerEntities;
61 } 68 }
62 69
  70 + @Override
  71 + public Optional<CustomerEntity> findCustomersByTenantIdAndTitle(UUID tenantId, String title) {
  72 + Select select = select().from(CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME);
  73 + Select.Where query = select.where();
  74 + query.and(eq(CUSTOMER_TENANT_ID_PROPERTY, tenantId));
  75 + query.and(eq(CUSTOMER_TITLE_PROPERTY, title));
  76 + return Optional.ofNullable(findOneByStatement(query));
  77 + }
  78 +
63 } 79 }
@@ -28,6 +28,8 @@ public interface CustomerService { @@ -28,6 +28,8 @@ public interface CustomerService {
28 public Customer saveCustomer(Customer customer); 28 public Customer saveCustomer(Customer customer);
29 29
30 public void deleteCustomer(CustomerId customerId); 30 public void deleteCustomer(CustomerId customerId);
  31 +
  32 + public Customer findOrCreatePublicCustomer(TenantId tenantId);
31 33
32 public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink); 34 public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink);
33 35
@@ -18,8 +18,12 @@ package org.thingsboard.server.dao.customer; @@ -18,8 +18,12 @@ package org.thingsboard.server.dao.customer;
18 import static org.thingsboard.server.dao.DaoUtil.convertDataList; 18 import static org.thingsboard.server.dao.DaoUtil.convertDataList;
19 import static org.thingsboard.server.dao.DaoUtil.getData; 19 import static org.thingsboard.server.dao.DaoUtil.getData;
20 20
  21 +import java.io.IOException;
21 import java.util.List; 22 import java.util.List;
  23 +import java.util.Optional;
22 24
  25 +import com.fasterxml.jackson.databind.JsonNode;
  26 +import com.fasterxml.jackson.databind.ObjectMapper;
23 import lombok.extern.slf4j.Slf4j; 27 import lombok.extern.slf4j.Slf4j;
24 import org.apache.commons.lang3.StringUtils; 28 import org.apache.commons.lang3.StringUtils;
25 import org.thingsboard.server.common.data.Customer; 29 import org.thingsboard.server.common.data.Customer;
@@ -46,6 +50,8 @@ import org.thingsboard.server.dao.service.Validator; @@ -46,6 +50,8 @@ import org.thingsboard.server.dao.service.Validator;
46 @Slf4j 50 @Slf4j
47 public class CustomerServiceImpl implements CustomerService { 51 public class CustomerServiceImpl implements CustomerService {
48 52
  53 + private static final String PUBLIC_CUSTOMER_TITLE = "Public";
  54 +
49 @Autowired 55 @Autowired
50 private CustomerDao customerDao; 56 private CustomerDao customerDao;
51 57
@@ -80,7 +86,7 @@ public class CustomerServiceImpl implements CustomerService { @@ -80,7 +86,7 @@ public class CustomerServiceImpl implements CustomerService {
80 @Override 86 @Override
81 public void deleteCustomer(CustomerId customerId) { 87 public void deleteCustomer(CustomerId customerId) {
82 log.trace("Executing deleteCustomer [{}]", customerId); 88 log.trace("Executing deleteCustomer [{}]", customerId);
83 - Validator.validateId(customerId, "Incorrect tenantId " + customerId); 89 + Validator.validateId(customerId, "Incorrect customerId " + customerId);
84 Customer customer = findCustomerById(customerId); 90 Customer customer = findCustomerById(customerId);
85 if (customer == null) { 91 if (customer == null) {
86 throw new IncorrectParameterException("Unable to delete non-existent customer."); 92 throw new IncorrectParameterException("Unable to delete non-existent customer.");
@@ -92,6 +98,27 @@ public class CustomerServiceImpl implements CustomerService { @@ -92,6 +98,27 @@ public class CustomerServiceImpl implements CustomerService {
92 } 98 }
93 99
94 @Override 100 @Override
  101 + public Customer findOrCreatePublicCustomer(TenantId tenantId) {
  102 + log.trace("Executing findOrCreatePublicCustomer, tenantId [{}]", tenantId);
  103 + Validator.validateId(tenantId, "Incorrect customerId " + tenantId);
  104 + Optional<CustomerEntity> publicCustomerEntity = customerDao.findCustomersByTenantIdAndTitle(tenantId.getId(), PUBLIC_CUSTOMER_TITLE);
  105 + if (publicCustomerEntity.isPresent()) {
  106 + return getData(publicCustomerEntity.get());
  107 + } else {
  108 + Customer publicCustomer = new Customer();
  109 + publicCustomer.setTenantId(tenantId);
  110 + publicCustomer.setTitle(PUBLIC_CUSTOMER_TITLE);
  111 + try {
  112 + publicCustomer.setAdditionalInfo(new ObjectMapper().readValue("{ \"isPublic\": true }", JsonNode.class));
  113 + } catch (IOException e) {
  114 + throw new IncorrectParameterException("Unable to create public customer.", e);
  115 + }
  116 + CustomerEntity customerEntity = customerDao.save(publicCustomer);
  117 + return getData(customerEntity);
  118 + }
  119 + }
  120 +
  121 + @Override
95 public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink) { 122 public TextPageData<Customer> findCustomersByTenantId(TenantId tenantId, TextPageLink pageLink) {
96 log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink); 123 log.trace("Executing findCustomersByTenantId, tenantId [{}], pageLink [{}]", tenantId, pageLink);
97 Validator.validateId(tenantId, "Incorrect tenantId " + tenantId); 124 Validator.validateId(tenantId, "Incorrect tenantId " + tenantId);
@@ -110,11 +137,35 @@ public class CustomerServiceImpl implements CustomerService { @@ -110,11 +137,35 @@ public class CustomerServiceImpl implements CustomerService {
110 137
111 private DataValidator<Customer> customerValidator = 138 private DataValidator<Customer> customerValidator =
112 new DataValidator<Customer>() { 139 new DataValidator<Customer>() {
  140 +
  141 + @Override
  142 + protected void validateCreate(Customer customer) {
  143 + customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
  144 + c -> {
  145 + throw new DataValidationException("Customer with such title already exists!");
  146 + }
  147 + );
  148 + }
  149 +
  150 + @Override
  151 + protected void validateUpdate(Customer customer) {
  152 + customerDao.findCustomersByTenantIdAndTitle(customer.getTenantId().getId(), customer.getTitle()).ifPresent(
  153 + c -> {
  154 + if (!c.getId().equals(customer.getUuidId())) {
  155 + throw new DataValidationException("Customer with such title already exists!");
  156 + }
  157 + }
  158 + );
  159 + }
  160 +
113 @Override 161 @Override
114 protected void validateDataImpl(Customer customer) { 162 protected void validateDataImpl(Customer customer) {
115 if (StringUtils.isEmpty(customer.getTitle())) { 163 if (StringUtils.isEmpty(customer.getTitle())) {
116 throw new DataValidationException("Customer title should be specified!"); 164 throw new DataValidationException("Customer title should be specified!");
117 } 165 }
  166 + if (customer.getTitle().equals(PUBLIC_CUSTOMER_TITLE)) {
  167 + throw new DataValidationException("'Public' title for customer is system reserved!");
  168 + }
118 if (!StringUtils.isEmpty(customer.getEmail())) { 169 if (!StringUtils.isEmpty(customer.getEmail())) {
119 validateEmail(customer.getEmail()); 170 validateEmail(customer.getEmail());
120 } 171 }
@@ -111,6 +111,7 @@ public class ModelConstants { @@ -111,6 +111,7 @@ public class ModelConstants {
111 public static final String CUSTOMER_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY; 111 public static final String CUSTOMER_ADDITIONAL_INFO_PROPERTY = ADDITIONAL_INFO_PROPERTY;
112 112
113 public static final String CUSTOMER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "customer_by_tenant_and_search_text"; 113 public static final String CUSTOMER_BY_TENANT_AND_SEARCH_TEXT_COLUMN_FAMILY_NAME = "customer_by_tenant_and_search_text";
  114 + public static final String CUSTOMER_BY_TENANT_AND_TITLE_VIEW_NAME = "customer_by_tenant_and_title";
114 115
115 /** 116 /**
116 * Cassandra device constants. 117 * Cassandra device constants.
@@ -137,6 +137,13 @@ CREATE TABLE IF NOT EXISTS thingsboard.customer ( @@ -137,6 +137,13 @@ CREATE TABLE IF NOT EXISTS thingsboard.customer (
137 PRIMARY KEY (id, tenant_id) 137 PRIMARY KEY (id, tenant_id)
138 ); 138 );
139 139
  140 +CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_title AS
  141 + SELECT *
  142 + from thingsboard.customer
  143 + WHERE tenant_id IS NOT NULL AND title IS NOT NULL AND id IS NOT NULL
  144 + PRIMARY KEY ( tenant_id, title, id )
  145 + WITH CLUSTERING ORDER BY ( title ASC, id DESC );
  146 +
140 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_search_text AS 147 CREATE MATERIALIZED VIEW IF NOT EXISTS thingsboard.customer_by_tenant_and_search_text AS
141 SELECT * 148 SELECT *
142 from thingsboard.customer 149 from thingsboard.customer
@@ -80,7 +80,7 @@ VALUES ( now ( ), minTimeuuid ( 0 ), 'gpio_widgets', 'gpio_panel', @@ -80,7 +80,7 @@ VALUES ( now ( ), minTimeuuid ( 0 ), 'gpio_widgets', 'gpio_panel',
80 80
81 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" ) 81 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
82 VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'timeseries_table', 82 VALUES ( now ( ), minTimeuuid ( 0 ), 'cards', 'timeseries_table',
83 -'{"type":"timeseries","sizeX":8,"sizeY":6.5,"resources":[],"templateHtml":"<md-tabs md-selected=\"sourceIndex\" ng-class=\"{''tb-headless'': sources.length === 1}\"\n id=\"tabs\" md-border-bottom flex class=\"tb-absolute-fill\">\n <md-tab ng-repeat=\"source in sources\" label=\"{{ source.datasource.name }}\">\n <md-table-container>\n <table md-table>\n <thead md-head md-order=\"source.query.order\" md-on-reorder=\"onReorder(source)\">\n <tr md-row>\n <th ng-show=\"showTimestamp\" md-column md-order-by=\"0\"><span>Timestamp</span></th>\n <th md-column md-order-by=\"{{ h.index }}\" ng-repeat=\"h in source.ts.header\"><span>{{ h.dataKey.label }}</span></th>\n </tr>\n </thead>\n <tbody md-body>\n <tr md-row ng-repeat=\"row in source.ts.data\">\n <td ng-show=\"$index > 0 || ($index === 0 && showTimestamp)\" md-cell ng-repeat=\"d in row track by $index\" ng-style=\"cellStyle(source, $index, d)\" ng-bind-html=\"cellContent(source, $index, row, d)\">\n </td>\n </tr> \n </tbody> \n </table>\n </md-table-container>\n <md-table-pagination md-limit=\"source.query.limit\" md-limit-options=\"[5, 10, 15]\"\n md-page=\"source.query.page\" md-total=\"{{source.ts.count}}\"\n md-on-paginate=\"onPaginate(source)\" md-page-select>\n </md-table-pagination>\n </md-tab>\n</md-tabs>","templateCss":"table.md-table thead.md-head>tr.md-row {\n height: 40px;\n}\n\ntable.md-table tbody.md-body>tr.md-row, table.md-table tfoot.md-foot>tr.md-row {\n height: 38px;\n}\n\n.md-table-pagination>* {\n height: 46px;\n}\n","controllerScript":"self.onInit = function() {\n \n var scope = self.ctx.$scope;\n \n self.ctx.filter = scope.$injector.get(\"$filter\");\n\n scope.sources = [];\n scope.sourceIndex = 0;\n scope.showTimestamp = self.ctx.settings.showTimestamp !== false;\n var origColor = self.ctx.widgetConfig.color || ''rgba(0, 0, 0, 0.87)'';\n var defaultColor = tinycolor(origColor);\n var mdDark = defaultColor.setAlpha(0.87).toRgbString();\n var mdDarkSecondary = defaultColor.setAlpha(0.54).toRgbString();\n var mdDarkDisabled = defaultColor.setAlpha(0.26).toRgbString();\n var mdDarkIcon = mdDarkSecondary;\n var mdDarkDivider = defaultColor.setAlpha(0.12).toRgbString();\n \n var cssString = ''table.md-table th.md-column {\\n''+\n ''color: '' + mdDarkSecondary + '';\\n''+\n ''}\\n''+\n ''table.md-table th.md-column md-icon.md-sort-icon {\\n''+\n ''color: '' + mdDarkDisabled + '';\\n''+\n ''}\\n''+\n ''table.md-table th.md-column.md-active, table.md-table th.md-column.md-active md-icon {\\n''+\n ''color: '' + mdDark + '';\\n''+\n ''}\\n''+\n ''table.md-table td.md-cell {\\n''+\n ''color: '' + mdDark + '';\\n''+\n ''border-top: 1px ''+mdDarkDivider+'' solid;\\n''+\n ''}\\n''+\n ''table.md-table td.md-cell.md-placeholder {\\n''+\n ''color: '' + mdDarkDisabled + '';\\n''+\n ''}\\n''+\n ''table.md-table td.md-cell md-select > .md-select-value > span.md-select-icon {\\n''+\n ''color: '' + mdDarkSecondary + '';\\n''+\n ''}\\n''+\n ''.md-table-pagination {\\n''+\n ''color: '' + mdDarkSecondary + '';\\n''+\n ''border-top: 1px ''+mdDarkDivider+'' solid;\\n''+\n ''}\\n''+\n ''.md-table-pagination .buttons md-icon {\\n''+\n ''color: '' + mdDarkSecondary + '';\\n''+\n ''}\\n''+\n ''.md-table-pagination md-select:not([disabled]):focus .md-select-value {\\n''+\n ''color: '' + mdDarkSecondary + '';\\n''+\n ''}'';\n \n var cssParser = new cssjs();\n cssParser.testMode = false;\n var namespace = ''ts-table-'' + hashCode(cssString);\n cssParser.cssPreviewNamespace = namespace;\n cssParser.createStyleElement(namespace, cssString);\n self.ctx.$container.addClass(namespace);\n \n function hashCode(str) {\n var hash = 0;\n var i, char;\n if (str.length === 0) return hash;\n for (i = 0; i < str.length; i++) {\n char = str.charCodeAt(i);\n hash = ((hash << 5) - hash) + char;\n hash = hash & hash;\n }\n return hash;\n }\n \n var keyOffset = 0;\n for (var ds = 0; ds < self.ctx.datasources.length; ds++) {\n var source = {};\n var datasource = self.ctx.datasources[ds];\n source.keyStartIndex = keyOffset;\n keyOffset += datasource.dataKeys.length;\n source.keyEndIndex = keyOffset;\n source.datasource = datasource;\n source.data = [];\n source.rawData = [];\n source.query = {\n limit: 5,\n page: 1,\n order: ''-0''\n }\n source.ts = {\n header: [],\n count: 0,\n data: [],\n stylesInfo: [],\n contentsInfo: [],\n rowDataTemplate: {}\n }\n source.ts.rowDataTemplate[''Timestamp''] = null;\n for (var a = 0; a < datasource.dataKeys.length; a++ ) {\n var dataKey = datasource.dataKeys[a];\n var keySettings = dataKey.settings;\n source.ts.header.push({\n index: a+1,\n dataKey: dataKey\n });\n source.ts.rowDataTemplate[dataKey.label] = null;\n\n var cellStyleFunction = null;\n var useCellStyleFunction = false;\n \n if (keySettings.useCellStyleFunction === true) {\n if (angular.isDefined(keySettings.cellStyleFunction) && keySettings.cellStyleFunction.length > 0) {\n try {\n cellStyleFunction = new Function(''value'', keySettings.cellStyleFunction);\n useCellStyleFunction = true;\n } catch (e) {\n cellStyleFunction = null;\n useCellStyleFunction = false;\n }\n }\n }\n\n source.ts.stylesInfo.push({\n useCellStyleFunction: useCellStyleFunction,\n cellStyleFunction: cellStyleFunction\n });\n \n var cellContentFunction = null;\n var useCellContentFunction = false;\n \n if (keySettings.useCellContentFunction === true) {\n if (angular.isDefined(keySettings.cellContentFunction) && keySettings.cellContentFunction.length > 0) {\n try {\n cellContentFunction = new Function(''value, rowData, filter'', keySettings.cellContentFunction);\n useCellContentFunction = true;\n } catch (e) {\n cellContentFunction = null;\n useCellContentFunction = false;\n }\n }\n }\n \n source.ts.contentsInfo.push({\n useCellContentFunction: useCellContentFunction,\n cellContentFunction: cellContentFunction\n });\n \n }\n scope.sources.push(source);\n }\n\n scope.onPaginate = function(source) {\n updatePage(source);\n }\n \n scope.onReorder = function(source) {\n reorder(source);\n updatePage(source);\n }\n \n scope.cellStyle = function(source, index, value) {\n var style = {};\n if (index > 0) {\n var styleInfo = source.ts.stylesInfo[index-1];\n if (styleInfo.useCellStyleFunction && styleInfo.cellStyleFunction) {\n try {\n style = styleInfo.cellStyleFunction(value);\n } catch (e) {\n style = {};\n }\n }\n }\n return style;\n }\n\n scope.cellContent = function(source, index, row, value) {\n if (index === 0) {\n return self.ctx.filter(''date'')(value, ''yyyy-MM-dd HH:mm:ss'');\n } else {\n var strContent = '''';\n if (angular.isDefined(value)) {\n strContent = ''''+value;\n }\n var content = strContent;\n var contentInfo = source.ts.contentsInfo[index-1];\n if (contentInfo.useCellContentFunction && contentInfo.cellContentFunction) {\n try {\n var rowData = source.ts.rowDataTemplate;\n rowData[''Timestamp''] = row[0];\n for (var h=0; h < source.ts.header.length; h++) {\n var headerInfo = source.ts.header[h];\n rowData[headerInfo.dataKey.name] = row[headerInfo.index];\n }\n content = contentInfo.cellContentFunction(value, rowData, self.ctx.filter);\n } catch (e) {\n content = strContent;\n }\n } \n return content;\n }\n }\n \n scope.$watch(''sourceIndex'', function(newIndex, oldIndex) {\n if (newIndex != oldIndex) {\n updateSourceData(scope.sources[scope.sourceIndex]);\n } \n });\n}\n\nself.onDataUpdated = function() {\n var scope = self.ctx.$scope;\n for (var s=0; s < scope.sources.length; s++) {\n var source = scope.sources[s];\n source.rawData = self.ctx.data.slice(source.keyStartIndex, source.keyEndIndex);\n }\n updateSourceData(scope.sources[scope.sourceIndex]);\n scope.$digest();\n}\n\nself.onDestroy = function() {\n}\n\nfunction updatePage(source) {\n var startIndex = source.query.limit * (source.query.page - 1);\n source.ts.data = source.data.slice(startIndex, startIndex + source.query.limit);\n}\n\nfunction reorder(source) {\n source.data = self.ctx.filter(''orderBy'')(source.data, source.query.order);\n}\n\nfunction convertData(data) {\n var rowsMap = {};\n for (var d = 0; d < data.length; d++) {\n var columnData = data[d].data;\n for (var i = 0; i < columnData.length; i++) {\n var cellData = columnData[i];\n var timestamp = cellData[0];\n var row = rowsMap[timestamp];\n if (!row) {\n row = [];\n row[0] = timestamp;\n for (var c = 0; c < data.length; c++) {\n row[c+1] = undefined;\n }\n rowsMap[timestamp] = row;\n }\n row[d+1] = cellData[1];\n }\n }\n var rows = [];\n for (var t in rowsMap) {\n rows.push(rowsMap[t]);\n }\n return rows;\n}\n\nfunction updateSourceData(source) {\n source.data = convertData(source.rawData);\n source.ts.count = source.data.length;\n reorder(source);\n updatePage(source);\n}\n","settingsSchema":"{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\"\n ]\n}","dataKeySettingsSchema":"{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix(''blue'', ''red'', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: ''20px'',\\n color: ''#ffffff'',\\n background: color.toRgbString(),\\n fontSize: ''18px''\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor(''blue'');\\n backgroundColor.setAlpha(value/100);\\n var color = ''blue'';\\n if (value > 50) {\\n color = ''white'';\\n }\\n \\n return {\\n paddingLeft: ''20px'',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: ''18px''\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false}"}', 83 +'{"type":"timeseries","sizeX":8,"sizeY":6.5,"resources":[],"templateHtml":"<tb-timeseries-table-widget \n config=\"config\"\n table-id=\"tableId\"\n datasources=\"datasources\"\n data=\"data\">\n</tb-timeseries-table-widget>","templateCss":"","controllerScript":"self.onInit = function() {\n \n var scope = self.ctx.$scope;\n var id = self.ctx.$scope.$injector.get(''utils'').guid();\n\n scope.config = {\n settings: self.ctx.settings,\n widgetConfig: self.ctx.widgetConfig\n }\n\n scope.datasources = self.ctx.datasources;\n scope.data = self.ctx.data;\n scope.tableId = \"table-\"+id;\n \n}\n\nself.onDataUpdated = function() {\n self.ctx.$scope.data = self.ctx.data;\n self.ctx.$scope.$broadcast(''timeseries-table-data-updated'', self.ctx.$scope.tableId);\n}\n\nself.onDestroy = function() {\n}","settingsSchema":"{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"TimeseriesTableSettings\",\n \"properties\": {\n \"showTimestamp\": {\n \"title\": \"Display timestamp column\",\n \"type\": \"boolean\",\n \"default\": true\n }\n },\n \"required\": []\n },\n \"form\": [\n \"showTimestamp\"\n ]\n}","dataKeySettingsSchema":"{\n \"schema\": {\n \"type\": \"object\",\n \"title\": \"DataKeySettings\",\n \"properties\": {\n \"useCellStyleFunction\": {\n \"title\": \"Use cell style function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellStyleFunction\": {\n \"title\": \"Cell style function: f(value)\",\n \"type\": \"string\",\n \"default\": \"\"\n },\n \"useCellContentFunction\": {\n \"title\": \"Use cell content function\",\n \"type\": \"boolean\",\n \"default\": false\n },\n \"cellContentFunction\": {\n \"title\": \"Cell content function: f(value, rowData, filter)\",\n \"type\": \"string\",\n \"default\": \"\"\n }\n },\n \"required\": []\n },\n \"form\": [\n \"useCellStyleFunction\",\n {\n \"key\": \"cellStyleFunction\",\n \"type\": \"javascript\"\n },\n \"useCellContentFunction\",\n {\n \"key\": \"cellContentFunction\",\n \"type\": \"javascript\"\n }\n ]\n}","defaultConfig":"{\"datasources\":[{\"type\":\"function\",\"name\":\"function\",\"dataKeys\":[{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Temperature °C\",\"color\":\"#2196f3\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = (value + 60)/120 * 100;\\n var color = tinycolor.mix(''blue'', ''red'', amount = percent);\\n color.setAlpha(.5);\\n return {\\n paddingLeft: ''20px'',\\n color: ''#ffffff'',\\n background: color.toRgbString(),\\n fontSize: ''18px''\\n };\\n} else {\\n return {};\\n}\"},\"_hash\":0.8587686344902596,\"funcBody\":\"var value = prevValue + Math.random() * 40 - 20;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < -60) {\\n\\tvalue = -60;\\n} else if (value > 60) {\\n\\tvalue = 60;\\n}\\nreturn value;\"},{\"name\":\"f(x)\",\"type\":\"function\",\"label\":\"Humidity, %\",\"color\":\"#ffc107\",\"settings\":{\"useCellStyleFunction\":true,\"cellStyleFunction\":\"if (value) {\\n var percent = value;\\n var backgroundColor = tinycolor(''blue'');\\n backgroundColor.setAlpha(value/100);\\n var color = ''blue'';\\n if (value > 50) {\\n color = ''white'';\\n }\\n \\n return {\\n paddingLeft: ''20px'',\\n color: color,\\n background: backgroundColor.toRgbString(),\\n fontSize: ''18px''\\n };\\n} else {\\n return {};\\n}\",\"useCellContentFunction\":false},\"_hash\":0.12775350966079668,\"funcBody\":\"var value = prevValue + Math.random() * 20 - 10;\\nvar multiplier = Math.pow(10, 1 || 0);\\nvar value = Math.round(value * multiplier) / multiplier;\\nif (value < 5) {\\n\\tvalue = 5;\\n} else if (value > 100) {\\n\\tvalue = 100;\\n}\\nreturn value;\"}]}],\"timewindow\":{\"realtime\":{\"interval\":1000,\"timewindowMs\":60000},\"aggregation\":{\"type\":\"NONE\",\"limit\":200}},\"showTitle\":true,\"backgroundColor\":\"rgb(255, 255, 255)\",\"color\":\"rgba(0, 0, 0, 0.87)\",\"padding\":\"8px\",\"settings\":{\"showTimestamp\":true},\"title\":\"Timeseries table\",\"dropShadow\":true,\"enableFullscreen\":true,\"titleStyle\":{\"fontSize\":\"16px\",\"fontWeight\":400},\"useDashboardTimewindow\":false,\"showLegend\":false}"}',
84 'Timeseries table' ); 84 'Timeseries table' );
85 85
86 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" ) 86 INSERT INTO "thingsboard"."widget_type" ( "id", "tenant_id", "bundle_alias", "alias", "descriptor", "name" )
@@ -34,11 +34,11 @@ import org.junit.Test; @@ -34,11 +34,11 @@ import org.junit.Test;
34 import com.datastax.driver.core.utils.UUIDs; 34 import com.datastax.driver.core.utils.UUIDs;
35 35
36 public class CustomerServiceImplTest extends AbstractServiceTest { 36 public class CustomerServiceImplTest extends AbstractServiceTest {
37 - 37 +
38 private IdComparator<Customer> idComparator = new IdComparator<>(); 38 private IdComparator<Customer> idComparator = new IdComparator<>();
39 - 39 +
40 private TenantId tenantId; 40 private TenantId tenantId;
41 - 41 +
42 @Before 42 @Before
43 public void before() { 43 public void before() {
44 Tenant tenant = new Tenant(); 44 Tenant tenant = new Tenant();
@@ -59,23 +59,23 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -59,23 +59,23 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
59 customer.setTenantId(tenantId); 59 customer.setTenantId(tenantId);
60 customer.setTitle("My customer"); 60 customer.setTitle("My customer");
61 Customer savedCustomer = customerService.saveCustomer(customer); 61 Customer savedCustomer = customerService.saveCustomer(customer);
62 - 62 +
63 Assert.assertNotNull(savedCustomer); 63 Assert.assertNotNull(savedCustomer);
64 Assert.assertNotNull(savedCustomer.getId()); 64 Assert.assertNotNull(savedCustomer.getId());
65 Assert.assertTrue(savedCustomer.getCreatedTime() > 0); 65 Assert.assertTrue(savedCustomer.getCreatedTime() > 0);
66 Assert.assertEquals(customer.getTenantId(), savedCustomer.getTenantId()); 66 Assert.assertEquals(customer.getTenantId(), savedCustomer.getTenantId());
67 Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle()); 67 Assert.assertEquals(customer.getTitle(), savedCustomer.getTitle());
68 -  
69 - 68 +
  69 +
70 savedCustomer.setTitle("My new customer"); 70 savedCustomer.setTitle("My new customer");
71 - 71 +
72 customerService.saveCustomer(savedCustomer); 72 customerService.saveCustomer(savedCustomer);
73 Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId()); 73 Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId());
74 Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle()); 74 Assert.assertEquals(foundCustomer.getTitle(), savedCustomer.getTitle());
75 - 75 +
76 customerService.deleteCustomer(savedCustomer.getId()); 76 customerService.deleteCustomer(savedCustomer.getId());
77 } 77 }
78 - 78 +
79 @Test 79 @Test
80 public void testFindCustomerById() { 80 public void testFindCustomerById() {
81 Customer customer = new Customer(); 81 Customer customer = new Customer();
@@ -87,21 +87,21 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -87,21 +87,21 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
87 Assert.assertEquals(savedCustomer, foundCustomer); 87 Assert.assertEquals(savedCustomer, foundCustomer);
88 customerService.deleteCustomer(savedCustomer.getId()); 88 customerService.deleteCustomer(savedCustomer.getId());
89 } 89 }
90 - 90 +
91 @Test(expected = DataValidationException.class) 91 @Test(expected = DataValidationException.class)
92 public void testSaveCustomerWithEmptyTitle() { 92 public void testSaveCustomerWithEmptyTitle() {
93 Customer customer = new Customer(); 93 Customer customer = new Customer();
94 customer.setTenantId(tenantId); 94 customer.setTenantId(tenantId);
95 customerService.saveCustomer(customer); 95 customerService.saveCustomer(customer);
96 } 96 }
97 - 97 +
98 @Test(expected = DataValidationException.class) 98 @Test(expected = DataValidationException.class)
99 public void testSaveCustomerWithEmptyTenant() { 99 public void testSaveCustomerWithEmptyTenant() {
100 Customer customer = new Customer(); 100 Customer customer = new Customer();
101 customer.setTitle("My customer"); 101 customer.setTitle("My customer");
102 customerService.saveCustomer(customer); 102 customerService.saveCustomer(customer);
103 } 103 }
104 - 104 +
105 @Test(expected = DataValidationException.class) 105 @Test(expected = DataValidationException.class)
106 public void testSaveCustomerWithInvalidTenant() { 106 public void testSaveCustomerWithInvalidTenant() {
107 Customer customer = new Customer(); 107 Customer customer = new Customer();
@@ -109,7 +109,7 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -109,7 +109,7 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
109 customer.setTenantId(new TenantId(UUIDs.timeBased())); 109 customer.setTenantId(new TenantId(UUIDs.timeBased()));
110 customerService.saveCustomer(customer); 110 customerService.saveCustomer(customer);
111 } 111 }
112 - 112 +
113 @Test(expected = DataValidationException.class) 113 @Test(expected = DataValidationException.class)
114 public void testSaveCustomerWithInvalidEmail() { 114 public void testSaveCustomerWithInvalidEmail() {
115 Customer customer = new Customer(); 115 Customer customer = new Customer();
@@ -118,7 +118,7 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -118,7 +118,7 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
118 customer.setEmail("invalid@mail"); 118 customer.setEmail("invalid@mail");
119 customerService.saveCustomer(customer); 119 customerService.saveCustomer(customer);
120 } 120 }
121 - 121 +
122 @Test 122 @Test
123 public void testDeleteCustomer() { 123 public void testDeleteCustomer() {
124 Customer customer = new Customer(); 124 Customer customer = new Customer();
@@ -129,23 +129,23 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -129,23 +129,23 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
129 Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId()); 129 Customer foundCustomer = customerService.findCustomerById(savedCustomer.getId());
130 Assert.assertNull(foundCustomer); 130 Assert.assertNull(foundCustomer);
131 } 131 }
132 - 132 +
133 @Test 133 @Test
134 public void testFindCustomersByTenantId() { 134 public void testFindCustomersByTenantId() {
135 Tenant tenant = new Tenant(); 135 Tenant tenant = new Tenant();
136 tenant.setTitle("Test tenant"); 136 tenant.setTitle("Test tenant");
137 tenant = tenantService.saveTenant(tenant); 137 tenant = tenantService.saveTenant(tenant);
138 - 138 +
139 TenantId tenantId = tenant.getId(); 139 TenantId tenantId = tenant.getId();
140 - 140 +
141 List<Customer> customers = new ArrayList<>(); 141 List<Customer> customers = new ArrayList<>();
142 - for (int i=0;i<135;i++) { 142 + for (int i = 0; i < 135; i++) {
143 Customer customer = new Customer(); 143 Customer customer = new Customer();
144 customer.setTenantId(tenantId); 144 customer.setTenantId(tenantId);
145 - customer.setTitle("Customer"+i); 145 + customer.setTitle("Customer" + i);
146 customers.add(customerService.saveCustomer(customer)); 146 customers.add(customerService.saveCustomer(customer));
147 } 147 }
148 - 148 +
149 List<Customer> loadedCustomers = new ArrayList<>(); 149 List<Customer> loadedCustomers = new ArrayList<>();
150 TextPageLink pageLink = new TextPageLink(23); 150 TextPageLink pageLink = new TextPageLink(23);
151 TextPageData<Customer> pageData = null; 151 TextPageData<Customer> pageData = null;
@@ -156,47 +156,47 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -156,47 +156,47 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
156 pageLink = pageData.getNextPageLink(); 156 pageLink = pageData.getNextPageLink();
157 } 157 }
158 } while (pageData.hasNext()); 158 } while (pageData.hasNext());
159 - 159 +
160 Collections.sort(customers, idComparator); 160 Collections.sort(customers, idComparator);
161 Collections.sort(loadedCustomers, idComparator); 161 Collections.sort(loadedCustomers, idComparator);
162 - 162 +
163 Assert.assertEquals(customers, loadedCustomers); 163 Assert.assertEquals(customers, loadedCustomers);
164 - 164 +
165 customerService.deleteCustomersByTenantId(tenantId); 165 customerService.deleteCustomersByTenantId(tenantId);
166 166
167 pageLink = new TextPageLink(33); 167 pageLink = new TextPageLink(33);
168 pageData = customerService.findCustomersByTenantId(tenantId, pageLink); 168 pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
169 Assert.assertFalse(pageData.hasNext()); 169 Assert.assertFalse(pageData.hasNext());
170 Assert.assertTrue(pageData.getData().isEmpty()); 170 Assert.assertTrue(pageData.getData().isEmpty());
171 - 171 +
172 tenantService.deleteTenant(tenantId); 172 tenantService.deleteTenant(tenantId);
173 } 173 }
174 - 174 +
175 @Test 175 @Test
176 public void testFindCustomersByTenantIdAndTitle() { 176 public void testFindCustomersByTenantIdAndTitle() {
177 String title1 = "Customer title 1"; 177 String title1 = "Customer title 1";
178 List<Customer> customersTitle1 = new ArrayList<>(); 178 List<Customer> customersTitle1 = new ArrayList<>();
179 - for (int i=0;i<143;i++) { 179 + for (int i = 0; i < 143; i++) {
180 Customer customer = new Customer(); 180 Customer customer = new Customer();
181 customer.setTenantId(tenantId); 181 customer.setTenantId(tenantId);
182 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));  
183 - String title = title1+suffix; 182 + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
  183 + String title = title1 + suffix;
184 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); 184 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
185 customer.setTitle(title); 185 customer.setTitle(title);
186 customersTitle1.add(customerService.saveCustomer(customer)); 186 customersTitle1.add(customerService.saveCustomer(customer));
187 } 187 }
188 String title2 = "Customer title 2"; 188 String title2 = "Customer title 2";
189 List<Customer> customersTitle2 = new ArrayList<>(); 189 List<Customer> customersTitle2 = new ArrayList<>();
190 - for (int i=0;i<175;i++) { 190 + for (int i = 0; i < 175; i++) {
191 Customer customer = new Customer(); 191 Customer customer = new Customer();
192 customer.setTenantId(tenantId); 192 customer.setTenantId(tenantId);
193 - String suffix = RandomStringUtils.randomAlphanumeric((int)(Math.random()*15));  
194 - String title = title2+suffix; 193 + String suffix = RandomStringUtils.randomAlphanumeric((int)(5 + Math.random()*10));
  194 + String title = title2 + suffix;
195 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase(); 195 title = i % 2 == 0 ? title.toLowerCase() : title.toUpperCase();
196 customer.setTitle(title); 196 customer.setTitle(title);
197 customersTitle2.add(customerService.saveCustomer(customer)); 197 customersTitle2.add(customerService.saveCustomer(customer));
198 } 198 }
199 - 199 +
200 List<Customer> loadedCustomersTitle1 = new ArrayList<>(); 200 List<Customer> loadedCustomersTitle1 = new ArrayList<>();
201 TextPageLink pageLink = new TextPageLink(15, title1); 201 TextPageLink pageLink = new TextPageLink(15, title1);
202 TextPageData<Customer> pageData = null; 202 TextPageData<Customer> pageData = null;
@@ -207,12 +207,12 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -207,12 +207,12 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
207 pageLink = pageData.getNextPageLink(); 207 pageLink = pageData.getNextPageLink();
208 } 208 }
209 } while (pageData.hasNext()); 209 } while (pageData.hasNext());
210 - 210 +
211 Collections.sort(customersTitle1, idComparator); 211 Collections.sort(customersTitle1, idComparator);
212 Collections.sort(loadedCustomersTitle1, idComparator); 212 Collections.sort(loadedCustomersTitle1, idComparator);
213 - 213 +
214 Assert.assertEquals(customersTitle1, loadedCustomersTitle1); 214 Assert.assertEquals(customersTitle1, loadedCustomersTitle1);
215 - 215 +
216 List<Customer> loadedCustomersTitle2 = new ArrayList<>(); 216 List<Customer> loadedCustomersTitle2 = new ArrayList<>();
217 pageLink = new TextPageLink(4, title2); 217 pageLink = new TextPageLink(4, title2);
218 do { 218 do {
@@ -225,22 +225,22 @@ public class CustomerServiceImplTest extends AbstractServiceTest { @@ -225,22 +225,22 @@ public class CustomerServiceImplTest extends AbstractServiceTest {
225 225
226 Collections.sort(customersTitle2, idComparator); 226 Collections.sort(customersTitle2, idComparator);
227 Collections.sort(loadedCustomersTitle2, idComparator); 227 Collections.sort(loadedCustomersTitle2, idComparator);
228 - 228 +
229 Assert.assertEquals(customersTitle2, loadedCustomersTitle2); 229 Assert.assertEquals(customersTitle2, loadedCustomersTitle2);
230 230
231 for (Customer customer : loadedCustomersTitle1) { 231 for (Customer customer : loadedCustomersTitle1) {
232 customerService.deleteCustomer(customer.getId()); 232 customerService.deleteCustomer(customer.getId());
233 } 233 }
234 - 234 +
235 pageLink = new TextPageLink(4, title1); 235 pageLink = new TextPageLink(4, title1);
236 pageData = customerService.findCustomersByTenantId(tenantId, pageLink); 236 pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
237 Assert.assertFalse(pageData.hasNext()); 237 Assert.assertFalse(pageData.hasNext());
238 Assert.assertEquals(0, pageData.getData().size()); 238 Assert.assertEquals(0, pageData.getData().size());
239 - 239 +
240 for (Customer customer : loadedCustomersTitle2) { 240 for (Customer customer : loadedCustomersTitle2) {
241 customerService.deleteCustomer(customer.getId()); 241 customerService.deleteCustomer(customer.getId());
242 } 242 }
243 - 243 +
244 pageLink = new TextPageLink(4, title2); 244 pageLink = new TextPageLink(4, title2);
245 pageData = customerService.findCustomersByTenantId(tenantId, pageLink); 245 pageData = customerService.findCustomersByTenantId(tenantId, pageLink);
246 Assert.assertFalse(pageData.hasNext()); 246 Assert.assertFalse(pageData.hasNext());
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -15,7 +15,6 @@ @@ -15,7 +15,6 @@
15 */ 15 */
16 package org.thingsboard.server.extensions.core.plugin.telemetry; 16 package org.thingsboard.server.extensions.core.plugin.telemetry;
17 17
18 -import com.sun.javafx.collections.MappingChange;  
19 import lombok.Setter; 18 import lombok.Setter;
20 import lombok.extern.slf4j.Slf4j; 19 import lombok.extern.slf4j.Slf4j;
21 import org.thingsboard.server.common.data.DataConstants; 20 import org.thingsboard.server.common.data.DataConstants;
@@ -292,7 +291,6 @@ public class SubscriptionManager { @@ -292,7 +291,6 @@ public class SubscriptionManager {
292 } else { 291 } else {
293 log.trace("[{}] Remote subscription is now handled on new server address: [{}]", s.getWsSessionId(), newAddress); 292 log.trace("[{}] Remote subscription is now handled on new server address: [{}]", s.getWsSessionId(), newAddress);
294 subscriptionIterator.remove(); 293 subscriptionIterator.remove();
295 -  
296 //TODO: onUpdate state of subscription by WsSessionId and other maps. 294 //TODO: onUpdate state of subscription by WsSessionId and other maps.
297 } 295 }
298 } 296 }
@@ -139,7 +139,12 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler { @@ -139,7 +139,12 @@ public class TelemetryRestMsgHandler extends DefaultRestMsgHandler {
139 public void onSuccess(PluginContext ctx, List<TsKvEntry> data) { 139 public void onSuccess(PluginContext ctx, List<TsKvEntry> data) {
140 Map<String, List<TsData>> result = new LinkedHashMap<>(); 140 Map<String, List<TsData>> result = new LinkedHashMap<>();
141 for (TsKvEntry entry : data) { 141 for (TsKvEntry entry : data) {
142 - result.put(entry.getKey(), data.stream().map(v -> new TsData(v.getTs(), v.getValueAsString())).collect(Collectors.toList())); 142 + List<TsData> vList = result.get(entry.getKey());
  143 + if (vList == null) {
  144 + vList = new ArrayList<>();
  145 + result.put(entry.getKey(), vList);
  146 + }
  147 + vList.add(new TsData(entry.getTs(), entry.getValueAsString()));
143 } 148 }
144 msg.getResponseHolder().setResult(new ResponseEntity<>(result, HttpStatus.OK)); 149 msg.getResponseHolder().setResult(new ResponseEntity<>(result, HttpStatus.OK));
145 } 150 }
@@ -30,7 +30,7 @@ import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg; @@ -30,7 +30,7 @@ import org.thingsboard.server.extensions.api.plugins.msg.RuleToPluginMsg;
30 import org.thingsboard.server.extensions.api.rules.RuleException; 30 import org.thingsboard.server.extensions.api.rules.RuleException;
31 import org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction; 31 import org.thingsboard.server.extensions.core.action.rpc.RpcPluginAction;
32 32
33 -import java.time.LocalDateTime; 33 +import java.time.ZonedDateTime;
34 import java.time.format.DateTimeFormatter; 34 import java.time.format.DateTimeFormatter;
35 35
36 /** 36 /**
@@ -51,7 +51,7 @@ public class TimePlugin extends AbstractPlugin<TimePluginConfiguration> implemen @@ -51,7 +51,7 @@ public class TimePlugin extends AbstractPlugin<TimePluginConfiguration> implemen
51 51
52 String reply; 52 String reply;
53 if (!StringUtils.isEmpty(format)) { 53 if (!StringUtils.isEmpty(format)) {
54 - reply = "\"" + formatter.format(LocalDateTime.now()) + "\""; 54 + reply = "\"" + formatter.format(ZonedDateTime.now()) + "\"";
55 } else { 55 } else {
56 reply = Long.toString(System.currentTimeMillis()); 56 reply = Long.toString(System.currentTimeMillis());
57 } 57 }
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>1.2.2</version> 25 + <version>1.2.3-SNAPSHOT</version>
26 <artifactId>extensions</artifactId> 26 <artifactId>extensions</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.extensions</groupId> 28 <groupId>org.thingsboard.extensions</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>extensions</artifactId> 24 <artifactId>extensions</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.extensions</groupId> 26 <groupId>org.thingsboard.extensions</groupId>
@@ -22,7 +22,7 @@ @@ -22,7 +22,7 @@
22 <modelVersion>4.0.0</modelVersion> 22 <modelVersion>4.0.0</modelVersion>
23 <parent> 23 <parent>
24 <groupId>org.thingsboard</groupId> 24 <groupId>org.thingsboard</groupId>
25 - <version>1.2.2</version> 25 + <version>1.2.3-SNAPSHOT</version>
26 <artifactId>extensions</artifactId> 26 <artifactId>extensions</artifactId>
27 </parent> 27 </parent>
28 <groupId>org.thingsboard.extensions</groupId> 28 <groupId>org.thingsboard.extensions</groupId>
@@ -55,6 +55,13 @@ public class RestApiCallPlugin extends AbstractPlugin<RestApiCallPluginConfigura @@ -55,6 +55,13 @@ public class RestApiCallPlugin extends AbstractPlugin<RestApiCallPluginConfigura
55 this.headers.add(AUTHORIZATION_HEADER_NAME, String.format(AUTHORIZATION_HEADER_FORMAT, new String(token))); 55 this.headers.add(AUTHORIZATION_HEADER_NAME, String.format(AUTHORIZATION_HEADER_FORMAT, new String(token)));
56 } 56 }
57 57
  58 + if (configuration.getHeaders() != null) {
  59 + configuration.getHeaders().forEach(h -> {
  60 + log.debug("Adding header to request object. Key = {}, Value = {}", h.getKey(), h.getValue());
  61 + this.headers.add(h.getKey(), h.getValue());
  62 + });
  63 + }
  64 +
58 init(); 65 init();
59 } 66 }
60 67
@@ -16,6 +16,9 @@ @@ -16,6 +16,9 @@
16 package org.thingsboard.server.extensions.rest.plugin; 16 package org.thingsboard.server.extensions.rest.plugin;
17 17
18 import lombok.Data; 18 import lombok.Data;
  19 +import org.thingsboard.server.extensions.core.plugin.KeyValuePluginProperties;
  20 +
  21 +import java.util.List;
19 22
20 @Data 23 @Data
21 public class RestApiCallPluginConfiguration { 24 public class RestApiCallPluginConfiguration {
@@ -27,4 +30,6 @@ public class RestApiCallPluginConfiguration { @@ -27,4 +30,6 @@ public class RestApiCallPluginConfiguration {
27 30
28 private String userName; 31 private String userName;
29 private String password; 32 private String password;
  33 +
  34 + private List<KeyValuePluginProperties> headers;
30 } 35 }
@@ -30,6 +30,24 @@ @@ -30,6 +30,24 @@
30 "password": { 30 "password": {
31 "title": "Password", 31 "title": "Password",
32 "type": "string" 32 "type": "string"
  33 + },
  34 + "headers": {
  35 + "title": "Request Headers",
  36 + "type": "array",
  37 + "items": {
  38 + "title": "Request Header",
  39 + "type": "object",
  40 + "properties": {
  41 + "key": {
  42 + "title": "Key",
  43 + "type": "string"
  44 + },
  45 + "value": {
  46 + "title": "Value",
  47 + "type": "string"
  48 + }
  49 + }
  50 + }
33 } 51 }
34 }, 52 },
35 "required": [ 53 "required": [
@@ -62,6 +80,7 @@ @@ -62,6 +80,7 @@
62 { 80 {
63 "key": "password", 81 "key": "password",
64 "type": "password" 82 "type": "password"
65 - } 83 + },
  84 + "headers"
66 ] 85 ]
67 } 86 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <groupId>org.thingsboard</groupId> 21 <groupId>org.thingsboard</groupId>
22 <artifactId>thingsboard</artifactId> 22 <artifactId>thingsboard</artifactId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <packaging>pom</packaging> 24 <packaging>pom</packaging>
25 25
26 <name>Thingsboard</name> 26 <name>Thingsboard</name>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -54,40 +54,4 @@ @@ -54,40 +54,4 @@
54 </dependency> 54 </dependency>
55 </dependencies> 55 </dependencies>
56 56
57 - <build>  
58 - <plugins>  
59 - <plugin>  
60 - <groupId>org.apache.maven.plugins</groupId>  
61 - <artifactId>maven-shade-plugin</artifactId>  
62 - <executions>  
63 - <execution>  
64 - <phase>package</phase>  
65 - <goals>  
66 - <goal>shade</goal>  
67 - </goals>  
68 - <configuration>  
69 - <filters>  
70 - <filter>  
71 - <artifact>*:*</artifact>  
72 - <excludes>  
73 - <exclude>META-INF/*.SF</exclude>  
74 - <exclude>META-INF/*.DSA</exclude>  
75 - <exclude>META-INF/*.RSA</exclude>  
76 - </excludes>  
77 - </filter>  
78 - </filters>  
79 - <transformers>  
80 - <transformer  
81 - implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">  
82 - <manifestEntries>  
83 - <Main-Class>org.thingsboard.client.tools.MqttStressTestTool</Main-Class>  
84 - </manifestEntries>  
85 - </transformer>  
86 - </transformers>  
87 - </configuration>  
88 - </execution>  
89 - </executions>  
90 - </plugin>  
91 - </plugins>  
92 - </build>  
93 </project> 57 </project>
@@ -27,6 +27,7 @@ import org.springframework.http.client.support.HttpRequestWrapper; @@ -27,6 +27,7 @@ import org.springframework.http.client.support.HttpRequestWrapper;
27 import org.springframework.web.client.HttpClientErrorException; 27 import org.springframework.web.client.HttpClientErrorException;
28 import org.springframework.web.client.RestTemplate; 28 import org.springframework.web.client.RestTemplate;
29 import org.thingsboard.server.common.data.Device; 29 import org.thingsboard.server.common.data.Device;
  30 +import org.thingsboard.server.common.data.id.CustomerId;
30 import org.thingsboard.server.common.data.id.DeviceId; 31 import org.thingsboard.server.common.data.id.DeviceId;
31 import org.thingsboard.server.common.data.security.DeviceCredentials; 32 import org.thingsboard.server.common.data.security.DeviceCredentials;
32 33
@@ -76,6 +77,12 @@ public class RestClient implements ClientHttpRequestInterceptor { @@ -76,6 +77,12 @@ public class RestClient implements ClientHttpRequestInterceptor {
76 return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody(); 77 return restTemplate.postForEntity(baseURL + "/api/device", device, Device.class).getBody();
77 } 78 }
78 79
  80 +
  81 + public Device assignDevice(CustomerId customerId, DeviceId deviceId) {
  82 + return restTemplate.postForEntity(baseURL + "/api/customer/{customerId}/device/{deviceId}", null, Device.class,
  83 + customerId.toString(), deviceId.toString()).getBody();
  84 + }
  85 +
79 public DeviceCredentials getCredentials(DeviceId id) { 86 public DeviceCredentials getCredentials(DeviceId id) {
80 return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody(); 87 return restTemplate.getForEntity(baseURL + "/api/device/" + id.getId().toString() + "/credentials", DeviceCredentials.class).getBody();
81 } 88 }
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>transport</artifactId> 24 <artifactId>transport</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard.transport</groupId> 26 <groupId>org.thingsboard.transport</groupId>
@@ -247,7 +247,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement @@ -247,7 +247,7 @@ public class MqttTransportHandler extends ChannelInboundHandlerAdapter implement
247 247
248 private MqttMessage createUnSubAckMessage(int msgId) { 248 private MqttMessage createUnSubAckMessage(int msgId) {
249 MqttFixedHeader mqttFixedHeader = 249 MqttFixedHeader mqttFixedHeader =
250 - new MqttFixedHeader(SUBACK, false, AT_LEAST_ONCE, false, 0); 250 + new MqttFixedHeader(UNSUBACK, false, AT_LEAST_ONCE, false, 0);
251 MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId); 251 MqttMessageIdVariableHeader mqttMessageIdVariableHeader = MqttMessageIdVariableHeader.from(msgId);
252 return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader); 252 return new MqttMessage(mqttFixedHeader, mqttMessageIdVariableHeader);
253 } 253 }
@@ -40,6 +40,8 @@ import java.security.cert.CertificateException; @@ -40,6 +40,8 @@ import java.security.cert.CertificateException;
40 */ 40 */
41 public class MqttTransportServerInitializer extends ChannelInitializer<SocketChannel> { 41 public class MqttTransportServerInitializer extends ChannelInitializer<SocketChannel> {
42 42
  43 + private static final int MAX_PAYLOAD_SIZE = 64 * 1024 * 1024;
  44 +
43 private final SessionMsgProcessor processor; 45 private final SessionMsgProcessor processor;
44 private final DeviceService deviceService; 46 private final DeviceService deviceService;
45 private final DeviceAuthService authService; 47 private final DeviceAuthService authService;
@@ -63,7 +65,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha @@ -63,7 +65,7 @@ public class MqttTransportServerInitializer extends ChannelInitializer<SocketCha
63 sslHandler = sslHandlerProvider.getSslHandler(); 65 sslHandler = sslHandlerProvider.getSslHandler();
64 pipeline.addLast(sslHandler); 66 pipeline.addLast(sslHandler);
65 } 67 }
66 - pipeline.addLast("decoder", new MqttDecoder()); 68 + pipeline.addLast("decoder", new MqttDecoder(MAX_PAYLOAD_SIZE));
67 pipeline.addLast("encoder", MqttEncoder.INSTANCE); 69 pipeline.addLast("encoder", MqttEncoder.INSTANCE);
68 70
69 MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, adaptor, sslHandler); 71 MqttTransportHandler handler = new MqttTransportHandler(processor, deviceService, authService, adaptor, sslHandler);
@@ -134,7 +134,7 @@ public class GatewaySessionCtx { @@ -134,7 +134,7 @@ public class GatewaySessionCtx {
134 JsonObject jsonObj = json.getAsJsonObject(); 134 JsonObject jsonObj = json.getAsJsonObject();
135 String deviceName = checkDeviceConnected(jsonObj.get("device").getAsString()); 135 String deviceName = checkDeviceConnected(jsonObj.get("device").getAsString());
136 Integer requestId = jsonObj.get("id").getAsInt(); 136 Integer requestId = jsonObj.get("id").getAsInt();
137 - String data = jsonObj.get("data").getAsString(); 137 + String data = jsonObj.get("data").toString();
138 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName); 138 GatewayDeviceSessionCtx deviceSessionCtx = devices.get(deviceName);
139 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(), 139 processor.process(new BasicToDeviceActorSessionMsg(deviceSessionCtx.getDevice(),
140 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data)))); 140 new BasicAdaptorToSessionActorMsg(deviceSessionCtx, new ToDeviceRpcResponseMsg(requestId, data))));
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
1 { 1 {
2 "name": "thingsboard", 2 "name": "thingsboard",
3 "private": true, 3 "private": true,
4 - "version": "1.2.2", 4 + "version": "1.2.3",
5 "description": "Thingsboard UI", 5 "description": "Thingsboard UI",
6 "licenses": [ 6 "licenses": [
7 { 7 {
@@ -33,6 +33,7 @@ @@ -33,6 +33,7 @@
33 "angular-messages": "1.5.8", 33 "angular-messages": "1.5.8",
34 "angular-route": "1.5.8", 34 "angular-route": "1.5.8",
35 "angular-sanitize": "1.5.8", 35 "angular-sanitize": "1.5.8",
  36 + "angular-socialshare": "^2.3.8",
36 "angular-storage": "0.0.15", 37 "angular-storage": "0.0.15",
37 "angular-touch": "1.5.8", 38 "angular-touch": "1.5.8",
38 "angular-translate": "2.13.1", 39 "angular-translate": "2.13.1",
@@ -49,6 +50,7 @@ @@ -49,6 +50,7 @@
49 "clipboard": "^1.5.15", 50 "clipboard": "^1.5.15",
50 "compass-sass-mixins": "^0.12.7", 51 "compass-sass-mixins": "^0.12.7",
51 "flot": "git://github.com/flot/flot.git#0.9-work", 52 "flot": "git://github.com/flot/flot.git#0.9-work",
  53 + "flot-curvedlines": "git://github.com/MichaelZinsmaier/CurvedLines.git#master",
52 "font-awesome": "^4.6.3", 54 "font-awesome": "^4.6.3",
53 "javascript-detect-element-resize": "^0.5.3", 55 "javascript-detect-element-resize": "^0.5.3",
54 "jquery": "^3.1.0", 56 "jquery": "^3.1.0",
@@ -20,7 +20,7 @@ @@ -20,7 +20,7 @@
20 <modelVersion>4.0.0</modelVersion> 20 <modelVersion>4.0.0</modelVersion>
21 <parent> 21 <parent>
22 <groupId>org.thingsboard</groupId> 22 <groupId>org.thingsboard</groupId>
23 - <version>1.2.2</version> 23 + <version>1.2.3-SNAPSHOT</version>
24 <artifactId>thingsboard</artifactId> 24 <artifactId>thingsboard</artifactId>
25 </parent> 25 </parent>
26 <groupId>org.thingsboard</groupId> 26 <groupId>org.thingsboard</groupId>
@@ -18,11 +18,14 @@ export default angular.module('thingsboard.api.customer', []) @@ -18,11 +18,14 @@ export default angular.module('thingsboard.api.customer', [])
18 .name; 18 .name;
19 19
20 /*@ngInject*/ 20 /*@ngInject*/
21 -function CustomerService($http, $q) { 21 +function CustomerService($http, $q, types) {
22 22
23 var service = { 23 var service = {
24 getCustomers: getCustomers, 24 getCustomers: getCustomers,
25 getCustomer: getCustomer, 25 getCustomer: getCustomer,
  26 + getShortCustomerInfo: getShortCustomerInfo,
  27 + applyAssignedCustomersInfo: applyAssignedCustomersInfo,
  28 + applyAssignedCustomerInfo: applyAssignedCustomerInfo,
26 deleteCustomer: deleteCustomer, 29 deleteCustomer: deleteCustomer,
27 saveCustomer: saveCustomer 30 saveCustomer: saveCustomer
28 } 31 }
@@ -60,6 +63,88 @@ function CustomerService($http, $q) { @@ -60,6 +63,88 @@ function CustomerService($http, $q) {
60 return deferred.promise; 63 return deferred.promise;
61 } 64 }
62 65
  66 + function getShortCustomerInfo(customerId) {
  67 + var deferred = $q.defer();
  68 + var url = '/api/customer/' + customerId + '/shortInfo';
  69 + $http.get(url, null).then(function success(response) {
  70 + deferred.resolve(response.data);
  71 + }, function fail(response) {
  72 + deferred.reject(response.data);
  73 + });
  74 + return deferred.promise;
  75 + }
  76 +
  77 + function applyAssignedCustomersInfo(items) {
  78 + var deferred = $q.defer();
  79 + var assignedCustomersMap = {};
  80 + function loadNextCustomerInfoOrComplete(i) {
  81 + i++;
  82 + if (i < items.length) {
  83 + loadNextCustomerInfo(i);
  84 + } else {
  85 + deferred.resolve(items);
  86 + }
  87 + }
  88 +
  89 + function loadNextCustomerInfo(i) {
  90 + var item = items[i];
  91 + item.assignedCustomer = {};
  92 + if (item.customerId && item.customerId.id != types.id.nullUid) {
  93 + item.assignedCustomer.id = item.customerId.id;
  94 + var assignedCustomer = assignedCustomersMap[item.customerId.id];
  95 + if (assignedCustomer){
  96 + item.assignedCustomer = assignedCustomer;
  97 + loadNextCustomerInfoOrComplete(i);
  98 + } else {
  99 + getShortCustomerInfo(item.customerId.id).then(
  100 + function success(info) {
  101 + assignedCustomer = {
  102 + id: item.customerId.id,
  103 + title: info.title,
  104 + isPublic: info.isPublic
  105 + };
  106 + assignedCustomersMap[assignedCustomer.id] = assignedCustomer;
  107 + item.assignedCustomer = assignedCustomer;
  108 + loadNextCustomerInfoOrComplete(i);
  109 + },
  110 + function fail() {
  111 + loadNextCustomerInfoOrComplete(i);
  112 + }
  113 + );
  114 + }
  115 + } else {
  116 + loadNextCustomerInfoOrComplete(i);
  117 + }
  118 + }
  119 + if (items.length > 0) {
  120 + loadNextCustomerInfo(0);
  121 + } else {
  122 + deferred.resolve(items);
  123 + }
  124 + return deferred.promise;
  125 + }
  126 +
  127 + function applyAssignedCustomerInfo(items, customerId) {
  128 + var deferred = $q.defer();
  129 + getShortCustomerInfo(customerId).then(
  130 + function success(info) {
  131 + var assignedCustomer = {
  132 + id: customerId,
  133 + title: info.title,
  134 + isPublic: info.isPublic
  135 + }
  136 + items.forEach(function(item) {
  137 + item.assignedCustomer = assignedCustomer;
  138 + });
  139 + deferred.resolve(items);
  140 + },
  141 + function fail() {
  142 + deferred.reject();
  143 + }
  144 + );
  145 + return deferred.promise;
  146 + }
  147 +
63 function saveCustomer(customer) { 148 function saveCustomer(customer) {
64 var deferred = $q.defer(); 149 var deferred = $q.defer();
65 var url = '/api/customer'; 150 var url = '/api/customer';
@@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', []) @@ -17,7 +17,7 @@ export default angular.module('thingsboard.api.dashboard', [])
17 .factory('dashboardService', DashboardService).name; 17 .factory('dashboardService', DashboardService).name;
18 18
19 /*@ngInject*/ 19 /*@ngInject*/
20 -function DashboardService($http, $q) { 20 +function DashboardService($http, $q, $location, customerService) {
21 21
22 var service = { 22 var service = {
23 assignDashboardToCustomer: assignDashboardToCustomer, 23 assignDashboardToCustomer: assignDashboardToCustomer,
@@ -27,7 +27,9 @@ function DashboardService($http, $q) { @@ -27,7 +27,9 @@ function DashboardService($http, $q) {
27 getTenantDashboards: getTenantDashboards, 27 getTenantDashboards: getTenantDashboards,
28 deleteDashboard: deleteDashboard, 28 deleteDashboard: deleteDashboard,
29 saveDashboard: saveDashboard, 29 saveDashboard: saveDashboard,
30 - unassignDashboardFromCustomer: unassignDashboardFromCustomer 30 + unassignDashboardFromCustomer: unassignDashboardFromCustomer,
  31 + makeDashboardPublic: makeDashboardPublic,
  32 + getPublicDashboardLink: getPublicDashboardLink
31 } 33 }
32 34
33 return service; 35 return service;
@@ -45,7 +47,15 @@ function DashboardService($http, $q) { @@ -45,7 +47,15 @@ function DashboardService($http, $q) {
45 url += '&textOffset=' + pageLink.textOffset; 47 url += '&textOffset=' + pageLink.textOffset;
46 } 48 }
47 $http.get(url, null).then(function success(response) { 49 $http.get(url, null).then(function success(response) {
48 - deferred.resolve(response.data); 50 + customerService.applyAssignedCustomersInfo(response.data.data).then(
  51 + function success(data) {
  52 + response.data.data = data;
  53 + deferred.resolve(response.data);
  54 + },
  55 + function fail() {
  56 + deferred.reject();
  57 + }
  58 + );
49 }, function fail() { 59 }, function fail() {
50 deferred.reject(); 60 deferred.reject();
51 }); 61 });
@@ -65,7 +75,15 @@ function DashboardService($http, $q) { @@ -65,7 +75,15 @@ function DashboardService($http, $q) {
65 url += '&textOffset=' + pageLink.textOffset; 75 url += '&textOffset=' + pageLink.textOffset;
66 } 76 }
67 $http.get(url, null).then(function success(response) { 77 $http.get(url, null).then(function success(response) {
68 - deferred.resolve(response.data); 78 + customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
  79 + function success(data) {
  80 + response.data.data = data;
  81 + deferred.resolve(response.data);
  82 + },
  83 + function fail() {
  84 + deferred.reject();
  85 + }
  86 + );
69 }, function fail() { 87 }, function fail() {
70 deferred.reject(); 88 deferred.reject();
71 }); 89 });
@@ -92,8 +110,8 @@ function DashboardService($http, $q) { @@ -92,8 +110,8 @@ function DashboardService($http, $q) {
92 var url = '/api/dashboard/' + dashboardId; 110 var url = '/api/dashboard/' + dashboardId;
93 $http.get(url, null).then(function success(response) { 111 $http.get(url, null).then(function success(response) {
94 deferred.resolve(response.data); 112 deferred.resolve(response.data);
95 - }, function fail(response) {  
96 - deferred.reject(response.data); 113 + }, function fail() {
  114 + deferred.reject();
97 }); 115 });
98 return deferred.promise; 116 return deferred.promise;
99 } 117 }
@@ -103,8 +121,8 @@ function DashboardService($http, $q) { @@ -103,8 +121,8 @@ function DashboardService($http, $q) {
103 var url = '/api/dashboard'; 121 var url = '/api/dashboard';
104 $http.post(url, dashboard).then(function success(response) { 122 $http.post(url, dashboard).then(function success(response) {
105 deferred.resolve(response.data); 123 deferred.resolve(response.data);
106 - }, function fail(response) {  
107 - deferred.reject(response.data); 124 + }, function fail() {
  125 + deferred.reject();
108 }); 126 });
109 return deferred.promise; 127 return deferred.promise;
110 } 128 }
@@ -114,8 +132,8 @@ function DashboardService($http, $q) { @@ -114,8 +132,8 @@ function DashboardService($http, $q) {
114 var url = '/api/dashboard/' + dashboardId; 132 var url = '/api/dashboard/' + dashboardId;
115 $http.delete(url).then(function success() { 133 $http.delete(url).then(function success() {
116 deferred.resolve(); 134 deferred.resolve();
117 - }, function fail(response) {  
118 - deferred.reject(response.data); 135 + }, function fail() {
  136 + deferred.reject();
119 }); 137 });
120 return deferred.promise; 138 return deferred.promise;
121 } 139 }
@@ -123,10 +141,10 @@ function DashboardService($http, $q) { @@ -123,10 +141,10 @@ function DashboardService($http, $q) {
123 function assignDashboardToCustomer(customerId, dashboardId) { 141 function assignDashboardToCustomer(customerId, dashboardId) {
124 var deferred = $q.defer(); 142 var deferred = $q.defer();
125 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId; 143 var url = '/api/customer/' + customerId + '/dashboard/' + dashboardId;
126 - $http.post(url, null).then(function success() {  
127 - deferred.resolve();  
128 - }, function fail(response) {  
129 - deferred.reject(response.data); 144 + $http.post(url, null).then(function success(response) {
  145 + deferred.resolve(response.data);
  146 + }, function fail() {
  147 + deferred.reject();
130 }); 148 });
131 return deferred.promise; 149 return deferred.promise;
132 } 150 }
@@ -134,12 +152,33 @@ function DashboardService($http, $q) { @@ -134,12 +152,33 @@ function DashboardService($http, $q) {
134 function unassignDashboardFromCustomer(dashboardId) { 152 function unassignDashboardFromCustomer(dashboardId) {
135 var deferred = $q.defer(); 153 var deferred = $q.defer();
136 var url = '/api/customer/dashboard/' + dashboardId; 154 var url = '/api/customer/dashboard/' + dashboardId;
137 - $http.delete(url).then(function success() {  
138 - deferred.resolve();  
139 - }, function fail(response) {  
140 - deferred.reject(response.data); 155 + $http.delete(url).then(function success(response) {
  156 + deferred.resolve(response.data);
  157 + }, function fail() {
  158 + deferred.reject();
  159 + });
  160 + return deferred.promise;
  161 + }
  162 +
  163 + function makeDashboardPublic(dashboardId) {
  164 + var deferred = $q.defer();
  165 + var url = '/api/customer/public/dashboard/' + dashboardId;
  166 + $http.post(url, null).then(function success(response) {
  167 + deferred.resolve(response.data);
  168 + }, function fail() {
  169 + deferred.reject();
141 }); 170 });
142 return deferred.promise; 171 return deferred.promise;
143 } 172 }
144 173
  174 + function getPublicDashboardLink(dashboard) {
  175 + var url = $location.protocol() + '://' + $location.host();
  176 + var port = $location.port();
  177 + if (port != 80 && port != 443) {
  178 + url += ":" + port;
  179 + }
  180 + url += "/dashboards/" + dashboard.id.id + "?publicId=" + dashboard.customerId.id;
  181 + return url;
  182 + }
  183 +
145 } 184 }
@@ -58,10 +58,10 @@ function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, t @@ -58,10 +58,10 @@ function DatasourceService($timeout, $filter, $log, telemetryWebsocketService, t
58 var datasourceSubscription = { 58 var datasourceSubscription = {
59 datasourceType: datasource.type, 59 datasourceType: datasource.type,
60 dataKeys: subscriptionDataKeys, 60 dataKeys: subscriptionDataKeys,
61 - type: listener.widget.type 61 + type: listener.subscriptionType
62 }; 62 };
63 63
64 - if (listener.widget.type === types.widgetType.timeseries.value) { 64 + if (listener.subscriptionType === types.widgetType.timeseries.value) {
65 datasourceSubscription.subscriptionTimewindow = angular.copy(listener.subscriptionTimewindow); 65 datasourceSubscription.subscriptionTimewindow = angular.copy(listener.subscriptionTimewindow);
66 } 66 }
67 if (datasourceSubscription.datasourceType === types.datasourceType.device) { 67 if (datasourceSubscription.datasourceType === types.datasourceType.device) {
@@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.device', [thingsboardTypes]) @@ -20,7 +20,7 @@ export default angular.module('thingsboard.api.device', [thingsboardTypes])
20 .name; 20 .name;
21 21
22 /*@ngInject*/ 22 /*@ngInject*/
23 -function DeviceService($http, $q, $filter, userService, telemetryWebsocketService, types) { 23 +function DeviceService($http, $q, $filter, userService, customerService, telemetryWebsocketService, types) {
24 24
25 25
26 var deviceAttributesSubscriptionMap = {}; 26 var deviceAttributesSubscriptionMap = {};
@@ -33,6 +33,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -33,6 +33,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
33 getDevices: getDevices, 33 getDevices: getDevices,
34 processDeviceAliases: processDeviceAliases, 34 processDeviceAliases: processDeviceAliases,
35 checkDeviceAlias: checkDeviceAlias, 35 checkDeviceAlias: checkDeviceAlias,
  36 + fetchAliasDeviceByNameFilter: fetchAliasDeviceByNameFilter,
36 getDeviceCredentials: getDeviceCredentials, 37 getDeviceCredentials: getDeviceCredentials,
37 getDeviceKeys: getDeviceKeys, 38 getDeviceKeys: getDeviceKeys,
38 getDeviceTimeseriesValues: getDeviceTimeseriesValues, 39 getDeviceTimeseriesValues: getDeviceTimeseriesValues,
@@ -40,6 +41,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -40,6 +41,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
40 saveDevice: saveDevice, 41 saveDevice: saveDevice,
41 saveDeviceCredentials: saveDeviceCredentials, 42 saveDeviceCredentials: saveDeviceCredentials,
42 unassignDeviceFromCustomer: unassignDeviceFromCustomer, 43 unassignDeviceFromCustomer: unassignDeviceFromCustomer,
  44 + makeDevicePublic: makeDevicePublic,
43 getDeviceAttributes: getDeviceAttributes, 45 getDeviceAttributes: getDeviceAttributes,
44 subscribeForDeviceAttributes: subscribeForDeviceAttributes, 46 subscribeForDeviceAttributes: subscribeForDeviceAttributes,
45 unsubscribeForDeviceAttributes: unsubscribeForDeviceAttributes, 47 unsubscribeForDeviceAttributes: unsubscribeForDeviceAttributes,
@@ -51,7 +53,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -51,7 +53,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
51 53
52 return service; 54 return service;
53 55
54 - function getTenantDevices(pageLink) { 56 + function getTenantDevices(pageLink, applyCustomersInfo, config) {
55 var deferred = $q.defer(); 57 var deferred = $q.defer();
56 var url = '/api/tenant/devices?limit=' + pageLink.limit; 58 var url = '/api/tenant/devices?limit=' + pageLink.limit;
57 if (angular.isDefined(pageLink.textSearch)) { 59 if (angular.isDefined(pageLink.textSearch)) {
@@ -63,15 +65,27 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -63,15 +65,27 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
63 if (angular.isDefined(pageLink.textOffset)) { 65 if (angular.isDefined(pageLink.textOffset)) {
64 url += '&textOffset=' + pageLink.textOffset; 66 url += '&textOffset=' + pageLink.textOffset;
65 } 67 }
66 - $http.get(url, null).then(function success(response) {  
67 - deferred.resolve(response.data); 68 + $http.get(url, config).then(function success(response) {
  69 + if (applyCustomersInfo) {
  70 + customerService.applyAssignedCustomersInfo(response.data.data).then(
  71 + function success(data) {
  72 + response.data.data = data;
  73 + deferred.resolve(response.data);
  74 + },
  75 + function fail() {
  76 + deferred.reject();
  77 + }
  78 + );
  79 + } else {
  80 + deferred.resolve(response.data);
  81 + }
68 }, function fail() { 82 }, function fail() {
69 deferred.reject(); 83 deferred.reject();
70 }); 84 });
71 return deferred.promise; 85 return deferred.promise;
72 } 86 }
73 87
74 - function getCustomerDevices(customerId, pageLink) { 88 + function getCustomerDevices(customerId, pageLink, applyCustomersInfo, config) {
75 var deferred = $q.defer(); 89 var deferred = $q.defer();
76 var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit; 90 var url = '/api/customer/' + customerId + '/devices?limit=' + pageLink.limit;
77 if (angular.isDefined(pageLink.textSearch)) { 91 if (angular.isDefined(pageLink.textSearch)) {
@@ -83,18 +97,35 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -83,18 +97,35 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
83 if (angular.isDefined(pageLink.textOffset)) { 97 if (angular.isDefined(pageLink.textOffset)) {
84 url += '&textOffset=' + pageLink.textOffset; 98 url += '&textOffset=' + pageLink.textOffset;
85 } 99 }
86 - $http.get(url, null).then(function success(response) {  
87 - deferred.resolve(response.data); 100 + $http.get(url, config).then(function success(response) {
  101 + if (applyCustomersInfo) {
  102 + customerService.applyAssignedCustomerInfo(response.data.data, customerId).then(
  103 + function success(data) {
  104 + response.data.data = data;
  105 + deferred.resolve(response.data);
  106 + },
  107 + function fail() {
  108 + deferred.reject();
  109 + }
  110 + );
  111 + } else {
  112 + deferred.resolve(response.data);
  113 + }
88 }, function fail() { 114 }, function fail() {
89 deferred.reject(); 115 deferred.reject();
90 }); 116 });
  117 +
91 return deferred.promise; 118 return deferred.promise;
92 } 119 }
93 120
94 - function getDevice(deviceId, ignoreErrors) { 121 + function getDevice(deviceId, ignoreErrors, config) {
95 var deferred = $q.defer(); 122 var deferred = $q.defer();
96 var url = '/api/device/' + deviceId; 123 var url = '/api/device/' + deviceId;
97 - $http.get(url, { ignoreErrors: ignoreErrors }).then(function success(response) { 124 + if (!config) {
  125 + config = {};
  126 + }
  127 + config = Object.assign(config, { ignoreErrors: ignoreErrors });
  128 + $http.get(url, config).then(function success(response) {
98 deferred.resolve(response.data); 129 deferred.resolve(response.data);
99 }, function fail(response) { 130 }, function fail(response) {
100 deferred.reject(response.data); 131 deferred.reject(response.data);
@@ -102,7 +133,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -102,7 +133,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
102 return deferred.promise; 133 return deferred.promise;
103 } 134 }
104 135
105 - function getDevices(deviceIds) { 136 + function getDevices(deviceIds, config) {
106 var deferred = $q.defer(); 137 var deferred = $q.defer();
107 var ids = ''; 138 var ids = '';
108 for (var i=0;i<deviceIds.length;i++) { 139 for (var i=0;i<deviceIds.length;i++) {
@@ -112,7 +143,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -112,7 +143,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
112 ids += deviceIds[i]; 143 ids += deviceIds[i];
113 } 144 }
114 var url = '/api/devices?deviceIds=' + ids; 145 var url = '/api/devices?deviceIds=' + ids;
115 - $http.get(url, null).then(function success(response) { 146 + $http.get(url, config).then(function success(response) {
116 var devices = response.data; 147 var devices = response.data;
117 devices.sort(function (device1, device2) { 148 devices.sort(function (device1, device2) {
118 var id1 = device1.id.id; 149 var id1 = device1.id.id;
@@ -128,16 +159,16 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -128,16 +159,16 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
128 return deferred.promise; 159 return deferred.promise;
129 } 160 }
130 161
131 - function fetchAliasDeviceByNameFilter(deviceNameFilter, limit) { 162 + function fetchAliasDeviceByNameFilter(deviceNameFilter, limit, applyCustomersInfo, config) {
132 var deferred = $q.defer(); 163 var deferred = $q.defer();
133 var user = userService.getCurrentUser(); 164 var user = userService.getCurrentUser();
134 var promise; 165 var promise;
135 var pageLink = {limit: limit, textSearch: deviceNameFilter}; 166 var pageLink = {limit: limit, textSearch: deviceNameFilter};
136 if (user.authority === 'CUSTOMER_USER') { 167 if (user.authority === 'CUSTOMER_USER') {
137 var customerId = user.customerId; 168 var customerId = user.customerId;
138 - promise = getCustomerDevices(customerId, pageLink); 169 + promise = getCustomerDevices(customerId, pageLink, applyCustomersInfo, config);
139 } else { 170 } else {
140 - promise = getTenantDevices(pageLink); 171 + promise = getTenantDevices(pageLink, applyCustomersInfo, config);
141 } 172 }
142 promise.then( 173 promise.then(
143 function success(result) { 174 function success(result) {
@@ -194,7 +225,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -194,7 +225,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
194 var deviceFilter = deviceAlias.deviceFilter; 225 var deviceFilter = deviceAlias.deviceFilter;
195 if (deviceFilter.useFilter) { 226 if (deviceFilter.useFilter) {
196 var deviceNameFilter = deviceFilter.deviceNameFilter; 227 var deviceNameFilter = deviceFilter.deviceNameFilter;
197 - fetchAliasDeviceByNameFilter(deviceNameFilter, 100).then( 228 + fetchAliasDeviceByNameFilter(deviceNameFilter, 100, false).then(
198 function(devices) { 229 function(devices) {
199 if (devices && devices != null) { 230 if (devices && devices != null) {
200 var resolvedAlias = {alias: alias, deviceId: devices[0].id.id}; 231 var resolvedAlias = {alias: alias, deviceId: devices[0].id.id};
@@ -276,7 +307,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -276,7 +307,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
276 var promise; 307 var promise;
277 if (deviceFilter.useFilter) { 308 if (deviceFilter.useFilter) {
278 var deviceNameFilter = deviceFilter.deviceNameFilter; 309 var deviceNameFilter = deviceFilter.deviceNameFilter;
279 - promise = fetchAliasDeviceByNameFilter(deviceNameFilter, 1); 310 + promise = fetchAliasDeviceByNameFilter(deviceNameFilter, 1, false);
280 } else { 311 } else {
281 var deviceList = deviceFilter.deviceList; 312 var deviceList = deviceFilter.deviceList;
282 promise = getDevices(deviceList); 313 promise = getDevices(deviceList);
@@ -301,8 +332,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -301,8 +332,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
301 var url = '/api/device'; 332 var url = '/api/device';
302 $http.post(url, device).then(function success(response) { 333 $http.post(url, device).then(function success(response) {
303 deferred.resolve(response.data); 334 deferred.resolve(response.data);
304 - }, function fail(response) {  
305 - deferred.reject(response.data); 335 + }, function fail() {
  336 + deferred.reject();
306 }); 337 });
307 return deferred.promise; 338 return deferred.promise;
308 } 339 }
@@ -312,8 +343,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -312,8 +343,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
312 var url = '/api/device/' + deviceId; 343 var url = '/api/device/' + deviceId;
313 $http.delete(url).then(function success() { 344 $http.delete(url).then(function success() {
314 deferred.resolve(); 345 deferred.resolve();
315 - }, function fail(response) {  
316 - deferred.reject(response.data); 346 + }, function fail() {
  347 + deferred.reject();
317 }); 348 });
318 return deferred.promise; 349 return deferred.promise;
319 } 350 }
@@ -323,8 +354,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -323,8 +354,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
323 var url = '/api/device/' + deviceId + '/credentials'; 354 var url = '/api/device/' + deviceId + '/credentials';
324 $http.get(url, null).then(function success(response) { 355 $http.get(url, null).then(function success(response) {
325 deferred.resolve(response.data); 356 deferred.resolve(response.data);
326 - }, function fail(response) {  
327 - deferred.reject(response.data); 357 + }, function fail() {
  358 + deferred.reject();
328 }); 359 });
329 return deferred.promise; 360 return deferred.promise;
330 } 361 }
@@ -334,8 +365,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -334,8 +365,8 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
334 var url = '/api/device/credentials'; 365 var url = '/api/device/credentials';
335 $http.post(url, deviceCredentials).then(function success(response) { 366 $http.post(url, deviceCredentials).then(function success(response) {
336 deferred.resolve(response.data); 367 deferred.resolve(response.data);
337 - }, function fail(response) {  
338 - deferred.reject(response.data); 368 + }, function fail() {
  369 + deferred.reject();
339 }); 370 });
340 return deferred.promise; 371 return deferred.promise;
341 } 372 }
@@ -343,10 +374,10 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -343,10 +374,10 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
343 function assignDeviceToCustomer(customerId, deviceId) { 374 function assignDeviceToCustomer(customerId, deviceId) {
344 var deferred = $q.defer(); 375 var deferred = $q.defer();
345 var url = '/api/customer/' + customerId + '/device/' + deviceId; 376 var url = '/api/customer/' + customerId + '/device/' + deviceId;
346 - $http.post(url, null).then(function success() {  
347 - deferred.resolve();  
348 - }, function fail(response) {  
349 - deferred.reject(response.data); 377 + $http.post(url, null).then(function success(response) {
  378 + deferred.resolve(response.data);
  379 + }, function fail() {
  380 + deferred.reject();
350 }); 381 });
351 return deferred.promise; 382 return deferred.promise;
352 } 383 }
@@ -354,10 +385,21 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -354,10 +385,21 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
354 function unassignDeviceFromCustomer(deviceId) { 385 function unassignDeviceFromCustomer(deviceId) {
355 var deferred = $q.defer(); 386 var deferred = $q.defer();
356 var url = '/api/customer/device/' + deviceId; 387 var url = '/api/customer/device/' + deviceId;
357 - $http.delete(url).then(function success() {  
358 - deferred.resolve();  
359 - }, function fail(response) {  
360 - deferred.reject(response.data); 388 + $http.delete(url).then(function success(response) {
  389 + deferred.resolve(response.data);
  390 + }, function fail() {
  391 + deferred.reject();
  392 + });
  393 + return deferred.promise;
  394 + }
  395 +
  396 + function makeDevicePublic(deviceId) {
  397 + var deferred = $q.defer();
  398 + var url = '/api/customer/public/device/' + deviceId;
  399 + $http.post(url, null).then(function success(response) {
  400 + deferred.resolve(response.data);
  401 + }, function fail() {
  402 + deferred.reject();
361 }); 403 });
362 return deferred.promise; 404 return deferred.promise;
363 } 405 }
@@ -425,7 +467,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -425,7 +467,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
425 } 467 }
426 } 468 }
427 469
428 - function getDeviceAttributes(deviceId, attributeScope, query, successCallback) { 470 + function getDeviceAttributes(deviceId, attributeScope, query, successCallback, config) {
429 var deferred = $q.defer(); 471 var deferred = $q.defer();
430 var subscriptionId = deviceId + attributeScope; 472 var subscriptionId = deviceId + attributeScope;
431 var das = deviceAttributesSubscriptionMap[subscriptionId]; 473 var das = deviceAttributesSubscriptionMap[subscriptionId];
@@ -445,7 +487,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic @@ -445,7 +487,7 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic
445 } 487 }
446 } else { 488 } else {
447 var url = '/api/plugins/telemetry/' + deviceId + '/values/attributes/' + attributeScope; 489 var url = '/api/plugins/telemetry/' + deviceId + '/values/attributes/' + attributeScope;
448 - $http.get(url, null).then(function success(response) { 490 + $http.get(url, config).then(function success(response) {
449 processDeviceAttributes(response.data, query, deferred, successCallback); 491 processDeviceAttributes(response.data, query, deferred, successCallback);
450 }, function fail() { 492 }, function fail() {
451 deferred.reject(); 493 deferred.reject();
@@ -25,6 +25,7 @@ function LoginService($http, $q) { @@ -25,6 +25,7 @@ function LoginService($http, $q) {
25 changePassword: changePassword, 25 changePassword: changePassword,
26 hasUser: hasUser, 26 hasUser: hasUser,
27 login: login, 27 login: login,
  28 + publicLogin: publicLogin,
28 resetPassword: resetPassword, 29 resetPassword: resetPassword,
29 sendResetPasswordLink: sendResetPasswordLink, 30 sendResetPasswordLink: sendResetPasswordLink,
30 } 31 }
@@ -49,6 +50,19 @@ function LoginService($http, $q) { @@ -49,6 +50,19 @@ function LoginService($http, $q) {
49 return deferred.promise; 50 return deferred.promise;
50 } 51 }
51 52
  53 + function publicLogin(publicId) {
  54 + var deferred = $q.defer();
  55 + var pubilcLoginRequest = {
  56 + publicId: publicId
  57 + };
  58 + $http.post('/api/auth/login/public', pubilcLoginRequest).then(function success(response) {
  59 + deferred.resolve(response);
  60 + }, function fail(response) {
  61 + deferred.reject(response);
  62 + });
  63 + return deferred.promise;
  64 + }
  65 +
52 function sendResetPasswordLink(email) { 66 function sendResetPasswordLink(email) {
53 var deferred = $q.defer(); 67 var deferred = $q.defer();
54 var url = '/api/noauth/resetPasswordByEmail?email=' + email; 68 var url = '/api/noauth/resetPasswordByEmail?email=' + email;
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +/*
  18 + options = {
  19 + type,
  20 + targetDeviceAliasIds, // RPC
  21 + targetDeviceIds, // RPC
  22 + datasources,
  23 + timeWindowConfig,
  24 + useDashboardTimewindow,
  25 + legendConfig,
  26 + decimals,
  27 + units,
  28 + callbacks
  29 + }
  30 + */
  31 +
  32 +export default class Subscription {
  33 + constructor(subscriptionContext, options) {
  34 +
  35 + this.ctx = subscriptionContext;
  36 + this.type = options.type;
  37 + this.callbacks = options.callbacks;
  38 + this.id = this.ctx.utils.guid();
  39 + this.cafs = {};
  40 + this.registrations = [];
  41 +
  42 + if (this.type === this.ctx.types.widgetType.rpc.value) {
  43 + this.callbacks.rpcStateChanged = this.callbacks.rpcStateChanged || function(){};
  44 + this.callbacks.onRpcSuccess = this.callbacks.onRpcSuccess || function(){};
  45 + this.callbacks.onRpcFailed = this.callbacks.onRpcFailed || function(){};
  46 + this.callbacks.onRpcErrorCleared = this.callbacks.onRpcErrorCleared || function(){};
  47 +
  48 + this.targetDeviceAliasIds = options.targetDeviceAliasIds;
  49 + this.targetDeviceIds = options.targetDeviceIds;
  50 +
  51 + this.targetDeviceAliasId = null;
  52 + this.targetDeviceId = null;
  53 +
  54 + this.rpcRejection = null;
  55 + this.rpcErrorText = null;
  56 + this.rpcEnabled = false;
  57 + this.executingRpcRequest = false;
  58 + this.executingPromises = [];
  59 + this.initRpc();
  60 + } else {
  61 + this.callbacks.onDataUpdated = this.callbacks.onDataUpdated || function(){};
  62 + this.callbacks.onDataUpdateError = this.callbacks.onDataUpdateError || function(){};
  63 + this.callbacks.dataLoading = this.callbacks.dataLoading || function(){};
  64 + this.callbacks.legendDataUpdated = this.callbacks.legendDataUpdated || function(){};
  65 + this.callbacks.timeWindowUpdated = this.callbacks.timeWindowUpdated || function(){};
  66 +
  67 + this.datasources = options.datasources;
  68 + this.datasourceListeners = [];
  69 + this.data = [];
  70 + this.hiddenData = [];
  71 + this.originalTimewindow = null;
  72 + this.timeWindow = {
  73 + stDiff: this.ctx.stDiff
  74 + }
  75 + this.useDashboardTimewindow = options.useDashboardTimewindow;
  76 +
  77 + if (this.useDashboardTimewindow) {
  78 + this.timeWindowConfig = angular.copy(options.dashboardTimewindow);
  79 + } else {
  80 + this.timeWindowConfig = angular.copy(options.timeWindowConfig);
  81 + }
  82 +
  83 + this.subscriptionTimewindow = null;
  84 +
  85 + this.units = options.units || '';
  86 + this.decimals = angular.isDefined(options.decimals) ? options.decimals : 2;
  87 +
  88 + this.loadingData = false;
  89 +
  90 + if (options.legendConfig) {
  91 + this.legendConfig = options.legendConfig;
  92 + this.legendData = {
  93 + keys: [],
  94 + data: []
  95 + };
  96 + this.displayLegend = true;
  97 + } else {
  98 + this.displayLegend = false;
  99 + }
  100 + this.caulculateLegendData = this.displayLegend &&
  101 + this.type === this.ctx.types.widgetType.timeseries.value &&
  102 + (this.legendConfig.showMin === true ||
  103 + this.legendConfig.showMax === true ||
  104 + this.legendConfig.showAvg === true ||
  105 + this.legendConfig.showTotal === true);
  106 + this.initDataSubscription();
  107 + }
  108 + }
  109 +
  110 + initDataSubscription() {
  111 + var dataIndex = 0;
  112 + for (var i = 0; i < this.datasources.length; i++) {
  113 + var datasource = this.datasources[i];
  114 + for (var a = 0; a < datasource.dataKeys.length; a++) {
  115 + var dataKey = datasource.dataKeys[a];
  116 + dataKey.pattern = angular.copy(dataKey.label);
  117 + var datasourceData = {
  118 + datasource: datasource,
  119 + dataKey: dataKey,
  120 + data: []
  121 + };
  122 + this.data.push(datasourceData);
  123 + this.hiddenData.push({data: []});
  124 + if (this.displayLegend) {
  125 + var legendKey = {
  126 + dataKey: dataKey,
  127 + dataIndex: dataIndex++
  128 + };
  129 + this.legendData.keys.push(legendKey);
  130 + var legendKeyData = {
  131 + min: null,
  132 + max: null,
  133 + avg: null,
  134 + total: null,
  135 + hidden: false
  136 + };
  137 + this.legendData.data.push(legendKeyData);
  138 + }
  139 + }
  140 + }
  141 +
  142 + var subscription = this;
  143 + var registration;
  144 +
  145 + if (this.displayLegend) {
  146 + this.legendData.keys = this.ctx.$filter('orderBy')(this.legendData.keys, '+label');
  147 + registration = this.ctx.$scope.$watch(
  148 + function() {
  149 + return subscription.legendData.data;
  150 + },
  151 + function (newValue, oldValue) {
  152 + for(var i = 0; i < newValue.length; i++) {
  153 + if(newValue[i].hidden != oldValue[i].hidden) {
  154 + subscription.updateDataVisibility(i);
  155 + }
  156 + }
  157 + }, true);
  158 + this.registrations.push(registration);
  159 + }
  160 +
  161 + if (this.type === this.ctx.types.widgetType.timeseries.value) {
  162 + if (this.useDashboardTimewindow) {
  163 + registration = this.ctx.$scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) {
  164 + if (!angular.equals(subscription.timeWindowConfig, newDashboardTimewindow) && newDashboardTimewindow) {
  165 + subscription.timeWindowConfig = angular.copy(newDashboardTimewindow);
  166 + subscription.unsubscribe();
  167 + subscription.subscribe();
  168 + }
  169 + });
  170 + this.registrations.push(registration);
  171 + } else {
  172 + this.startWatchingTimewindow();
  173 + }
  174 + }
  175 +
  176 + registration = this.ctx.$scope.$on('deviceAliasListChanged', function () {
  177 + subscription.checkSubscriptions();
  178 + });
  179 +
  180 + this.registrations.push(registration);
  181 + }
  182 +
  183 + startWatchingTimewindow() {
  184 + var subscription = this;
  185 + this.timeWindowWatchRegistration = this.ctx.$scope.$watch(function () {
  186 + return subscription.timeWindowConfig;
  187 + }, function (newTimewindow, prevTimewindow) {
  188 + if (!angular.equals(newTimewindow, prevTimewindow)) {
  189 + subscription.unsubscribe();
  190 + subscription.subscribe();
  191 + }
  192 + }, true);
  193 + this.registrations.push(this.timeWindowWatchRegistration);
  194 + }
  195 +
  196 + stopWatchingTimewindow() {
  197 + if (this.timeWindowWatchRegistration) {
  198 + this.timeWindowWatchRegistration();
  199 + var index = this.registrations.indexOf(this.timeWindowWatchRegistration);
  200 + if (index > -1) {
  201 + this.registrations.splice(index, 1);
  202 + }
  203 + }
  204 + }
  205 +
  206 + initRpc() {
  207 +
  208 + if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) {
  209 + this.targetDeviceAliasId = this.targetDeviceAliasIds[0];
  210 + if (this.ctx.aliasesInfo.deviceAliases[this.targetDeviceAliasId]) {
  211 + this.targetDeviceId = this.ctx.aliasesInfo.deviceAliases[this.targetDeviceAliasId].deviceId;
  212 + }
  213 + var subscription = this;
  214 + var registration = this.ctx.$scope.$on('deviceAliasListChanged', function () {
  215 + var deviceId = null;
  216 + if (subscription.ctx.aliasesInfo.deviceAliases[subscription.targetDeviceAliasId]) {
  217 + deviceId = subscription.ctx.aliasesInfo.deviceAliases[subscription.targetDeviceAliasId].deviceId;
  218 + }
  219 + if (!angular.equals(deviceId, subscription.targetDeviceId)) {
  220 + subscription.targetDeviceId = deviceId;
  221 + if (subscription.targetDeviceId) {
  222 + subscription.rpcEnabled = true;
  223 + } else {
  224 + subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false;
  225 + }
  226 + subscription.callbacks.rpcStateChanged(subscription);
  227 + }
  228 + });
  229 + this.registrations.push(registration);
  230 + } else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) {
  231 + this.targetDeviceId = this.targetDeviceIds[0];
  232 + }
  233 +
  234 + if (this.targetDeviceId) {
  235 + this.rpcEnabled = true;
  236 + } else {
  237 + this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false;
  238 + }
  239 + this.callbacks.rpcStateChanged(this);
  240 + }
  241 +
  242 + clearRpcError() {
  243 + this.rpcRejection = null;
  244 + this.rpcErrorText = null;
  245 + this.callbacks.onRpcErrorCleared(this);
  246 + }
  247 +
  248 + sendOneWayCommand(method, params, timeout) {
  249 + return this.sendCommand(true, method, params, timeout);
  250 + }
  251 +
  252 + sendTwoWayCommand(method, params, timeout) {
  253 + return this.sendCommand(false, method, params, timeout);
  254 + }
  255 +
  256 + sendCommand(oneWayElseTwoWay, method, params, timeout) {
  257 + if (!this.rpcEnabled) {
  258 + return this.ctx.$q.reject();
  259 + }
  260 +
  261 + if (this.rpcRejection && this.rpcRejection.status !== 408) {
  262 + this.rpcRejection = null;
  263 + this.rpcErrorText = null;
  264 + this.callbacks.onRpcErrorCleared(this);
  265 + }
  266 +
  267 + var subscription = this;
  268 +
  269 + var requestBody = {
  270 + method: method,
  271 + params: params
  272 + };
  273 +
  274 + if (timeout && timeout > 0) {
  275 + requestBody.timeout = timeout;
  276 + }
  277 +
  278 + var deferred = this.ctx.$q.defer();
  279 + this.executingRpcRequest = true;
  280 + this.callbacks.rpcStateChanged(this);
  281 + if (this.ctx.$scope.widgetEditMode) {
  282 + this.ctx.$timeout(function() {
  283 + subscription.executingRpcRequest = false;
  284 + subscription.callbacks.rpcStateChanged(subscription);
  285 + if (oneWayElseTwoWay) {
  286 + deferred.resolve();
  287 + } else {
  288 + deferred.resolve(requestBody);
  289 + }
  290 + }, 500);
  291 + } else {
  292 + this.executingPromises.push(deferred.promise);
  293 + var targetSendFunction = oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand : this.ctx.deviceService.sendTwoWayRpcCommand;
  294 + targetSendFunction(this.targetDeviceId, requestBody).then(
  295 + function success(responseBody) {
  296 + subscription.rpcRejection = null;
  297 + subscription.rpcErrorText = null;
  298 + var index = subscription.executingPromises.indexOf(deferred.promise);
  299 + if (index >= 0) {
  300 + subscription.executingPromises.splice( index, 1 );
  301 + }
  302 + subscription.executingRpcRequest = subscription.executingPromises.length > 0;
  303 + subscription.callbacks.onRpcSuccess(subscription);
  304 + deferred.resolve(responseBody);
  305 + },
  306 + function fail(rejection) {
  307 + var index = subscription.executingPromises.indexOf(deferred.promise);
  308 + if (index >= 0) {
  309 + subscription.executingPromises.splice( index, 1 );
  310 + }
  311 + subscription.executingRpcRequest = subscription.executingPromises.length > 0;
  312 + subscription.callbacks.rpcStateChanged(subscription);
  313 + if (!subscription.executingRpcRequest || rejection.status === 408) {
  314 + subscription.rpcRejection = rejection;
  315 + if (rejection.status === 408) {
  316 + subscription.rpcErrorText = 'Device is offline.';
  317 + } else {
  318 + subscription.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText;
  319 + if (rejection.data && rejection.data.length > 0) {
  320 + subscription.rpcErrorText += '</br>';
  321 + subscription.rpcErrorText += rejection.data;
  322 + }
  323 + }
  324 + subscription.callbacks.onRpcFailed(subscription);
  325 + }
  326 + deferred.reject(rejection);
  327 + }
  328 + );
  329 + }
  330 + return deferred.promise;
  331 + }
  332 +
  333 + updateDataVisibility(index) {
  334 + var hidden = this.legendData.data[index].hidden;
  335 + if (hidden) {
  336 + this.hiddenData[index].data = this.data[index].data;
  337 + this.data[index].data = [];
  338 + } else {
  339 + this.data[index].data = this.hiddenData[index].data;
  340 + this.hiddenData[index].data = [];
  341 + }
  342 + this.onDataUpdated();
  343 + }
  344 +
  345 + onDataUpdated(apply) {
  346 + if (this.cafs['dataUpdated']) {
  347 + this.cafs['dataUpdated']();
  348 + this.cafs['dataUpdated'] = null;
  349 + }
  350 + var subscription = this;
  351 + this.cafs['dataUpdated'] = this.ctx.tbRaf(function() {
  352 + try {
  353 + subscription.callbacks.onDataUpdated(subscription, apply);
  354 + } catch (e) {
  355 + subscription.callbacks.onDataUpdateError(subscription, e);
  356 + }
  357 + });
  358 + if (apply) {
  359 + this.ctx.$scope.$digest();
  360 + }
  361 + }
  362 +
  363 + updateTimewindowConfig(newTimewindow) {
  364 + this.timeWindowConfig = newTimewindow;
  365 + }
  366 +
  367 + onResetTimewindow() {
  368 + if (this.useDashboardTimewindow) {
  369 + this.ctx.dashboardTimewindowApi.onResetTimewindow();
  370 + } else {
  371 + if (this.originalTimewindow) {
  372 + this.stopWatchingTimewindow();
  373 + this.timeWindowConfig = angular.copy(this.originalTimewindow);
  374 + this.originalTimewindow = null;
  375 + this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
  376 + this.unsubscribe();
  377 + this.subscribe();
  378 + this.startWatchingTimewindow();
  379 + }
  380 + }
  381 + }
  382 +
  383 + onUpdateTimewindow(startTimeMs, endTimeMs) {
  384 + if (this.useDashboardTimewindow) {
  385 + this.ctx.dashboardTimewindowApi.onUpdateTimewindow(startTimeMs, endTimeMs);
  386 + } else {
  387 + this.stopWatchingTimewindow();
  388 + if (!this.originalTimewindow) {
  389 + this.originalTimewindow = angular.copy(this.timeWindowConfig);
  390 + }
  391 + this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs);
  392 + this.callbacks.timeWindowUpdated(this, this.timeWindowConfig);
  393 + this.unsubscribe();
  394 + this.subscribe();
  395 + this.startWatchingTimewindow();
  396 + }
  397 + }
  398 +
  399 + notifyDataLoading() {
  400 + this.loadingData = true;
  401 + this.callbacks.dataLoading(this);
  402 + }
  403 +
  404 + notifyDataLoaded() {
  405 + this.loadingData = false;
  406 + this.callbacks.dataLoading(this);
  407 + }
  408 +
  409 + updateTimewindow() {
  410 + this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000;
  411 + if (this.subscriptionTimewindow.realtimeWindowMs) {
  412 + this.timeWindow.maxTime = (new Date).getTime() + this.timeWindow.stDiff;
  413 + this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs;
  414 + } else if (this.subscriptionTimewindow.fixedWindow) {
  415 + this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs;
  416 + this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs;
  417 + }
  418 + }
  419 +
  420 + updateRealtimeSubscription(subscriptionTimewindow) {
  421 + if (subscriptionTimewindow) {
  422 + this.subscriptionTimewindow = subscriptionTimewindow;
  423 + } else {
  424 + this.subscriptionTimewindow =
  425 + this.ctx.timeService.createSubscriptionTimewindow(
  426 + this.timeWindowConfig,
  427 + this.timeWindow.stDiff);
  428 + }
  429 + this.updateTimewindow();
  430 + return this.subscriptionTimewindow;
  431 + }
  432 +
  433 + dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) {
  434 + this.notifyDataLoaded();
  435 + var update = true;
  436 + var currentData;
  437 + if (this.displayLegend && this.legendData.data[datasourceIndex + dataKeyIndex].hidden) {
  438 + currentData = this.hiddenData[datasourceIndex + dataKeyIndex];
  439 + } else {
  440 + currentData = this.data[datasourceIndex + dataKeyIndex];
  441 + }
  442 + if (this.type === this.ctx.types.widgetType.latest.value) {
  443 + var prevData = currentData.data;
  444 + if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) {
  445 + var prevValue = prevData[0][1];
  446 + if (prevValue === sourceData.data[0][1]) {
  447 + update = false;
  448 + }
  449 + }
  450 + }
  451 + if (update) {
  452 + if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) {
  453 + this.updateTimewindow();
  454 + }
  455 + currentData.data = sourceData.data;
  456 + if (this.caulculateLegendData) {
  457 + this.updateLegend(datasourceIndex + dataKeyIndex, sourceData.data, apply);
  458 + }
  459 + this.onDataUpdated(apply);
  460 + }
  461 + }
  462 +
  463 + updateLegend(dataIndex, data, apply) {
  464 + var legendKeyData = this.legendData.data[dataIndex];
  465 + if (this.legendConfig.showMin) {
  466 + legendKeyData.min = this.ctx.widgetUtils.formatValue(calculateMin(data), this.decimals, this.units);
  467 + }
  468 + if (this.legendConfig.showMax) {
  469 + legendKeyData.max = this.ctx.widgetUtils.formatValue(calculateMax(data), this.decimals, this.units);
  470 + }
  471 + if (this.legendConfig.showAvg) {
  472 + legendKeyData.avg = this.ctx.widgetUtils.formatValue(calculateAvg(data), this.decimals, this.units);
  473 + }
  474 + if (this.legendConfig.showTotal) {
  475 + legendKeyData.total = this.ctx.widgetUtils.formatValue(calculateTotal(data), this.decimals, this.units);
  476 + }
  477 + this.callbacks.legendDataUpdated(this, apply !== false);
  478 + }
  479 +
  480 + subscribe() {
  481 + if (this.type === this.ctx.types.widgetType.rpc.value) {
  482 + return;
  483 + }
  484 + this.notifyDataLoading();
  485 + if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) {
  486 + this.updateRealtimeSubscription();
  487 + if (this.subscriptionTimewindow.fixedWindow) {
  488 + this.onDataUpdated();
  489 + }
  490 + }
  491 + var index = 0;
  492 + for (var i = 0; i < this.datasources.length; i++) {
  493 + var datasource = this.datasources[i];
  494 + if (angular.isFunction(datasource))
  495 + continue;
  496 + var deviceId = null;
  497 + if (datasource.type === this.ctx.types.datasourceType.device) {
  498 + var aliasName = null;
  499 + var deviceName = null;
  500 + if (datasource.deviceId) {
  501 + deviceId = datasource.deviceId;
  502 + datasource.name = datasource.deviceName;
  503 + aliasName = datasource.deviceName;
  504 + deviceName = datasource.deviceName;
  505 + } else if (datasource.deviceAliasId && this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId]) {
  506 + deviceId = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].deviceId;
  507 + datasource.name = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].alias;
  508 + aliasName = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].alias;
  509 + deviceName = '';
  510 + var devicesInfo = this.ctx.aliasesInfo.deviceAliasesInfo[datasource.deviceAliasId];
  511 + for (var d = 0; d < devicesInfo.length; d++) {
  512 + if (devicesInfo[d].id === deviceId) {
  513 + deviceName = devicesInfo[d].name;
  514 + break;
  515 + }
  516 + }
  517 + }
  518 + } else {
  519 + datasource.name = datasource.name || this.ctx.types.datasourceType.function;
  520 + }
  521 + for (var dk = 0; dk < datasource.dataKeys.length; dk++) {
  522 + updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, deviceName, aliasName);
  523 + }
  524 +
  525 + var subscription = this;
  526 +
  527 + var listener = {
  528 + subscriptionType: this.type,
  529 + subscriptionTimewindow: this.subscriptionTimewindow,
  530 + datasource: datasource,
  531 + deviceId: deviceId,
  532 + dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) {
  533 + subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply);
  534 + },
  535 + updateRealtimeSubscription: function () {
  536 + this.subscriptionTimewindow = subscription.updateRealtimeSubscription();
  537 + return this.subscriptionTimewindow;
  538 + },
  539 + setRealtimeSubscription: function (subscriptionTimewindow) {
  540 + subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow));
  541 + },
  542 + datasourceIndex: index
  543 + };
  544 +
  545 + for (var a = 0; a < datasource.dataKeys.length; a++) {
  546 + this.data[index + a].data = [];
  547 + }
  548 +
  549 + index += datasource.dataKeys.length;
  550 +
  551 + this.datasourceListeners.push(listener);
  552 + this.ctx.datasourceService.subscribeToDatasource(listener);
  553 + }
  554 + }
  555 +
  556 + unsubscribe() {
  557 + if (this.type !== this.ctx.types.widgetType.rpc.value) {
  558 + for (var i = 0; i < this.datasourceListeners.length; i++) {
  559 + var listener = this.datasourceListeners[i];
  560 + this.ctx.datasourceService.unsubscribeFromDatasource(listener);
  561 + }
  562 + this.datasourceListeners = [];
  563 + }
  564 + }
  565 +
  566 + checkSubscriptions() {
  567 + var subscriptionsChanged = false;
  568 + for (var i = 0; i < this.datasourceListeners.length; i++) {
  569 + var listener = this.datasourceListeners[i];
  570 + var deviceId = null;
  571 + var aliasName = null;
  572 + if (listener.datasource.type === this.ctx.types.datasourceType.device) {
  573 + if (listener.datasource.deviceAliasId &&
  574 + this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId]) {
  575 + deviceId = this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].deviceId;
  576 + aliasName = this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].alias;
  577 + }
  578 + if (!angular.equals(deviceId, listener.deviceId) ||
  579 + !angular.equals(aliasName, listener.datasource.name)) {
  580 + subscriptionsChanged = true;
  581 + break;
  582 + }
  583 + }
  584 + }
  585 + if (subscriptionsChanged) {
  586 + this.unsubscribe();
  587 + this.subscribe();
  588 + }
  589 + }
  590 +
  591 + destroy() {
  592 + this.unsubscribe();
  593 + for (var cafId in this.cafs) {
  594 + if (this.cafs[cafId]) {
  595 + this.cafs[cafId]();
  596 + this.cafs[cafId] = null;
  597 + }
  598 + }
  599 + this.registrations.forEach(function (registration) {
  600 + registration();
  601 + });
  602 + this.registrations = [];
  603 + }
  604 +
  605 +}
  606 +
  607 +const varsRegex = /\$\{([^\}]*)\}/g;
  608 +
  609 +function updateDataKeyLabel(dataKey, dsName, deviceName, aliasName) {
  610 + var pattern = dataKey.pattern;
  611 + var label = dataKey.pattern;
  612 + var match = varsRegex.exec(pattern);
  613 + while (match !== null) {
  614 + var variable = match[0];
  615 + var variableName = match[1];
  616 + if (variableName === 'dsName') {
  617 + label = label.split(variable).join(dsName);
  618 + } else if (variableName === 'deviceName') {
  619 + label = label.split(variable).join(deviceName);
  620 + } else if (variableName === 'aliasName') {
  621 + label = label.split(variable).join(aliasName);
  622 + }
  623 + match = varsRegex.exec(pattern);
  624 + }
  625 + dataKey.label = label;
  626 +}
  627 +
  628 +function calculateMin(data) {
  629 + if (data.length > 0) {
  630 + var result = Number(data[0][1]);
  631 + for (var i=1;i<data.length;i++) {
  632 + result = Math.min(result, Number(data[i][1]));
  633 + }
  634 + return result;
  635 + } else {
  636 + return null;
  637 + }
  638 +}
  639 +
  640 +function calculateMax(data) {
  641 + if (data.length > 0) {
  642 + var result = Number(data[0][1]);
  643 + for (var i=1;i<data.length;i++) {
  644 + result = Math.max(result, Number(data[i][1]));
  645 + }
  646 + return result;
  647 + } else {
  648 + return null;
  649 + }
  650 +}
  651 +
  652 +function calculateAvg(data) {
  653 + if (data.length > 0) {
  654 + return calculateTotal(data)/data.length;
  655 + } else {
  656 + return null;
  657 + }
  658 +}
  659 +
  660 +function calculateTotal(data) {
  661 + if (data.length > 0) {
  662 + var result = 0;
  663 + for (var i = 0; i < data.length; i++) {
  664 + result += Number(data[i][1]);
  665 + }
  666 + return result;
  667 + } else {
  668 + return null;
  669 + }
  670 +}
@@ -45,15 +45,21 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty @@ -45,15 +45,21 @@ function TelemetryWebsocketService($rootScope, $websocket, $timeout, $window, ty
45 socketCloseTimer, 45 socketCloseTimer,
46 reconnectTimer; 46 reconnectTimer;
47 47
  48 + var port = location.port;
48 if (location.protocol === "https:") { 49 if (location.protocol === "https:") {
  50 + if (!port) {
  51 + port = "443";
  52 + }
49 telemetryUri = "wss:"; 53 telemetryUri = "wss:";
50 } else { 54 } else {
  55 + if (!port) {
  56 + port = "80";
  57 + }
51 telemetryUri = "ws:"; 58 telemetryUri = "ws:";
52 } 59 }
53 - telemetryUri += "//" + location.hostname + ":" + location.port; 60 + telemetryUri += "//" + location.hostname + ":" + port;
54 telemetryUri += "/api/ws/plugins/telemetry"; 61 telemetryUri += "/api/ws/plugins/telemetry";
55 62
56 -  
57 var service = { 63 var service = {
58 subscribe: subscribe, 64 subscribe: subscribe,
59 unsubscribe: unsubscribe 65 unsubscribe: unsubscribe
@@ -22,9 +22,10 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin, @@ -22,9 +22,10 @@ export default angular.module('thingsboard.api.user', [thingsboardApiLogin,
22 .name; 22 .name;
23 23
24 /*@ngInject*/ 24 /*@ngInject*/
25 -function UserService($http, $q, $rootScope, adminService, dashboardService, toast, store, jwtHelper, $translate, $state) { 25 +function UserService($http, $q, $rootScope, adminService, dashboardService, loginService, toast, store, jwtHelper, $translate, $state, $location) {
26 var currentUser = null, 26 var currentUser = null,
27 currentUserDetails = null, 27 currentUserDetails = null,
  28 + lastPublicDashboardId = null,
28 allowedDashboardIds = [], 29 allowedDashboardIds = [],
29 userLoaded = false; 30 userLoaded = false;
30 31
@@ -33,6 +34,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -33,6 +34,9 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
33 var service = { 34 var service = {
34 deleteUser: deleteUser, 35 deleteUser: deleteUser,
35 getAuthority: getAuthority, 36 getAuthority: getAuthority,
  37 + isPublic: isPublic,
  38 + getPublicId: getPublicId,
  39 + parsePublicId: parsePublicId,
36 isAuthenticated: isAuthenticated, 40 isAuthenticated: isAuthenticated,
37 getCurrentUser: getCurrentUser, 41 getCurrentUser: getCurrentUser,
38 getCustomerUsers: getCustomerUsers, 42 getCustomerUsers: getCustomerUsers,
@@ -51,18 +55,25 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -51,18 +55,25 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
51 updateAuthorizationHeader: updateAuthorizationHeader, 55 updateAuthorizationHeader: updateAuthorizationHeader,
52 gotoDefaultPlace: gotoDefaultPlace, 56 gotoDefaultPlace: gotoDefaultPlace,
53 forceDefaultPlace: forceDefaultPlace, 57 forceDefaultPlace: forceDefaultPlace,
54 - logout: logout 58 + updateLastPublicDashboardId: updateLastPublicDashboardId,
  59 + logout: logout,
  60 + reloadUser: reloadUser
55 } 61 }
56 62
57 - loadUser(true).then(function success() {  
58 - notifyUserLoaded();  
59 - }, function fail() {  
60 - notifyUserLoaded();  
61 - }); 63 + reloadUser();
62 64
63 return service; 65 return service;
64 66
65 - function updateAndValidateToken(token, prefix) { 67 + function reloadUser() {
  68 + userLoaded = false;
  69 + loadUser(true).then(function success() {
  70 + notifyUserLoaded();
  71 + }, function fail() {
  72 + notifyUserLoaded();
  73 + });
  74 + }
  75 +
  76 + function updateAndValidateToken(token, prefix, notify) {
66 var valid = false; 77 var valid = false;
67 var tokenData = jwtHelper.decodeToken(token); 78 var tokenData = jwtHelper.decodeToken(token);
68 var issuedAt = tokenData.iat; 79 var issuedAt = tokenData.iat;
@@ -76,7 +87,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -76,7 +87,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
76 valid = true; 87 valid = true;
77 } 88 }
78 } 89 }
79 - if (!valid) { 90 + if (!valid && notify) {
80 $rootScope.$broadcast('unauthenticated'); 91 $rootScope.$broadcast('unauthenticated');
81 } 92 }
82 } 93 }
@@ -91,6 +102,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -91,6 +102,7 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
91 function setUserFromJwtToken(jwtToken, refreshToken, notify, doLogout) { 102 function setUserFromJwtToken(jwtToken, refreshToken, notify, doLogout) {
92 currentUser = null; 103 currentUser = null;
93 currentUserDetails = null; 104 currentUserDetails = null;
  105 + lastPublicDashboardId = null;
94 allowedDashboardIds = []; 106 allowedDashboardIds = [];
95 if (!jwtToken) { 107 if (!jwtToken) {
96 clearTokenData(); 108 clearTokenData();
@@ -98,8 +110,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -98,8 +110,8 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
98 $rootScope.$broadcast('unauthenticated', doLogout); 110 $rootScope.$broadcast('unauthenticated', doLogout);
99 } 111 }
100 } else { 112 } else {
101 - updateAndValidateToken(jwtToken, 'jwt_token');  
102 - updateAndValidateToken(refreshToken, 'refresh_token'); 113 + updateAndValidateToken(jwtToken, 'jwt_token', true);
  114 + updateAndValidateToken(refreshToken, 'refresh_token', true);
103 if (notify) { 115 if (notify) {
104 loadUser(false).then(function success() { 116 loadUser(false).then(function success() {
105 $rootScope.$broadcast('authenticated'); 117 $rootScope.$broadcast('authenticated');
@@ -213,13 +225,58 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -213,13 +225,58 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
213 } 225 }
214 } 226 }
215 227
  228 + function isPublic() {
  229 + if (currentUser) {
  230 + return currentUser.isPublic;
  231 + } else {
  232 + return false;
  233 + }
  234 + }
  235 +
  236 + function getPublicId() {
  237 + if (isPublic()) {
  238 + return currentUser.sub;
  239 + } else {
  240 + return null;
  241 + }
  242 + }
  243 +
  244 + function parsePublicId() {
  245 + var token = getJwtToken();
  246 + if (token) {
  247 + var tokenData = jwtHelper.decodeToken(token);
  248 + if (tokenData && tokenData.isPublic) {
  249 + return tokenData.sub;
  250 + }
  251 + }
  252 + return null;
  253 + }
  254 +
216 function isUserLoaded() { 255 function isUserLoaded() {
217 return userLoaded; 256 return userLoaded;
218 } 257 }
219 258
220 function loadUser(doTokenRefresh) { 259 function loadUser(doTokenRefresh) {
  260 +
221 var deferred = $q.defer(); 261 var deferred = $q.defer();
222 - if (!currentUser) { 262 +
  263 + function fetchAllowedDashboardIds() {
  264 + var pageLink = {limit: 100};
  265 + dashboardService.getCustomerDashboards(currentUser.customerId, pageLink).then(
  266 + function success(result) {
  267 + var dashboards = result.data;
  268 + for (var d=0;d<dashboards.length;d++) {
  269 + allowedDashboardIds.push(dashboards[d].id.id);
  270 + }
  271 + deferred.resolve();
  272 + },
  273 + function fail() {
  274 + deferred.reject();
  275 + }
  276 + );
  277 + }
  278 +
  279 + function procceedJwtTokenValidate() {
223 validateJwtToken(doTokenRefresh).then(function success() { 280 validateJwtToken(doTokenRefresh).then(function success() {
224 var jwtToken = store.get('jwt_token'); 281 var jwtToken = store.get('jwt_token');
225 currentUser = jwtHelper.decodeToken(jwtToken); 282 currentUser = jwtHelper.decodeToken(jwtToken);
@@ -228,29 +285,19 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -228,29 +285,19 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
228 } else if (currentUser) { 285 } else if (currentUser) {
229 currentUser.authority = "ANONYMOUS"; 286 currentUser.authority = "ANONYMOUS";
230 } 287 }
231 - if (currentUser.userId) { 288 + if (currentUser.isPublic) {
  289 + $rootScope.forceFullscreen = true;
  290 + fetchAllowedDashboardIds();
  291 + } else if (currentUser.userId) {
232 getUser(currentUser.userId).then( 292 getUser(currentUser.userId).then(
233 function success(user) { 293 function success(user) {
234 currentUserDetails = user; 294 currentUserDetails = user;
235 $rootScope.forceFullscreen = false; 295 $rootScope.forceFullscreen = false;
236 - if (currentUserDetails.additionalInfo &&  
237 - currentUserDetails.additionalInfo.defaultDashboardFullscreen) {  
238 - $rootScope.forceFullscreen = currentUserDetails.additionalInfo.defaultDashboardFullscreen === true; 296 + if (userForceFullscreen()) {
  297 + $rootScope.forceFullscreen = true;
239 } 298 }
240 if ($rootScope.forceFullscreen && currentUser.authority === 'CUSTOMER_USER') { 299 if ($rootScope.forceFullscreen && currentUser.authority === 'CUSTOMER_USER') {
241 - var pageLink = {limit: 100};  
242 - dashboardService.getCustomerDashboards(currentUser.customerId, pageLink).then(  
243 - function success(result) {  
244 - var dashboards = result.data;  
245 - for (var d=0;d<dashboards.length;d++) {  
246 - allowedDashboardIds.push(dashboards[d].id.id);  
247 - }  
248 - deferred.resolve();  
249 - },  
250 - function fail() {  
251 - deferred.reject();  
252 - }  
253 - ); 300 + fetchAllowedDashboardIds();
254 } else { 301 } else {
255 deferred.resolve(); 302 deferred.resolve();
256 } 303 }
@@ -265,6 +312,23 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -265,6 +312,23 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
265 }, function fail() { 312 }, function fail() {
266 deferred.reject(); 313 deferred.reject();
267 }); 314 });
  315 + }
  316 +
  317 + if (!currentUser) {
  318 + var locationSearch = $location.search();
  319 + if (locationSearch.publicId) {
  320 + loginService.publicLogin(locationSearch.publicId).then(function success(response) {
  321 + var token = response.data.token;
  322 + var refreshToken = response.data.refreshToken;
  323 + updateAndValidateToken(token, 'jwt_token', false);
  324 + updateAndValidateToken(refreshToken, 'refresh_token', false);
  325 + procceedJwtTokenValidate();
  326 + }, function fail() {
  327 + deferred.reject();
  328 + });
  329 + } else {
  330 + procceedJwtTokenValidate();
  331 + }
268 } else { 332 } else {
269 deferred.resolve(); 333 deferred.resolve();
270 } 334 }
@@ -373,17 +437,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -373,17 +437,17 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
373 function forceDefaultPlace(to, params) { 437 function forceDefaultPlace(to, params) {
374 if (currentUser && isAuthenticated()) { 438 if (currentUser && isAuthenticated()) {
375 if (currentUser.authority === 'CUSTOMER_USER') { 439 if (currentUser.authority === 'CUSTOMER_USER') {
376 - if (currentUserDetails &&  
377 - currentUserDetails.additionalInfo &&  
378 - currentUserDetails.additionalInfo.defaultDashboardId) {  
379 - if ($rootScope.forceFullscreen) {  
380 - if (to.name === 'home.profile') {  
381 - return false;  
382 - } else if (to.name === 'home.dashboards.dashboard' && allowedDashboardIds.indexOf(params.dashboardId) > -1) { 440 + if ((userHasDefaultDashboard() && $rootScope.forceFullscreen) || isPublic()) {
  441 + if (to.name === 'home.profile') {
  442 + if (userHasProfile()) {
383 return false; 443 return false;
384 } else { 444 } else {
385 return true; 445 return true;
386 } 446 }
  447 + } else if (to.name === 'home.dashboards.dashboard' && allowedDashboardIds.indexOf(params.dashboardId) > -1) {
  448 + return false;
  449 + } else {
  450 + return true;
387 } 451 }
388 } 452 }
389 } 453 }
@@ -395,11 +459,12 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -395,11 +459,12 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
395 if (currentUser && isAuthenticated()) { 459 if (currentUser && isAuthenticated()) {
396 var place = 'home.links'; 460 var place = 'home.links';
397 if (currentUser.authority === 'CUSTOMER_USER') { 461 if (currentUser.authority === 'CUSTOMER_USER') {
398 - if (currentUserDetails &&  
399 - currentUserDetails.additionalInfo &&  
400 - currentUserDetails.additionalInfo.defaultDashboardId) { 462 + if (userHasDefaultDashboard()) {
401 place = 'home.dashboards.dashboard'; 463 place = 'home.dashboards.dashboard';
402 params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId}; 464 params = {dashboardId: currentUserDetails.additionalInfo.defaultDashboardId};
  465 + } else if (isPublic()) {
  466 + place = 'home.dashboards.dashboard';
  467 + params = {dashboardId: lastPublicDashboardId};
403 } 468 }
404 } else if (currentUser.authority === 'SYS_ADMIN') { 469 } else if (currentUser.authority === 'SYS_ADMIN') {
405 adminService.checkUpdates().then( 470 adminService.checkUpdates().then(
@@ -416,4 +481,27 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas @@ -416,4 +481,27 @@ function UserService($http, $q, $rootScope, adminService, dashboardService, toas
416 } 481 }
417 } 482 }
418 483
  484 + function userHasDefaultDashboard() {
  485 + return currentUserDetails &&
  486 + currentUserDetails.additionalInfo &&
  487 + currentUserDetails.additionalInfo.defaultDashboardId;
  488 + }
  489 +
  490 + function userForceFullscreen() {
  491 + return (currentUser && currentUser.isPublic) ||
  492 + (currentUserDetails.additionalInfo &&
  493 + currentUserDetails.additionalInfo.defaultDashboardFullscreen &&
  494 + currentUserDetails.additionalInfo.defaultDashboardFullscreen === true);
  495 + }
  496 +
  497 + function userHasProfile() {
  498 + return currentUser && !currentUser.isPublic;
  499 + }
  500 +
  501 + function updateLastPublicDashboardId(dashboardId) {
  502 + if (isPublic()) {
  503 + lastPublicDashboardId = dashboardId;
  504 + }
  505 + }
  506 +
419 } 507 }
@@ -17,7 +17,8 @@ import $ from 'jquery'; @@ -17,7 +17,8 @@ import $ from 'jquery';
17 import moment from 'moment'; 17 import moment from 'moment';
18 import tinycolor from 'tinycolor2'; 18 import tinycolor from 'tinycolor2';
19 19
20 -import thinsboardLedLight from '../components/led-light.directive'; 20 +import thingsboardLedLight from '../components/led-light.directive';
  21 +import thingsboardTimeseriesTableWidget from '../widget/lib/timeseries-table-widget';
21 22
22 import TbFlot from '../widget/lib/flot-widget'; 23 import TbFlot from '../widget/lib/flot-widget';
23 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge'; 24 import TbAnalogueLinearGauge from '../widget/lib/analogue-linear-gauge';
@@ -31,7 +32,8 @@ import cssjs from '../../vendor/css.js/css'; @@ -31,7 +32,8 @@ import cssjs from '../../vendor/css.js/css';
31 import thingsboardTypes from '../common/types.constant'; 32 import thingsboardTypes from '../common/types.constant';
32 import thingsboardUtils from '../common/utils.service'; 33 import thingsboardUtils from '../common/utils.service';
33 34
34 -export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thinsboardLedLight, thingsboardTypes, thingsboardUtils]) 35 +export default angular.module('thingsboard.api.widget', ['oc.lazyLoad', thingsboardLedLight, thingsboardTimeseriesTableWidget,
  36 + thingsboardTypes, thingsboardUtils])
35 .factory('widgetService', WidgetService) 37 .factory('widgetService', WidgetService)
36 .name; 38 .name;
37 39
@@ -539,6 +541,10 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ @@ -539,6 +541,10 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
539 541
540 ' }\n\n' + 542 ' }\n\n' +
541 543
  544 + ' self.useCustomDatasources = function() {\n\n' +
  545 +
  546 + ' }\n\n' +
  547 +
542 ' self.onResize = function() {\n\n' + 548 ' self.onResize = function() {\n\n' +
543 549
544 ' }\n\n' + 550 ' }\n\n' +
@@ -579,6 +585,11 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ @@ -579,6 +585,11 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
579 if (angular.isFunction(widgetTypeInstance.getDataKeySettingsSchema)) { 585 if (angular.isFunction(widgetTypeInstance.getDataKeySettingsSchema)) {
580 result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema(); 586 result.dataKeySettingsSchema = widgetTypeInstance.getDataKeySettingsSchema();
581 } 587 }
  588 + if (angular.isFunction(widgetTypeInstance.useCustomDatasources)) {
  589 + result.useCustomDatasources = widgetTypeInstance.useCustomDatasources();
  590 + } else {
  591 + result.useCustomDatasources = false;
  592 + }
582 return result; 593 return result;
583 } catch (e) { 594 } catch (e) {
584 utils.processWidgetException(e); 595 utils.processWidgetException(e);
@@ -617,6 +628,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ @@ -617,6 +628,7 @@ function WidgetService($rootScope, $http, $q, $filter, $ocLazyLoad, $window, typ
617 if (widgetType.dataKeySettingsSchema) { 628 if (widgetType.dataKeySettingsSchema) {
618 widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema; 629 widgetInfo.typeDataKeySettingsSchema = widgetType.dataKeySettingsSchema;
619 } 630 }
  631 + widgetInfo.useCustomDatasources = widgetType.useCustomDatasources;
620 putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem); 632 putWidgetInfoToCache(widgetInfo, bundleAlias, widgetInfo.alias, isSystem);
621 putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem); 633 putWidgetTypeFunctionToCache(widgetType.widgetTypeFunction, bundleAlias, widgetInfo.alias, isSystem);
622 deferred.resolve(widgetInfo); 634 deferred.resolve(widgetInfo);
@@ -16,6 +16,7 @@ @@ -16,6 +16,7 @@
16 import injectTapEventPlugin from 'react-tap-event-plugin'; 16 import injectTapEventPlugin from 'react-tap-event-plugin';
17 import UrlHandler from './url.handler'; 17 import UrlHandler from './url.handler';
18 import addLocaleKorean from './locale/locale.constant-ko'; 18 import addLocaleKorean from './locale/locale.constant-ko';
  19 +import addLocaleChinese from './locale/locale.constant-zh';
19 20
20 /* eslint-disable import/no-unresolved, import/default */ 21 /* eslint-disable import/no-unresolved, import/default */
21 22
@@ -50,11 +51,15 @@ export default function AppConfig($provide, @@ -50,11 +51,15 @@ export default function AppConfig($provide,
50 $translateProvider.addInterpolation('$translateMessageFormatInterpolation'); 51 $translateProvider.addInterpolation('$translateMessageFormatInterpolation');
51 52
52 addLocaleKorean(locales); 53 addLocaleKorean(locales);
  54 + addLocaleChinese(locales);
53 var $window = angular.injector(['ng']).get('$window'); 55 var $window = angular.injector(['ng']).get('$window');
54 var lang = $window.navigator.language || $window.navigator.userLanguage; 56 var lang = $window.navigator.language || $window.navigator.userLanguage;
55 if (lang === 'ko') { 57 if (lang === 'ko') {
56 $translateProvider.useSanitizeValueStrategy(null); 58 $translateProvider.useSanitizeValueStrategy(null);
57 $translateProvider.preferredLanguage('ko_KR'); 59 $translateProvider.preferredLanguage('ko_KR');
  60 + } else if (lang === 'zh') {
  61 + $translateProvider.useSanitizeValueStrategy(null);
  62 + $translateProvider.preferredLanguage('zh_CN');
58 } 63 }
59 64
60 for (var langKey in locales) { 65 for (var langKey in locales) {
@@ -19,6 +19,7 @@ import angular from 'angular'; @@ -19,6 +19,7 @@ import angular from 'angular';
19 import ngMaterial from 'angular-material'; 19 import ngMaterial from 'angular-material';
20 import ngMdIcons from 'angular-material-icons'; 20 import ngMdIcons from 'angular-material-icons';
21 import ngCookies from 'angular-cookies'; 21 import ngCookies from 'angular-cookies';
  22 +import angularSocialshare from 'angular-socialshare';
22 import 'angular-translate'; 23 import 'angular-translate';
23 import 'angular-translate-loader-static-files'; 24 import 'angular-translate-loader-static-files';
24 import 'angular-translate-storage-local'; 25 import 'angular-translate-storage-local';
@@ -82,6 +83,7 @@ angular.module('thingsboard', [ @@ -82,6 +83,7 @@ angular.module('thingsboard', [
82 ngMaterial, 83 ngMaterial,
83 ngMdIcons, 84 ngMdIcons,
84 ngCookies, 85 ngCookies,
  86 + angularSocialshare,
85 'pascalprecht.translate', 87 'pascalprecht.translate',
86 'mdColorPicker', 88 'mdColorPicker',
87 mdPickers, 89 mdPickers,
@@ -55,8 +55,39 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, @@ -55,8 +55,39 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
55 }); 55 });
56 56
57 $rootScope.stateChangeStartHandle = $rootScope.$on('$stateChangeStart', function (evt, to, params) { 57 $rootScope.stateChangeStartHandle = $rootScope.$on('$stateChangeStart', function (evt, to, params) {
  58 +
  59 + function waitForUserLoaded() {
  60 + if ($rootScope.userLoadedHandle) {
  61 + $rootScope.userLoadedHandle();
  62 + }
  63 + $rootScope.userLoadedHandle = $rootScope.$on('userLoaded', function () {
  64 + $rootScope.userLoadedHandle();
  65 + $state.go(to.name, params);
  66 + });
  67 + }
  68 +
  69 + function reloadUserFromPublicId() {
  70 + userService.setUserFromJwtToken(null, null, false);
  71 + waitForUserLoaded();
  72 + userService.reloadUser();
  73 + }
  74 +
  75 + var locationSearch = $location.search();
  76 + var publicId = locationSearch.publicId;
  77 +
58 if (userService.isUserLoaded() === true) { 78 if (userService.isUserLoaded() === true) {
59 if (userService.isAuthenticated()) { 79 if (userService.isAuthenticated()) {
  80 + if (userService.isPublic()) {
  81 + if (userService.parsePublicId() !== publicId) {
  82 + evt.preventDefault();
  83 + if (publicId && publicId.length > 0) {
  84 + reloadUserFromPublicId();
  85 + } else {
  86 + userService.logout();
  87 + }
  88 + return;
  89 + }
  90 + }
60 if (userService.forceDefaultPlace(to, params)) { 91 if (userService.forceDefaultPlace(to, params)) {
61 evt.preventDefault(); 92 evt.preventDefault();
62 gotoDefaultPlace(params); 93 gotoDefaultPlace(params);
@@ -75,7 +106,10 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, @@ -75,7 +106,10 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
75 } 106 }
76 } 107 }
77 } else { 108 } else {
78 - if (to.module === 'private') { 109 + if (publicId && publicId.length > 0) {
  110 + evt.preventDefault();
  111 + reloadUserFromPublicId();
  112 + } else if (to.module === 'private') {
79 evt.preventDefault(); 113 evt.preventDefault();
80 if (to.url === '/home' || to.url === '/') { 114 if (to.url === '/home' || to.url === '/') {
81 $state.go('login', params); 115 $state.go('login', params);
@@ -86,19 +120,17 @@ export default function AppRun($rootScope, $window, $injector, $location, $log, @@ -86,19 +120,17 @@ export default function AppRun($rootScope, $window, $injector, $location, $log,
86 } 120 }
87 } else { 121 } else {
88 evt.preventDefault(); 122 evt.preventDefault();
89 - if ($rootScope.userLoadedHandle) {  
90 - $rootScope.userLoadedHandle();  
91 - }  
92 - $rootScope.userLoadedHandle = $rootScope.$on('userLoaded', function () {  
93 - $rootScope.userLoadedHandle();  
94 - $state.go(to.name, params);  
95 - }); 123 + waitForUserLoaded();
96 } 124 }
97 }) 125 })
98 126
99 $rootScope.pageTitle = 'Thingsboard'; 127 $rootScope.pageTitle = 'Thingsboard';
100 128
101 - $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to) { 129 + $rootScope.stateChangeSuccessHandle = $rootScope.$on('$stateChangeSuccess', function (evt, to, params) {
  130 + if (userService.isPublic() && to.name === 'home.dashboards.dashboard') {
  131 + $location.search('publicId', userService.getPublicId());
  132 + userService.updateLastPublicDashboardId(params.dashboardId);
  133 + }
102 if (angular.isDefined(to.data.pageTitle)) { 134 if (angular.isDefined(to.data.pageTitle)) {
103 $translate(to.data.pageTitle).then(function (translation) { 135 $translate(to.data.pageTitle).then(function (translation) {
104 $rootScope.pageTitle = 'Thingsboard | ' + translation; 136 $rootScope.pageTitle = 'Thingsboard | ' + translation;
@@ -22,7 +22,7 @@ export default angular.module('thingsboard.utils', [thingsboardTypes]) @@ -22,7 +22,7 @@ export default angular.module('thingsboard.utils', [thingsboardTypes])
22 .name; 22 .name;
23 23
24 /*@ngInject*/ 24 /*@ngInject*/
25 -function Utils($mdColorPalette, $rootScope, $window, types) { 25 +function Utils($mdColorPalette, $rootScope, $window, $q, deviceService, types) {
26 26
27 var predefinedFunctions = {}, 27 var predefinedFunctions = {},
28 predefinedFunctionsList = [], 28 predefinedFunctionsList = [],
@@ -104,7 +104,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -104,7 +104,10 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
104 parseException: parseException, 104 parseException: parseException,
105 processWidgetException: processWidgetException, 105 processWidgetException: processWidgetException,
106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty, 106 isDescriptorSchemaNotEmpty: isDescriptorSchemaNotEmpty,
107 - filterSearchTextEntities: filterSearchTextEntities 107 + filterSearchTextEntities: filterSearchTextEntities,
  108 + guid: guid,
  109 + createDatasoucesFromSubscriptionsInfo: createDatasoucesFromSubscriptionsInfo,
  110 + isLocalUrl: isLocalUrl
108 } 111 }
109 112
110 return service; 113 return service;
@@ -276,4 +279,165 @@ function Utils($mdColorPalette, $rootScope, $window, types) { @@ -276,4 +279,165 @@ function Utils($mdColorPalette, $rootScope, $window, types) {
276 deferred.resolve(response); 279 deferred.resolve(response);
277 } 280 }
278 281
  282 + function guid() {
  283 + function s4() {
  284 + return Math.floor((1 + Math.random()) * 0x10000)
  285 + .toString(16)
  286 + .substring(1);
  287 + }
  288 + return s4() + s4() + '-' + s4() + '-' + s4() + '-' +
  289 + s4() + '-' + s4() + s4() + s4();
  290 + }
  291 +
  292 + function genNextColor(datasources) {
  293 + var index = 0;
  294 + if (datasources) {
  295 + for (var i = 0; i < datasources.length; i++) {
  296 + var datasource = datasources[i];
  297 + index += datasource.dataKeys.length;
  298 + }
  299 + }
  300 + return getMaterialColor(index);
  301 + }
  302 +
  303 + /*var defaultDataKey = {
  304 + name: 'f(x)',
  305 + type: types.dataKeyType.function,
  306 + label: 'Sin',
  307 + color: getMaterialColor(0),
  308 + funcBody: getPredefinedFunctionBody('Sin'),
  309 + settings: {},
  310 + _hash: Math.random()
  311 + };
  312 +
  313 + var defaultDatasource = {
  314 + type: types.datasourceType.function,
  315 + name: types.datasourceType.function,
  316 + dataKeys: [angular.copy(defaultDataKey)]
  317 + };*/
  318 +
  319 + function createKey(keyInfo, type, datasources) {
  320 + var dataKey = {
  321 + name: keyInfo.name,
  322 + type: type,
  323 + label: keyInfo.label || keyInfo.name,
  324 + color: genNextColor(datasources),
  325 + funcBody: keyInfo.funcBody,
  326 + settings: {},
  327 + _hash: Math.random()
  328 + }
  329 + return dataKey;
  330 + }
  331 +
  332 + function createDatasourceKeys(keyInfos, type, datasource, datasources) {
  333 + for (var i=0;i<keyInfos.length;i++) {
  334 + var keyInfo = keyInfos[i];
  335 + var dataKey = createKey(keyInfo, type, datasources);
  336 + datasource.dataKeys.push(dataKey);
  337 + }
  338 + }
  339 +
  340 + function createDatasourceFromSubscription(subscriptionInfo, datasources, device) {
  341 + var datasource;
  342 + if (subscriptionInfo.type === types.datasourceType.device) {
  343 + datasource = {
  344 + type: subscriptionInfo.type,
  345 + deviceName: device.name,
  346 + name: device.name,
  347 + deviceId: device.id.id,
  348 + dataKeys: []
  349 + }
  350 + } else if (subscriptionInfo.type === types.datasourceType.function) {
  351 + datasource = {
  352 + type: subscriptionInfo.type,
  353 + name: subscriptionInfo.name || types.datasourceType.function,
  354 + dataKeys: []
  355 + }
  356 + }
  357 + datasources.push(datasource);
  358 + if (subscriptionInfo.timeseries) {
  359 + createDatasourceKeys(subscriptionInfo.timeseries, types.dataKeyType.timeseries, datasource, datasources);
  360 + }
  361 + if (subscriptionInfo.attributes) {
  362 + createDatasourceKeys(subscriptionInfo.attributes, types.dataKeyType.attribute, datasource, datasources);
  363 + }
  364 + if (subscriptionInfo.functions) {
  365 + createDatasourceKeys(subscriptionInfo.functions, types.dataKeyType.function, datasource, datasources);
  366 + }
  367 + }
  368 +
  369 + function processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred) {
  370 + if (index < subscriptionsInfo.length) {
  371 + var subscriptionInfo = subscriptionsInfo[index];
  372 + if (subscriptionInfo.type === types.datasourceType.device) {
  373 + if (subscriptionInfo.deviceId) {
  374 + deviceService.getDevice(subscriptionInfo.deviceId, true, {ignoreLoading: true}).then(
  375 + function success(device) {
  376 + createDatasourceFromSubscription(subscriptionInfo, datasources, device);
  377 + index++;
  378 + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
  379 + },
  380 + function fail() {
  381 + index++;
  382 + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
  383 + }
  384 + );
  385 + } else if (subscriptionInfo.deviceName || subscriptionInfo.deviceNamePrefix
  386 + || subscriptionInfo.deviceIds) {
  387 + var promise;
  388 + if (subscriptionInfo.deviceName) {
  389 + promise = deviceService.fetchAliasDeviceByNameFilter(subscriptionInfo.deviceName, 1, false, {ignoreLoading: true});
  390 + } else if (subscriptionInfo.deviceNamePrefix) {
  391 + promise = deviceService.fetchAliasDeviceByNameFilter(subscriptionInfo.deviceNamePrefix, 100, false, {ignoreLoading: true});
  392 + } else if (subscriptionInfo.deviceIds) {
  393 + promise = deviceService.getDevices(subscriptionInfo.deviceIds, {ignoreLoading: true});
  394 + }
  395 + promise.then(
  396 + function success(devices) {
  397 + if (devices && devices.length > 0) {
  398 + for (var i = 0; i < devices.length; i++) {
  399 + var device = devices[i];
  400 + createDatasourceFromSubscription(subscriptionInfo, datasources, device);
  401 + }
  402 + }
  403 + index++;
  404 + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
  405 + },
  406 + function fail() {
  407 + index++;
  408 + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
  409 + }
  410 + )
  411 + } else {
  412 + index++;
  413 + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
  414 + }
  415 + } else if (subscriptionInfo.type === types.datasourceType.function) {
  416 + createDatasourceFromSubscription(subscriptionInfo, datasources);
  417 + index++;
  418 + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred);
  419 + }
  420 + } else {
  421 + deferred.resolve(datasources);
  422 + }
  423 + }
  424 +
  425 + function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) {
  426 + var deferred = $q.defer();
  427 + var datasources = [];
  428 + processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred);
  429 + return deferred.promise;
  430 + }
  431 +
  432 + function isLocalUrl(url) {
  433 + var parser = document.createElement('a'); //eslint-disable-line
  434 + parser.href = url;
  435 + var host = parser.hostname;
  436 + if (host === "localhost" || host === "127.0.0.1") {
  437 + return true;
  438 + } else {
  439 + return false;
  440 + }
  441 + }
  442 +
279 } 443 }
@@ -87,8 +87,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -87,8 +87,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
87 var highlightedMode = false; 87 var highlightedMode = false;
88 var highlightedWidget = null; 88 var highlightedWidget = null;
89 var selectedWidget = null; 89 var selectedWidget = null;
90 - var mouseDownWidget = -1;  
91 - var widgetMouseMoved = false;  
92 90
93 var gridsterParent = $('#gridster-parent', $element); 91 var gridsterParent = $('#gridster-parent', $element);
94 var gridsterElement = angular.element($('#gridster-child', gridsterParent)); 92 var gridsterElement = angular.element($('#gridster-child', gridsterParent));
@@ -152,9 +150,9 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -152,9 +150,9 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
152 vm.resetHighlight = resetHighlight; 150 vm.resetHighlight = resetHighlight;
153 151
154 vm.onWidgetFullscreenChanged = onWidgetFullscreenChanged; 152 vm.onWidgetFullscreenChanged = onWidgetFullscreenChanged;
  153 +
155 vm.widgetMouseDown = widgetMouseDown; 154 vm.widgetMouseDown = widgetMouseDown;
156 - vm.widgetMouseMove = widgetMouseMove;  
157 - vm.widgetMouseUp = widgetMouseUp; 155 + vm.widgetClicked = widgetClicked;
158 156
159 vm.widgetSizeX = widgetSizeX; 157 vm.widgetSizeX = widgetSizeX;
160 vm.widgetSizeY = widgetSizeY; 158 vm.widgetSizeY = widgetSizeY;
@@ -184,23 +182,23 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -184,23 +182,23 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
184 182
185 vm.dashboardTimewindowApi = { 183 vm.dashboardTimewindowApi = {
186 onResetTimewindow: function() { 184 onResetTimewindow: function() {
187 - if (vm.originalDashboardTimewindow) {  
188 - vm.dashboardTimewindow = angular.copy(vm.originalDashboardTimewindow);  
189 - vm.originalDashboardTimewindow = null;  
190 - } 185 + $timeout(function() {
  186 + if (vm.originalDashboardTimewindow) {
  187 + vm.dashboardTimewindow = angular.copy(vm.originalDashboardTimewindow);
  188 + vm.originalDashboardTimewindow = null;
  189 + }
  190 + }, 0);
191 }, 191 },
192 onUpdateTimewindow: function(startTimeMs, endTimeMs) { 192 onUpdateTimewindow: function(startTimeMs, endTimeMs) {
193 if (!vm.originalDashboardTimewindow) { 193 if (!vm.originalDashboardTimewindow) {
194 vm.originalDashboardTimewindow = angular.copy(vm.dashboardTimewindow); 194 vm.originalDashboardTimewindow = angular.copy(vm.dashboardTimewindow);
195 } 195 }
196 - vm.dashboardTimewindow = timeService.toHistoryTimewindow(vm.dashboardTimewindow, startTimeMs, endTimeMs); 196 + $timeout(function() {
  197 + vm.dashboardTimewindow = timeService.toHistoryTimewindow(vm.dashboardTimewindow, startTimeMs, endTimeMs);
  198 + }, 0);
197 } 199 }
198 }; 200 };
199 201
200 - //$element[0].onmousemove=function(){  
201 - // widgetMouseMove();  
202 - // }  
203 -  
204 //TODO: widgets visibility 202 //TODO: widgets visibility
205 /*gridsterParent.scroll(function () { 203 /*gridsterParent.scroll(function () {
206 updateVisibleRect(); 204 updateVisibleRect();
@@ -350,7 +348,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -350,7 +348,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
350 } 348 }
351 349
352 function loadDashboard() { 350 function loadDashboard() {
353 - resetWidgetClick();  
354 $timeout(function () { 351 $timeout(function () {
355 if (vm.loadWidgets) { 352 if (vm.loadWidgets) {
356 var promise = vm.loadWidgets(); 353 var promise = vm.loadWidgets();
@@ -434,42 +431,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -434,42 +431,17 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
434 return gridsterElement && gridsterElement[0] === gridster.$element[0]; 431 return gridsterElement && gridsterElement[0] === gridster.$element[0];
435 } 432 }
436 433
437 - function resetWidgetClick () {  
438 - mouseDownWidget = -1;  
439 - widgetMouseMoved = false;  
440 - }  
441 -  
442 function onWidgetFullscreenChanged(expanded, widget) { 434 function onWidgetFullscreenChanged(expanded, widget) {
443 vm.isWidgetExpanded = expanded; 435 vm.isWidgetExpanded = expanded;
444 $scope.$broadcast('onWidgetFullscreenChanged', vm.isWidgetExpanded, widget); 436 $scope.$broadcast('onWidgetFullscreenChanged', vm.isWidgetExpanded, widget);
445 } 437 }
446 438
447 function widgetMouseDown ($event, widget) { 439 function widgetMouseDown ($event, widget) {
448 - mouseDownWidget = widget;  
449 - widgetMouseMoved = false;  
450 if (vm.onWidgetMouseDown) { 440 if (vm.onWidgetMouseDown) {
451 vm.onWidgetMouseDown({event: $event, widget: widget}); 441 vm.onWidgetMouseDown({event: $event, widget: widget});
452 } 442 }
453 } 443 }
454 444
455 - function widgetMouseMove () {  
456 - if (mouseDownWidget) {  
457 - widgetMouseMoved = true;  
458 - }  
459 - }  
460 -  
461 - function widgetMouseUp ($event, widget) {  
462 - $timeout(function () {  
463 - if (!widgetMouseMoved && mouseDownWidget) {  
464 - if (widget === mouseDownWidget) {  
465 - widgetClicked($event, widget);  
466 - }  
467 - }  
468 - mouseDownWidget = null;  
469 - widgetMouseMoved = false;  
470 - }, 0);  
471 - }  
472 -  
473 function widgetClicked ($event, widget) { 445 function widgetClicked ($event, widget) {
474 if ($event) { 446 if ($event) {
475 $event.stopPropagation(); 447 $event.stopPropagation();
@@ -518,7 +490,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -518,7 +490,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
518 } 490 }
519 491
520 function editWidget ($event, widget) { 492 function editWidget ($event, widget) {
521 - resetWidgetClick();  
522 if ($event) { 493 if ($event) {
523 $event.stopPropagation(); 494 $event.stopPropagation();
524 } 495 }
@@ -528,7 +499,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -528,7 +499,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
528 } 499 }
529 500
530 function exportWidget ($event, widget) { 501 function exportWidget ($event, widget) {
531 - resetWidgetClick();  
532 if ($event) { 502 if ($event) {
533 $event.stopPropagation(); 503 $event.stopPropagation();
534 } 504 }
@@ -538,7 +508,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -538,7 +508,6 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
538 } 508 }
539 509
540 function removeWidget($event, widget) { 510 function removeWidget($event, widget) {
541 - resetWidgetClick();  
542 if ($event) { 511 if ($event) {
543 $event.stopPropagation(); 512 $event.stopPropagation();
544 } 513 }
@@ -548,27 +517,21 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t @@ -548,27 +517,21 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t
548 } 517 }
549 518
550 function highlightWidget(widget, delay) { 519 function highlightWidget(widget, delay) {
551 - highlightedMode = true;  
552 - highlightedWidget = widget;  
553 - if (vm.gridster) {  
554 - var item = $('.gridster-item', vm.gridster.$element)[vm.widgets.indexOf(widget)];  
555 - if (item) {  
556 - var height = $(item).outerHeight(true);  
557 - var rectHeight = gridsterParent.height();  
558 - var offset = (rectHeight - height) / 2;  
559 - var scrollTop = item.offsetTop;  
560 - if (offset > 0) {  
561 - scrollTop -= offset;  
562 - }  
563 - gridsterParent.animate({  
564 - scrollTop: scrollTop  
565 - }, delay);  
566 - } 520 + if (!highlightedMode || highlightedWidget != widget) {
  521 + highlightedMode = true;
  522 + highlightedWidget = widget;
  523 + scrollToWidget(widget, delay);
567 } 524 }
568 } 525 }
569 526
570 function selectWidget(widget, delay) { 527 function selectWidget(widget, delay) {
571 - selectedWidget = widget; 528 + if (selectedWidget != widget) {
  529 + selectedWidget = widget;
  530 + scrollToWidget(widget, delay);
  531 + }
  532 + }
  533 +
  534 + function scrollToWidget(widget, delay) {
572 if (vm.gridster) { 535 if (vm.gridster) {
573 var item = $('.gridster-item', vm.gridster.$element)[vm.widgets.indexOf(widget)]; 536 var item = $('.gridster-item', vm.gridster.$element)[vm.widgets.indexOf(widget)];
574 if (item) { 537 if (item) {
@@ -37,9 +37,7 @@ @@ -37,9 +37,7 @@
37 'tb-not-highlighted': vm.isNotHighlighted(widget), 37 'tb-not-highlighted': vm.isNotHighlighted(widget),
38 'md-whiteframe-4dp': vm.dropWidgetShadow(widget)}" 38 'md-whiteframe-4dp': vm.dropWidgetShadow(widget)}"
39 tb-mousedown="vm.widgetMouseDown($event, widget)" 39 tb-mousedown="vm.widgetMouseDown($event, widget)"
40 - tb-mousemove="vm.widgetMouseMove($event, widget)"  
41 - tb-mouseup="vm.widgetMouseUp($event, widget)"  
42 - ng-click="" 40 + ng-click="vm.widgetClicked($event, widget)"
43 tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)" 41 tb-contextmenu="vm.openWidgetContextMenu($event, widget, $mdOpenMousepointMenu)"
44 ng-style="{cursor: 'pointer', 42 ng-style="{cursor: 'pointer',
45 color: vm.widgetColor(widget), 43 color: vm.widgetColor(widget),
@@ -49,7 +47,7 @@ @@ -49,7 +47,7 @@
49 <span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span> 47 <span ng-show="vm.showWidgetTitle(widget)" ng-style="vm.widgetTitleStyle(widget)" class="md-subhead">{{widget.config.title}}</span>
50 <tb-timewindow aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow> 48 <tb-timewindow aggregation ng-if="vm.hasTimewindow(widget)" ng-model="widget.config.timewindow"></tb-timewindow>
51 </div> 49 </div>
52 - <div class="tb-widget-actions" layout="row" layout-align="start center"> 50 + <div class="tb-widget-actions" layout="row" layout-align="start center" tb-mousedown="$event.stopPropagation()">
53 <md-button id="expand-button" 51 <md-button id="expand-button"
54 ng-show="!vm.isEdit && vm.enableWidgetFullscreen(widget)" 52 ng-show="!vm.isEdit && vm.enableWidgetFullscreen(widget)"
55 aria-label="{{ 'fullscreen.fullscreen' | translate }}" 53 aria-label="{{ 'fullscreen.fullscreen' | translate }}"
@@ -41,7 +41,7 @@ function DeviceFilter($compile, $templateCache, $q, deviceService) { @@ -41,7 +41,7 @@ function DeviceFilter($compile, $templateCache, $q, deviceService) {
41 41
42 var deferred = $q.defer(); 42 var deferred = $q.defer();
43 43
44 - deviceService.getTenantDevices(pageLink).then(function success(result) { 44 + deviceService.getTenantDevices(pageLink, false).then(function success(result) {
45 deferred.resolve(result.data); 45 deferred.resolve(result.data);
46 }, function fail() { 46 }, function fail() {
47 deferred.reject(); 47 deferred.reject();
@@ -26,6 +26,7 @@ import gridTemplate from './grid.tpl.html'; @@ -26,6 +26,7 @@ import gridTemplate from './grid.tpl.html';
26 26
27 export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav]) 27 export default angular.module('thingsboard.directives.grid', [thingsboardScopeElement, thingsboardDetailsSidenav])
28 .directive('tbGrid', Grid) 28 .directive('tbGrid', Grid)
  29 + .controller('ItemCardController', ItemCardController)
29 .directive('tbGridCardContent', GridCardContent) 30 .directive('tbGridCardContent', GridCardContent)
30 .filter('range', RangeFilter) 31 .filter('range', RangeFilter)
31 .name; 32 .name;
@@ -44,14 +45,52 @@ function RangeFilter() { @@ -44,14 +45,52 @@ function RangeFilter() {
44 } 45 }
45 46
46 /*@ngInject*/ 47 /*@ngInject*/
47 -function GridCardContent($compile) { 48 +function ItemCardController() {
  49 +
  50 + var vm = this; //eslint-disable-line
  51 +
  52 +}
  53 +
  54 +/*@ngInject*/
  55 +function GridCardContent($compile, $controller) {
48 var linker = function(scope, element) { 56 var linker = function(scope, element) {
  57 +
  58 + var controllerInstance = null;
  59 +
49 scope.$watch('itemTemplate', 60 scope.$watch('itemTemplate',
50 - function(value) {  
51 - element.html(value);  
52 - $compile(element.contents())(scope); 61 + function() {
  62 + initContent();
  63 + }
  64 + );
  65 + scope.$watch('itemController',
  66 + function() {
  67 + initContent();
53 } 68 }
54 ); 69 );
  70 + scope.$watch('parentCtl',
  71 + function() {
  72 + controllerInstance.parentCtl = scope.parentCtl;
  73 + }
  74 + );
  75 + scope.$watch('item',
  76 + function() {
  77 + controllerInstance.item = scope.item;
  78 + }
  79 + );
  80 +
  81 + function initContent() {
  82 + if (scope.itemTemplate && scope.itemController && !controllerInstance) {
  83 + element.html(scope.itemTemplate);
  84 + var locals = {};
  85 + angular.extend(locals, {$scope: scope, $element: element});
  86 + var controller = $controller(scope.itemController, locals, true, 'vm');
  87 + controller.instance = controller();
  88 + controllerInstance = controller.instance;
  89 + controllerInstance.item = scope.item;
  90 + controllerInstance.parentCtl = scope.parentCtl;
  91 + $compile(element.contents())(scope);
  92 + }
  93 + }
55 }; 94 };
56 95
57 return { 96 return {
@@ -61,6 +100,7 @@ function GridCardContent($compile) { @@ -61,6 +100,7 @@ function GridCardContent($compile) {
61 parentCtl: "=parentCtl", 100 parentCtl: "=parentCtl",
62 gridCtl: "=gridCtl", 101 gridCtl: "=gridCtl",
63 itemTemplate: "=itemTemplate", 102 itemTemplate: "=itemTemplate",
  103 + itemController: "=itemController",
64 item: "=item" 104 item: "=item"
65 } 105 }
66 }; 106 };
@@ -171,26 +211,31 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -171,26 +211,31 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
171 vm.items.pending = true; 211 vm.items.pending = true;
172 promise.then( 212 promise.then(
173 function success(items) { 213 function success(items) {
174 - vm.items.data = vm.items.data.concat(items.data);  
175 - var startIndex = vm.items.data.length - items.data.length;  
176 - var endIndex = vm.items.data.length;  
177 - for (var i = startIndex; i < endIndex; i++) {  
178 - var item = vm.items.data[i];  
179 - item.index = i;  
180 - var row = Math.floor(i / vm.columns);  
181 - var itemRow = vm.items.rowData[row];  
182 - if (!itemRow) {  
183 - itemRow = [];  
184 - vm.items.rowData.push(itemRow); 214 + if (vm.items.reloadPending) {
  215 + vm.items.pending = false;
  216 + reload();
  217 + } else {
  218 + vm.items.data = vm.items.data.concat(items.data);
  219 + var startIndex = vm.items.data.length - items.data.length;
  220 + var endIndex = vm.items.data.length;
  221 + for (var i = startIndex; i < endIndex; i++) {
  222 + var item = vm.items.data[i];
  223 + item.index = i;
  224 + var row = Math.floor(i / vm.columns);
  225 + var itemRow = vm.items.rowData[row];
  226 + if (!itemRow) {
  227 + itemRow = [];
  228 + vm.items.rowData.push(itemRow);
  229 + }
  230 + itemRow.push(item);
185 } 231 }
186 - itemRow.push(item);  
187 - }  
188 - vm.items.nextPageLink = items.nextPageLink;  
189 - vm.items.hasNext = items.hasNext;  
190 - if (vm.items.hasNext) {  
191 - vm.items.nextPageLink.limit = pageSize; 232 + vm.items.nextPageLink = items.nextPageLink;
  233 + vm.items.hasNext = items.hasNext;
  234 + if (vm.items.hasNext) {
  235 + vm.items.nextPageLink.limit = pageSize;
  236 + }
  237 + vm.items.pending = false;
192 } 238 }
193 - vm.items.pending = false;  
194 }, 239 },
195 function fail() { 240 function fail() {
196 vm.items.hasNext = false; 241 vm.items.hasNext = false;
@@ -291,6 +336,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -291,6 +336,11 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
291 } else if (vm.config.itemCardTemplateUrl) { 336 } else if (vm.config.itemCardTemplateUrl) {
292 vm.itemCardTemplate = $templateCache.get(vm.config.itemCardTemplateUrl); 337 vm.itemCardTemplate = $templateCache.get(vm.config.itemCardTemplateUrl);
293 } 338 }
  339 + if (vm.config.itemCardController) {
  340 + vm.itemCardController = vm.config.itemCardController;
  341 + } else {
  342 + vm.itemCardController = 'ItemCardController';
  343 + }
294 344
295 vm.parentCtl = vm.config.parentCtl || vm; 345 vm.parentCtl = vm.config.parentCtl || vm;
296 346
@@ -380,26 +430,35 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra @@ -380,26 +430,35 @@ function GridController($scope, $state, $mdDialog, $document, $q, $timeout, $tra
380 } 430 }
381 431
382 $scope.$on('searchTextUpdated', function () { 432 $scope.$on('searchTextUpdated', function () {
383 - vm.items = {  
384 - data: [],  
385 - rowData: [],  
386 - nextPageLink: {  
387 - limit: pageSize,  
388 - textSearch: $scope.searchConfig.searchText  
389 - },  
390 - selections: {},  
391 - selectedCount: 0,  
392 - hasNext: true,  
393 - pending: false  
394 - };  
395 - vm.detailsConfig.isDetailsOpen = false;  
396 - vm.itemRows.getItemAtIndex(pageSize); 433 + reload();
397 }); 434 });
398 435
399 vm.onGridInited(vm); 436 vm.onGridInited(vm);
400 437
401 vm.itemRows.getItemAtIndex(pageSize); 438 vm.itemRows.getItemAtIndex(pageSize);
402 439
  440 + function reload() {
  441 + if (vm.items && vm.items.pending) {
  442 + vm.items.reloadPending = true;
  443 + } else {
  444 + vm.items = {
  445 + data: [],
  446 + rowData: [],
  447 + nextPageLink: {
  448 + limit: pageSize,
  449 + textSearch: $scope.searchConfig.searchText
  450 + },
  451 + selections: {},
  452 + selectedCount: 0,
  453 + hasNext: true,
  454 + pending: false
  455 + };
  456 + vm.detailsConfig.isDetailsOpen = false;
  457 + vm.items.reloadPending = false;
  458 + vm.itemRows.getItemAtIndex(pageSize);
  459 + }
  460 + }
  461 +
403 function refreshList() { 462 function refreshList() {
404 $state.go($state.current, vm.refreshParamsFunc(), {reload: true}); 463 $state.go($state.current, vm.refreshParamsFunc(), {reload: true});
405 } 464 }
@@ -43,13 +43,13 @@ @@ -43,13 +43,13 @@
43 </md-card-title> 43 </md-card-title>
44 </section> 44 </section>
45 <md-card-content flex> 45 <md-card-content flex>
46 - <tb-grid-card-content grid-ctl="vm" parent-ctl="vm.parentCtl" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content> 46 + <tb-grid-card-content grid-ctl="vm" parent-ctl="vm.parentCtl" item-controller="vm.itemCardController" item-template="vm.itemCardTemplate" item="rowItem[n]"></tb-grid-card-content>
47 </md-card-content> 47 </md-card-content>
48 <md-card-actions layout="row" layout-align="end end"> 48 <md-card-actions layout="row" layout-align="end end">
49 <md-button ng-if="action.isEnabled(rowItem[n])" ng-disabled="loading" class="md-icon-button md-primary" ng-repeat="action in vm.actionsList" 49 <md-button ng-if="action.isEnabled(rowItem[n])" ng-disabled="loading" class="md-icon-button md-primary" ng-repeat="action in vm.actionsList"
50 ng-click="action.onAction($event, rowItem[n])" aria-label="{{ action.name() }}"> 50 ng-click="action.onAction($event, rowItem[n])" aria-label="{{ action.name() }}">
51 <md-tooltip md-direction="top"> 51 <md-tooltip md-direction="top">
52 - {{ action.details() }} 52 + {{ action.details( rowItem[n] ) }}
53 </md-tooltip> 53 </md-tooltip>
54 <ng-md-icon icon="{{action.icon}}"></ng-md-icon> 54 <ng-md-icon icon="{{action.icon}}"></ng-md-icon>
55 </md-button> 55 </md-button>
@@ -62,7 +62,7 @@ @@ -62,7 +62,7 @@
62 </div> 62 </div>
63 <tb-details-sidenav 63 <tb-details-sidenav
64 header-title="{{vm.getItemTitleFunc(vm.operatingItem())}}" 64 header-title="{{vm.getItemTitleFunc(vm.operatingItem())}}"
65 - header-subtitle="{{vm.itemDetailsText()}}" 65 + header-subtitle="{{vm.itemDetailsText(vm.operatingItem())}}"
66 is-read-only="vm.isDetailsReadOnly(vm.operatingItem())" 66 is-read-only="vm.isDetailsReadOnly(vm.operatingItem())"
67 is-open="vm.detailsConfig.isDetailsOpen" 67 is-open="vm.detailsConfig.isDetailsOpen"
68 is-edit="vm.detailsConfig.isDetailsEditMode" 68 is-edit="vm.detailsConfig.isDetailsEditMode"
@@ -44,15 +44,8 @@ function Legend($compile, $templateCache, types) { @@ -44,15 +44,8 @@ function Legend($compile, $templateCache, types) {
44 scope.isHorizontal = scope.legendConfig.position === types.position.bottom.value || 44 scope.isHorizontal = scope.legendConfig.position === types.position.bottom.value ||
45 scope.legendConfig.position === types.position.top.value; 45 scope.legendConfig.position === types.position.top.value;
46 46
47 - scope.$on('legendDataUpdated', function (event, apply) {  
48 - if (apply) {  
49 - scope.$digest();  
50 - }  
51 - });  
52 -  
53 scope.toggleHideData = function(index) { 47 scope.toggleHideData = function(index) {
54 scope.legendData.data[index].hidden = !scope.legendData.data[index].hidden; 48 scope.legendData.data[index].hidden = !scope.legendData.data[index].hidden;
55 - scope.$emit('legendDataHiddenChanged', index);  
56 } 49 }
57 50
58 $compile(element.contents())(scope); 51 $compile(element.contents())(scope);
  1 +/*
  2 + * Copyright © 2016-2017 The Thingsboard Authors
  3 + *
  4 + * Licensed under the Apache License, Version 2.0 (the "License");
  5 + * you may not use this file except in compliance with the License.
  6 + * You may obtain a copy of the License at
  7 + *
  8 + * http://www.apache.org/licenses/LICENSE-2.0
  9 + *
  10 + * Unless required by applicable law or agreed to in writing, software
  11 + * distributed under the License is distributed on an "AS IS" BASIS,
  12 + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13 + * See the License for the specific language governing permissions and
  14 + * limitations under the License.
  15 + */
  16 +
  17 +/* eslint-disable import/no-unresolved, import/default */
  18 +
  19 +import socialsharePanelTemplate from './socialshare-panel.tpl.html';
  20 +
  21 +/* eslint-enable import/no-unresolved, import/default */
  22 +
  23 +
  24 +export default angular.module('thingsboard.directives.socialsharePanel', [])
  25 + .directive('tbSocialSharePanel', SocialsharePanel)
  26 + .name;
  27 +
  28 +/*@ngInject*/
  29 +function SocialsharePanel() {
  30 + return {
  31 + restrict: "E",
  32 + scope: true,
  33 + bindToController: {
  34 + shareTitle: '@',
  35 + shareText: '@',
  36 + shareLink: '@',
  37 + shareHashTags: '@'
  38 + },
  39 + controller: SocialsharePanelController,
  40 + controllerAs: 'vm',
  41 + templateUrl: socialsharePanelTemplate
  42 + };
  43 +}
  44 +
  45 +/*@ngInject*/
  46 +function SocialsharePanelController(utils) {
  47 +
  48 + let vm = this;
  49 +
  50 + vm.isShareLinkLocal = function() {
  51 + if (vm.shareLink && vm.shareLink.length > 0) {
  52 + return utils.isLocalUrl(vm.shareLink);
  53 + } else {
  54 + return true;
  55 + }
  56 + }
  57 +
  58 +}
  1 +<!--
  2 +
  3 + Copyright © 2016-2017 The Thingsboard Authors
  4 +
  5 + Licensed under the Apache License, Version 2.0 (the "License");
  6 + you may not use this file except in compliance with the License.
  7 + You may obtain a copy of the License at
  8 +
  9 + http://www.apache.org/licenses/LICENSE-2.0
  10 +
  11 + Unless required by applicable law or agreed to in writing, software
  12 + distributed under the License is distributed on an "AS IS" BASIS,
  13 + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14 + See the License for the specific language governing permissions and
  15 + limitations under the License.
  16 +
  17 +-->
  18 +
  19 +<div layout="row" ng-show="!vm.isShareLinkLocal()">
  20 + <md-button class="md-icon-button md-raised md-primary"
  21 + socialshare
  22 + socialshare-provider="facebook"
  23 + socialshare-title="{{ vm.shareTitle }}"
  24 + socialshare-text="{{ vm.shareText }}"
  25 + socialshare-url="{{ vm.shareLink }}">
  26 + <ng-md-icon icon="facebook" aria-label="Facebook"></ng-md-icon>
  27 + <md-tooltip md-direction="top">
  28 + {{ 'action.share-via' | translate:{provider:'Facebook'} }}
  29 + </md-tooltip>
  30 + </md-button>
  31 + <md-button class="md-icon-button md-raised md-primary"
  32 + socialshare
  33 + socialshare-provider="twitter"
  34 + socialshare-text="{{ vm.shareTitle }}"
  35 + socialshare-hashtags="{{ vm.shareHashTags }}"
  36 + socialshare-url="{{ vm.shareLink }}">
  37 + <ng-md-icon icon="twitter" aria-label="Twitter"></ng-md-icon>
  38 + <md-tooltip md-direction="top">
  39 + {{ 'action.share-via' | translate:{provider:'Twitter'} }}
  40 + </md-tooltip>
  41 + </md-button>
  42 + <md-button class="md-icon-button md-raised md-primary"
  43 + socialshare
  44 + socialshare-provider="linkedin"
  45 + socialshare-text="{{ vm.shareTitle }}"
  46 + socialshare-url="{{ vm.shareLink }}">
  47 + <ng-md-icon icon="linkedin" aria-label="Linkedin"></ng-md-icon>
  48 + <md-tooltip md-direction="top">
  49 + {{ 'action.share-via' | translate:{provider:'Linkedin'} }}
  50 + </md-tooltip>
  51 + </md-button>
  52 + <md-button class="md-icon-button md-raised md-primary"
  53 + socialshare
  54 + socialshare-provider="reddit"
  55 + socialshare-text="{{ vm.shareTitle }}"
  56 + socialshare-url="{{ vm.shareLink }}">
  57 + <md-icon md-svg-icon="mdi:reddit" aria-label="Reddit"></md-icon>
  58 + <md-tooltip md-direction="top">
  59 + {{ 'action.share-via' | translate:{provider:'Reddit'} }}
  60 + </md-tooltip>
  61 + </md-button>
  62 +</div>
@@ -76,6 +76,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -76,6 +76,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
76 scope.forceExpandDatasources = false; 76 scope.forceExpandDatasources = false;
77 } 77 }
78 78
  79 + if (angular.isUndefined(scope.isDataEnabled)) {
  80 + scope.isDataEnabled = true;
  81 + }
  82 +
79 scope.currentSettingsSchema = {}; 83 scope.currentSettingsSchema = {};
80 scope.currentSettings = angular.copy(scope.emptySettingsSchema); 84 scope.currentSettings = angular.copy(scope.emptySettingsSchema);
81 85
@@ -108,7 +112,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -108,7 +112,8 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
108 scope.showLegend = angular.isDefined(ngModelCtrl.$viewValue.showLegend) ? 112 scope.showLegend = angular.isDefined(ngModelCtrl.$viewValue.showLegend) ?
109 ngModelCtrl.$viewValue.showLegend : scope.widgetType === types.widgetType.timeseries.value; 113 ngModelCtrl.$viewValue.showLegend : scope.widgetType === types.widgetType.timeseries.value;
110 scope.legendConfig = ngModelCtrl.$viewValue.legendConfig; 114 scope.legendConfig = ngModelCtrl.$viewValue.legendConfig;
111 - if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value) { 115 + if (scope.widgetType !== types.widgetType.rpc.value && scope.widgetType !== types.widgetType.static.value
  116 + && scope.isDataEnabled) {
112 if (scope.datasources) { 117 if (scope.datasources) {
113 scope.datasources.splice(0, scope.datasources.length); 118 scope.datasources.splice(0, scope.datasources.length);
114 } else { 119 } else {
@@ -119,7 +124,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -119,7 +124,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
119 scope.datasources.push({value: ngModelCtrl.$viewValue.datasources[i]}); 124 scope.datasources.push({value: ngModelCtrl.$viewValue.datasources[i]});
120 } 125 }
121 } 126 }
122 - } else if (scope.widgetType === types.widgetType.rpc.value) { 127 + } else if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
123 if (ngModelCtrl.$viewValue.targetDeviceAliasIds && ngModelCtrl.$viewValue.targetDeviceAliasIds.length > 0) { 128 if (ngModelCtrl.$viewValue.targetDeviceAliasIds && ngModelCtrl.$viewValue.targetDeviceAliasIds.length > 0) {
124 var aliasId = ngModelCtrl.$viewValue.targetDeviceAliasIds[0]; 129 var aliasId = ngModelCtrl.$viewValue.targetDeviceAliasIds[0];
125 if (scope.deviceAliases[aliasId]) { 130 if (scope.deviceAliases[aliasId]) {
@@ -159,10 +164,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -159,10 +164,10 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
159 if (ngModelCtrl.$viewValue) { 164 if (ngModelCtrl.$viewValue) {
160 var value = ngModelCtrl.$viewValue; 165 var value = ngModelCtrl.$viewValue;
161 var valid; 166 var valid;
162 - if (scope.widgetType === types.widgetType.rpc.value) { 167 + if (scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
163 valid = value && value.targetDeviceAliasIds && value.targetDeviceAliasIds.length > 0; 168 valid = value && value.targetDeviceAliasIds && value.targetDeviceAliasIds.length > 0;
164 ngModelCtrl.$setValidity('targetDeviceAliasIds', valid); 169 ngModelCtrl.$setValidity('targetDeviceAliasIds', valid);
165 - } else if (scope.widgetType !== types.widgetType.static.value) { 170 + } else if (scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) {
166 valid = value && value.datasources && value.datasources.length > 0; 171 valid = value && value.datasources && value.datasources.length > 0;
167 ngModelCtrl.$setValidity('datasources', valid); 172 ngModelCtrl.$setValidity('datasources', valid);
168 } 173 }
@@ -228,7 +233,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -228,7 +233,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
228 233
229 scope.$watch('datasources', function () { 234 scope.$watch('datasources', function () {
230 if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value 235 if (ngModelCtrl.$viewValue && scope.widgetType !== types.widgetType.rpc.value
231 - && scope.widgetType !== types.widgetType.static.value) { 236 + && scope.widgetType !== types.widgetType.static.value && scope.isDataEnabled) {
232 var value = ngModelCtrl.$viewValue; 237 var value = ngModelCtrl.$viewValue;
233 if (value.datasources) { 238 if (value.datasources) {
234 value.datasources.splice(0, value.datasources.length); 239 value.datasources.splice(0, value.datasources.length);
@@ -246,7 +251,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -246,7 +251,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
246 }, true); 251 }, true);
247 252
248 scope.$watch('targetDeviceAlias.value', function () { 253 scope.$watch('targetDeviceAlias.value', function () {
249 - if (ngModelCtrl.$viewValue && scope.widgetType === types.widgetType.rpc.value) { 254 + if (ngModelCtrl.$viewValue && scope.widgetType === types.widgetType.rpc.value && scope.isDataEnabled) {
250 var value = ngModelCtrl.$viewValue; 255 var value = ngModelCtrl.$viewValue;
251 if (scope.targetDeviceAlias.value) { 256 if (scope.targetDeviceAlias.value) {
252 value.targetDeviceAliasIds = [scope.targetDeviceAlias.value.id]; 257 value.targetDeviceAliasIds = [scope.targetDeviceAlias.value.id];
@@ -359,6 +364,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti @@ -359,6 +364,7 @@ function WidgetConfig($compile, $templateCache, $rootScope, $timeout, types, uti
359 require: "^ngModel", 364 require: "^ngModel",
360 scope: { 365 scope: {
361 forceExpandDatasources: '=?', 366 forceExpandDatasources: '=?',
  367 + isDataEnabled: '=?',
362 widgetType: '=', 368 widgetType: '=',
363 widgetSettingsSchema: '=', 369 widgetSettingsSchema: '=',
364 datakeySettingsSchema: '=', 370 datakeySettingsSchema: '=',
@@ -31,7 +31,7 @@ @@ -31,7 +31,7 @@
31 </section> 31 </section>
32 </div> 32 </div>
33 <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default" 33 <v-accordion id="datasources-accordion" control="datasourcesAccordion" class="vAccordion--default"
34 - ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value"> 34 + ng-show="widgetType !== types.widgetType.rpc.value && widgetType !== types.widgetType.static.value && isDataEnabled">
35 <v-pane id="datasources-pane" expanded="true"> 35 <v-pane id="datasources-pane" expanded="true">
36 <v-pane-header> 36 <v-pane-header>
37 {{ 'widget-config.datasources' | translate }} 37 {{ 'widget-config.datasources' | translate }}
@@ -96,7 +96,7 @@ @@ -96,7 +96,7 @@
96 </v-pane> 96 </v-pane>
97 </v-accordion> 97 </v-accordion>
98 <v-accordion id="target-devices-accordion" control="targetDevicesAccordion" class="vAccordion--default" 98 <v-accordion id="target-devices-accordion" control="targetDevicesAccordion" class="vAccordion--default"
99 - ng-show="widgetType === types.widgetType.rpc.value"> 99 + ng-show="widgetType === types.widgetType.rpc.value && isDataEnabled">
100 <v-pane id="target-devices-pane" expanded="true"> 100 <v-pane id="target-devices-pane" expanded="true">
101 <v-pane-header> 101 <v-pane-header>
102 {{ 'widget-config.target-device' | translate }} 102 {{ 'widget-config.target-device' | translate }}