Showing
60 changed files
with
2462 additions
and
844 deletions
Too many changes to show.
To preserve performance only 60 of 69 files are displayed.
@@ -39,6 +39,7 @@ import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvi | @@ -39,6 +39,7 @@ import org.thingsboard.server.service.security.auth.rest.RestAuthenticationProvi | ||
39 | import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter; | 39 | import org.thingsboard.server.service.security.auth.rest.RestLoginProcessingFilter; |
40 | import org.thingsboard.server.service.security.auth.jwt.*; | 40 | import org.thingsboard.server.service.security.auth.jwt.*; |
41 | import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor; | 41 | import org.thingsboard.server.service.security.auth.jwt.extractor.TokenExtractor; |
42 | +import org.thingsboard.server.service.security.auth.rest.RestPublicLoginProcessingFilter; | ||
42 | 43 | ||
43 | import java.util.ArrayList; | 44 | import java.util.ArrayList; |
44 | import java.util.Arrays; | 45 | import java.util.Arrays; |
@@ -56,6 +57,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | @@ -56,6 +57,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | ||
56 | public static final String WEBJARS_ENTRY_POINT = "/webjars/**"; | 57 | public static final String WEBJARS_ENTRY_POINT = "/webjars/**"; |
57 | public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**"; | 58 | public static final String DEVICE_API_ENTRY_POINT = "/api/v1/**"; |
58 | public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; | 59 | public static final String FORM_BASED_LOGIN_ENTRY_POINT = "/api/auth/login"; |
60 | + public static final String PUBLIC_LOGIN_ENTRY_POINT = "/api/auth/login/public"; | ||
59 | public static final String TOKEN_REFRESH_ENTRY_POINT = "/api/auth/token"; | 61 | 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/**"}; | 62 | 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/**"; | 63 | public static final String TOKEN_BASED_AUTH_ENTRY_POINT = "/api/**"; |
@@ -88,9 +90,17 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | @@ -88,9 +90,17 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | ||
88 | } | 90 | } |
89 | 91 | ||
90 | @Bean | 92 | @Bean |
93 | + protected RestPublicLoginProcessingFilter buildRestPublicLoginProcessingFilter() throws Exception { | ||
94 | + RestPublicLoginProcessingFilter filter = new RestPublicLoginProcessingFilter(PUBLIC_LOGIN_ENTRY_POINT, successHandler, failureHandler, objectMapper); | ||
95 | + filter.setAuthenticationManager(this.authenticationManager); | ||
96 | + return filter; | ||
97 | + } | ||
98 | + | ||
99 | + @Bean | ||
91 | protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { | 100 | protected JwtTokenAuthenticationProcessingFilter buildJwtTokenAuthenticationProcessingFilter() throws Exception { |
92 | List<String> pathsToSkip = new ArrayList(Arrays.asList(NON_TOKEN_BASED_AUTH_ENTRY_POINTS)); | 101 | 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)); | 102 | + pathsToSkip.addAll(Arrays.asList(WS_TOKEN_BASED_AUTH_ENTRY_POINT, TOKEN_REFRESH_ENTRY_POINT, FORM_BASED_LOGIN_ENTRY_POINT, |
103 | + PUBLIC_LOGIN_ENTRY_POINT, DEVICE_API_ENTRY_POINT, WEBJARS_ENTRY_POINT)); | ||
94 | SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); | 104 | SkipPathRequestMatcher matcher = new SkipPathRequestMatcher(pathsToSkip, TOKEN_BASED_AUTH_ENTRY_POINT); |
95 | JwtTokenAuthenticationProcessingFilter filter | 105 | JwtTokenAuthenticationProcessingFilter filter |
96 | = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher); | 106 | = new JwtTokenAuthenticationProcessingFilter(failureHandler, jwtHeaderTokenExtractor, matcher); |
@@ -146,6 +156,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | @@ -146,6 +156,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | ||
146 | .antMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars | 156 | .antMatchers(WEBJARS_ENTRY_POINT).permitAll() // Webjars |
147 | .antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API | 157 | .antMatchers(DEVICE_API_ENTRY_POINT).permitAll() // Device HTTP Transport API |
148 | .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point | 158 | .antMatchers(FORM_BASED_LOGIN_ENTRY_POINT).permitAll() // Login end-point |
159 | + .antMatchers(PUBLIC_LOGIN_ENTRY_POINT).permitAll() // Public login end-point | ||
149 | .antMatchers(TOKEN_REFRESH_ENTRY_POINT).permitAll() // Token refresh end-point | 160 | .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 | 161 | .antMatchers(NON_TOKEN_BASED_AUTH_ENTRY_POINTS).permitAll() // static resources, user activation and password reset end-points |
151 | .and() | 162 | .and() |
@@ -156,6 +167,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | @@ -156,6 +167,7 @@ public class ThingsboardSecurityConfiguration extends WebSecurityConfigurerAdapt | ||
156 | .exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) | 167 | .exceptionHandling().accessDeniedHandler(restAccessDeniedHandler) |
157 | .and() | 168 | .and() |
158 | .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) | 169 | .addFilterBefore(buildRestLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) |
170 | + .addFilterBefore(buildRestPublicLoginProcessingFilter(), UsernamePasswordAuthenticationFilter.class) | ||
159 | .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) | 171 | .addFilterBefore(buildJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class) |
160 | .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) | 172 | .addFilterBefore(buildRefreshTokenProcessingFilter(), UsernamePasswordAuthenticationFilter.class) |
161 | .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class); | 173 | .addFilterBefore(buildWsJwtTokenAuthenticationProcessingFilter(), UsernamePasswordAuthenticationFilter.class); |
@@ -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.*; |
@@ -43,14 +46,22 @@ public class CustomerController extends BaseController { | @@ -43,14 +46,22 @@ public class CustomerController extends BaseController { | ||
43 | } | 46 | } |
44 | 47 | ||
45 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") | 48 | @PreAuthorize("hasAnyAuthority('TENANT_ADMIN', 'CUSTOMER_USER')") |
46 | - @RequestMapping(value = "/customer/{customerId}/title", method = RequestMethod.GET, produces = "application/text") | 49 | + @RequestMapping(value = "/customer/{customerId}/shortInfo", method = RequestMethod.GET) |
47 | @ResponseBody | 50 | @ResponseBody |
48 | - public String getCustomerTitleById(@PathVariable("customerId") String strCustomerId) throws ThingsboardException { | 51 | + public JsonNode getShortCustomerInfoById(@PathVariable("customerId") String strCustomerId) throws ThingsboardException { |
49 | checkParameter("customerId", strCustomerId); | 52 | checkParameter("customerId", strCustomerId); |
50 | try { | 53 | try { |
51 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); | 54 | CustomerId customerId = new CustomerId(toUUID(strCustomerId)); |
52 | Customer customer = checkCustomerId(customerId); | 55 | Customer customer = checkCustomerId(customerId); |
53 | - return customer.getTitle(); | 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; | ||
54 | } catch (Exception e) { | 65 | } catch (Exception e) { |
55 | throw handleException(e); | 66 | throw handleException(e); |
56 | } | 67 | } |
@@ -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 |
application/src/main/java/org/thingsboard/server/service/security/auth/rest/PublicLoginRequest.java
0 → 100644
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 | } |
application/src/main/java/org/thingsboard/server/service/security/model/UserPrincipal.java
0 → 100644
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 |
@@ -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" ) |
@@ -18,12 +18,14 @@ export default angular.module('thingsboard.api.customer', []) | @@ -18,12 +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 | - getCustomerTitle: getCustomerTitle, | 26 | + getShortCustomerInfo: getShortCustomerInfo, |
27 | + applyAssignedCustomersInfo: applyAssignedCustomersInfo, | ||
28 | + applyAssignedCustomerInfo: applyAssignedCustomerInfo, | ||
27 | deleteCustomer: deleteCustomer, | 29 | deleteCustomer: deleteCustomer, |
28 | saveCustomer: saveCustomer | 30 | saveCustomer: saveCustomer |
29 | } | 31 | } |
@@ -61,9 +63,9 @@ function CustomerService($http, $q) { | @@ -61,9 +63,9 @@ function CustomerService($http, $q) { | ||
61 | return deferred.promise; | 63 | return deferred.promise; |
62 | } | 64 | } |
63 | 65 | ||
64 | - function getCustomerTitle(customerId) { | 66 | + function getShortCustomerInfo(customerId) { |
65 | var deferred = $q.defer(); | 67 | var deferred = $q.defer(); |
66 | - var url = '/api/customer/' + customerId + '/title'; | 68 | + var url = '/api/customer/' + customerId + '/shortInfo'; |
67 | $http.get(url, null).then(function success(response) { | 69 | $http.get(url, null).then(function success(response) { |
68 | deferred.resolve(response.data); | 70 | deferred.resolve(response.data); |
69 | }, function fail(response) { | 71 | }, function fail(response) { |
@@ -72,6 +74,77 @@ function CustomerService($http, $q) { | @@ -72,6 +74,77 @@ function CustomerService($http, $q) { | ||
72 | return deferred.promise; | 74 | return deferred.promise; |
73 | } | 75 | } |
74 | 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 | + | ||
75 | function saveCustomer(customer) { | 148 | function saveCustomer(customer) { |
76 | var deferred = $q.defer(); | 149 | var deferred = $q.defer(); |
77 | 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, config) { | 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)) { |
@@ -64,14 +66,26 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic | @@ -64,14 +66,26 @@ function DeviceService($http, $q, $filter, userService, telemetryWebsocketServic | ||
64 | url += '&textOffset=' + pageLink.textOffset; | 66 | url += '&textOffset=' + pageLink.textOffset; |
65 | } | 67 | } |
66 | $http.get(url, config).then(function success(response) { | 68 | $http.get(url, config).then(function success(response) { |
67 | - deferred.resolve(response.data); | 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 | } |
@@ -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; |
ui/src/app/api/subscription.js
0 → 100644
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 | + registration = this.ctx.$scope.$watch(function () { | ||
173 | + return subscription.timeWindowConfig; | ||
174 | + }, function (newTimewindow, prevTimewindow) { | ||
175 | + if (!angular.equals(newTimewindow, prevTimewindow)) { | ||
176 | + subscription.unsubscribe(); | ||
177 | + subscription.subscribe(); | ||
178 | + } | ||
179 | + }); | ||
180 | + this.registrations.push(registration); | ||
181 | + } | ||
182 | + } | ||
183 | + | ||
184 | + registration = this.ctx.$scope.$on('deviceAliasListChanged', function () { | ||
185 | + subscription.checkSubscriptions(); | ||
186 | + }); | ||
187 | + | ||
188 | + this.registrations.push(registration); | ||
189 | + } | ||
190 | + | ||
191 | + initRpc() { | ||
192 | + | ||
193 | + if (this.targetDeviceAliasIds && this.targetDeviceAliasIds.length > 0) { | ||
194 | + this.targetDeviceAliasId = this.targetDeviceAliasIds[0]; | ||
195 | + if (this.ctx.aliasesInfo.deviceAliases[this.targetDeviceAliasId]) { | ||
196 | + this.targetDeviceId = this.ctx.aliasesInfo.deviceAliases[this.targetDeviceAliasId].deviceId; | ||
197 | + } | ||
198 | + var subscription = this; | ||
199 | + var registration = this.ctx.$scope.$on('deviceAliasListChanged', function () { | ||
200 | + var deviceId = null; | ||
201 | + if (subscription.ctx.aliasesInfo.deviceAliases[subscription.targetDeviceAliasId]) { | ||
202 | + deviceId = subscription.ctx.aliasesInfo.deviceAliases[subscription.targetDeviceAliasId].deviceId; | ||
203 | + } | ||
204 | + if (!angular.equals(deviceId, subscription.targetDeviceId)) { | ||
205 | + subscription.targetDeviceId = deviceId; | ||
206 | + if (subscription.targetDeviceId) { | ||
207 | + subscription.rpcEnabled = true; | ||
208 | + } else { | ||
209 | + subscription.rpcEnabled = subscription.ctx.$scope.widgetEditMode ? true : false; | ||
210 | + } | ||
211 | + subscription.callbacks.rpcStateChanged(subscription); | ||
212 | + } | ||
213 | + }); | ||
214 | + this.registrations.push(registration); | ||
215 | + } else if (this.targetDeviceIds && this.targetDeviceIds.length > 0) { | ||
216 | + this.targetDeviceId = this.targetDeviceIds[0]; | ||
217 | + } | ||
218 | + | ||
219 | + if (this.targetDeviceId) { | ||
220 | + this.rpcEnabled = true; | ||
221 | + } else { | ||
222 | + this.rpcEnabled = this.ctx.$scope.widgetEditMode ? true : false; | ||
223 | + } | ||
224 | + this.callbacks.rpcStateChanged(this); | ||
225 | + } | ||
226 | + | ||
227 | + clearRpcError() { | ||
228 | + this.rpcRejection = null; | ||
229 | + this.rpcErrorText = null; | ||
230 | + this.callbacks.onRpcErrorCleared(this); | ||
231 | + } | ||
232 | + | ||
233 | + sendOneWayCommand(method, params, timeout) { | ||
234 | + return this.sendCommand(true, method, params, timeout); | ||
235 | + } | ||
236 | + | ||
237 | + sendTwoWayCommand(method, params, timeout) { | ||
238 | + return this.sendCommand(false, method, params, timeout); | ||
239 | + } | ||
240 | + | ||
241 | + sendCommand(oneWayElseTwoWay, method, params, timeout) { | ||
242 | + if (!this.rpcEnabled) { | ||
243 | + return this.ctx.$q.reject(); | ||
244 | + } | ||
245 | + | ||
246 | + if (this.rpcRejection && this.rpcRejection.status !== 408) { | ||
247 | + this.rpcRejection = null; | ||
248 | + this.rpcErrorText = null; | ||
249 | + this.callbacks.onRpcErrorCleared(this); | ||
250 | + } | ||
251 | + | ||
252 | + var subscription = this; | ||
253 | + | ||
254 | + var requestBody = { | ||
255 | + method: method, | ||
256 | + params: params | ||
257 | + }; | ||
258 | + | ||
259 | + if (timeout && timeout > 0) { | ||
260 | + requestBody.timeout = timeout; | ||
261 | + } | ||
262 | + | ||
263 | + var deferred = this.ctx.$q.defer(); | ||
264 | + this.executingRpcRequest = true; | ||
265 | + this.callbacks.rpcStateChanged(this); | ||
266 | + if (this.ctx.$scope.widgetEditMode) { | ||
267 | + this.ctx.$timeout(function() { | ||
268 | + subscription.executingRpcRequest = false; | ||
269 | + subscription.callbacks.rpcStateChanged(subscription); | ||
270 | + if (oneWayElseTwoWay) { | ||
271 | + deferred.resolve(); | ||
272 | + } else { | ||
273 | + deferred.resolve(requestBody); | ||
274 | + } | ||
275 | + }, 500); | ||
276 | + } else { | ||
277 | + this.executingPromises.push(deferred.promise); | ||
278 | + var targetSendFunction = oneWayElseTwoWay ? this.ctx.deviceService.sendOneWayRpcCommand : this.ctx.deviceService.sendTwoWayRpcCommand; | ||
279 | + targetSendFunction(this.targetDeviceId, requestBody).then( | ||
280 | + function success(responseBody) { | ||
281 | + subscription.rpcRejection = null; | ||
282 | + subscription.rpcErrorText = null; | ||
283 | + var index = subscription.executingPromises.indexOf(deferred.promise); | ||
284 | + if (index >= 0) { | ||
285 | + subscription.executingPromises.splice( index, 1 ); | ||
286 | + } | ||
287 | + subscription.executingRpcRequest = subscription.executingPromises.length > 0; | ||
288 | + subscription.callbacks.onRpcSuccess(subscription); | ||
289 | + deferred.resolve(responseBody); | ||
290 | + }, | ||
291 | + function fail(rejection) { | ||
292 | + var index = subscription.executingPromises.indexOf(deferred.promise); | ||
293 | + if (index >= 0) { | ||
294 | + subscription.executingPromises.splice( index, 1 ); | ||
295 | + } | ||
296 | + subscription.executingRpcRequest = subscription.executingPromises.length > 0; | ||
297 | + subscription.callbacks.rpcStateChanged(subscription); | ||
298 | + if (!subscription.executingRpcRequest || rejection.status === 408) { | ||
299 | + subscription.rpcRejection = rejection; | ||
300 | + if (rejection.status === 408) { | ||
301 | + subscription.rpcErrorText = 'Device is offline.'; | ||
302 | + } else { | ||
303 | + subscription.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText; | ||
304 | + if (rejection.data && rejection.data.length > 0) { | ||
305 | + subscription.rpcErrorText += '</br>'; | ||
306 | + subscription.rpcErrorText += rejection.data; | ||
307 | + } | ||
308 | + } | ||
309 | + subscription.callbacks.onRpcFailed(subscription); | ||
310 | + } | ||
311 | + deferred.reject(rejection); | ||
312 | + } | ||
313 | + ); | ||
314 | + } | ||
315 | + return deferred.promise; | ||
316 | + } | ||
317 | + | ||
318 | + updateDataVisibility(index) { | ||
319 | + var hidden = this.legendData.data[index].hidden; | ||
320 | + if (hidden) { | ||
321 | + this.hiddenData[index].data = this.data[index].data; | ||
322 | + this.data[index].data = []; | ||
323 | + } else { | ||
324 | + this.data[index].data = this.hiddenData[index].data; | ||
325 | + this.hiddenData[index].data = []; | ||
326 | + } | ||
327 | + this.onDataUpdated(); | ||
328 | + } | ||
329 | + | ||
330 | + onDataUpdated(apply) { | ||
331 | + if (this.cafs['dataUpdated']) { | ||
332 | + this.cafs['dataUpdated'](); | ||
333 | + this.cafs['dataUpdated'] = null; | ||
334 | + } | ||
335 | + var subscription = this; | ||
336 | + this.cafs['dataUpdated'] = this.ctx.tbRaf(function() { | ||
337 | + try { | ||
338 | + subscription.callbacks.onDataUpdated(this, apply); | ||
339 | + } catch (e) { | ||
340 | + subscription.callbacks.onDataUpdateError(this, e); | ||
341 | + } | ||
342 | + }); | ||
343 | + if (apply) { | ||
344 | + this.ctx.$scope.$digest(); | ||
345 | + } | ||
346 | + } | ||
347 | + | ||
348 | + updateTimewindowConfig(newTimewindow) { | ||
349 | + this.timeWindowConfig = newTimewindow; | ||
350 | + } | ||
351 | + | ||
352 | + onResetTimewindow() { | ||
353 | + if (this.useDashboardTimewindow) { | ||
354 | + this.ctx.dashboardTimewindowApi.onResetTimewindow(); | ||
355 | + } else { | ||
356 | + if (this.originalTimewindow) { | ||
357 | + this.timeWindowConfig = angular.copy(this.originalTimewindow); | ||
358 | + this.originalTimewindow = null; | ||
359 | + this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); | ||
360 | + } | ||
361 | + } | ||
362 | + } | ||
363 | + | ||
364 | + onUpdateTimewindow(startTimeMs, endTimeMs) { | ||
365 | + if (this.useDashboardTimewindow) { | ||
366 | + this.ctx.dashboardTimewindowApi.onUpdateTimewindow(startTimeMs, endTimeMs); | ||
367 | + } else { | ||
368 | + if (!this.originalTimewindow) { | ||
369 | + this.originalTimewindow = angular.copy(this.timeWindowConfig); | ||
370 | + } | ||
371 | + this.timeWindowConfig = this.ctx.timeService.toHistoryTimewindow(this.timeWindowConfig, startTimeMs, endTimeMs); | ||
372 | + this.callbacks.timeWindowUpdated(this, this.timeWindowConfig); | ||
373 | + } | ||
374 | + } | ||
375 | + | ||
376 | + notifyDataLoading() { | ||
377 | + this.loadingData = true; | ||
378 | + this.callbacks.dataLoading(this); | ||
379 | + } | ||
380 | + | ||
381 | + notifyDataLoaded() { | ||
382 | + this.loadingData = false; | ||
383 | + this.callbacks.dataLoading(this); | ||
384 | + } | ||
385 | + | ||
386 | + updateTimewindow() { | ||
387 | + this.timeWindow.interval = this.subscriptionTimewindow.aggregation.interval || 1000; | ||
388 | + if (this.subscriptionTimewindow.realtimeWindowMs) { | ||
389 | + this.timeWindow.maxTime = (new Date).getTime() + this.timeWindow.stDiff; | ||
390 | + this.timeWindow.minTime = this.timeWindow.maxTime - this.subscriptionTimewindow.realtimeWindowMs; | ||
391 | + } else if (this.subscriptionTimewindow.fixedWindow) { | ||
392 | + this.timeWindow.maxTime = this.subscriptionTimewindow.fixedWindow.endTimeMs; | ||
393 | + this.timeWindow.minTime = this.subscriptionTimewindow.fixedWindow.startTimeMs; | ||
394 | + } | ||
395 | + } | ||
396 | + | ||
397 | + updateRealtimeSubscription(subscriptionTimewindow) { | ||
398 | + if (subscriptionTimewindow) { | ||
399 | + this.subscriptionTimewindow = subscriptionTimewindow; | ||
400 | + } else { | ||
401 | + this.subscriptionTimewindow = | ||
402 | + this.ctx.timeService.createSubscriptionTimewindow( | ||
403 | + this.timeWindowConfig, | ||
404 | + this.timeWindow.stDiff); | ||
405 | + } | ||
406 | + this.updateTimewindow(); | ||
407 | + return this.subscriptionTimewindow; | ||
408 | + } | ||
409 | + | ||
410 | + dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) { | ||
411 | + this.notifyDataLoaded(); | ||
412 | + var update = true; | ||
413 | + var currentData; | ||
414 | + if (this.displayLegend && this.legendData.data[datasourceIndex + dataKeyIndex].hidden) { | ||
415 | + currentData = this.hiddenData[datasourceIndex + dataKeyIndex]; | ||
416 | + } else { | ||
417 | + currentData = this.data[datasourceIndex + dataKeyIndex]; | ||
418 | + } | ||
419 | + if (this.type === this.ctx.types.widgetType.latest.value) { | ||
420 | + var prevData = currentData.data; | ||
421 | + if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) { | ||
422 | + var prevValue = prevData[0][1]; | ||
423 | + if (prevValue === sourceData.data[0][1]) { | ||
424 | + update = false; | ||
425 | + } | ||
426 | + } | ||
427 | + } | ||
428 | + if (update) { | ||
429 | + if (this.subscriptionTimewindow && this.subscriptionTimewindow.realtimeWindowMs) { | ||
430 | + this.updateTimewindow(); | ||
431 | + } | ||
432 | + currentData.data = sourceData.data; | ||
433 | + if (this.caulculateLegendData) { | ||
434 | + this.updateLegend(datasourceIndex + dataKeyIndex, sourceData.data, apply); | ||
435 | + } | ||
436 | + this.onDataUpdated(apply); | ||
437 | + } | ||
438 | + } | ||
439 | + | ||
440 | + updateLegend(dataIndex, data, apply) { | ||
441 | + var legendKeyData = this.legendData.data[dataIndex]; | ||
442 | + if (this.legendConfig.showMin) { | ||
443 | + legendKeyData.min = this.ctx.widgetUtils.formatValue(calculateMin(data), this.decimals, this.units); | ||
444 | + } | ||
445 | + if (this.legendConfig.showMax) { | ||
446 | + legendKeyData.max = this.ctx.widgetUtils.formatValue(calculateMax(data), this.decimals, this.units); | ||
447 | + } | ||
448 | + if (this.legendConfig.showAvg) { | ||
449 | + legendKeyData.avg = this.ctx.widgetUtils.formatValue(calculateAvg(data), this.decimals, this.units); | ||
450 | + } | ||
451 | + if (this.legendConfig.showTotal) { | ||
452 | + legendKeyData.total = this.ctx.widgetUtils.formatValue(calculateTotal(data), this.decimals, this.units); | ||
453 | + } | ||
454 | + this.callbacks.legendDataUpdated(this, apply !== false); | ||
455 | + } | ||
456 | + | ||
457 | + subscribe() { | ||
458 | + if (this.type === this.ctx.types.widgetType.rpc.value) { | ||
459 | + return; | ||
460 | + } | ||
461 | + this.notifyDataLoading(); | ||
462 | + if (this.type === this.ctx.types.widgetType.timeseries.value && this.timeWindowConfig) { | ||
463 | + this.updateRealtimeSubscription(); | ||
464 | + if (this.subscriptionTimewindow.fixedWindow) { | ||
465 | + this.onDataUpdated(); | ||
466 | + } | ||
467 | + } | ||
468 | + var index = 0; | ||
469 | + for (var i = 0; i < this.datasources.length; i++) { | ||
470 | + var datasource = this.datasources[i]; | ||
471 | + if (angular.isFunction(datasource)) | ||
472 | + continue; | ||
473 | + var deviceId = null; | ||
474 | + if (datasource.type === this.ctx.types.datasourceType.device) { | ||
475 | + var aliasName = null; | ||
476 | + var deviceName = null; | ||
477 | + if (datasource.deviceId) { | ||
478 | + deviceId = datasource.deviceId; | ||
479 | + datasource.name = datasource.deviceName; | ||
480 | + aliasName = datasource.deviceName; | ||
481 | + deviceName = datasource.deviceName; | ||
482 | + } else if (datasource.deviceAliasId && this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId]) { | ||
483 | + deviceId = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].deviceId; | ||
484 | + datasource.name = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].alias; | ||
485 | + aliasName = this.ctx.aliasesInfo.deviceAliases[datasource.deviceAliasId].alias; | ||
486 | + deviceName = ''; | ||
487 | + var devicesInfo = this.ctx.aliasesInfo.deviceAliasesInfo[datasource.deviceAliasId]; | ||
488 | + for (var d = 0; d < devicesInfo.length; d++) { | ||
489 | + if (devicesInfo[d].id === deviceId) { | ||
490 | + deviceName = devicesInfo[d].name; | ||
491 | + break; | ||
492 | + } | ||
493 | + } | ||
494 | + } | ||
495 | + } else { | ||
496 | + datasource.name = datasource.name || this.ctx.types.datasourceType.function; | ||
497 | + } | ||
498 | + for (var dk = 0; dk < datasource.dataKeys.length; dk++) { | ||
499 | + updateDataKeyLabel(datasource.dataKeys[dk], datasource.name, deviceName, aliasName); | ||
500 | + } | ||
501 | + | ||
502 | + var subscription = this; | ||
503 | + | ||
504 | + var listener = { | ||
505 | + subscriptionType: this.type, | ||
506 | + subscriptionTimewindow: this.subscriptionTimewindow, | ||
507 | + datasource: datasource, | ||
508 | + deviceId: deviceId, | ||
509 | + dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { | ||
510 | + subscription.dataUpdated(data, datasourceIndex, dataKeyIndex, apply); | ||
511 | + }, | ||
512 | + updateRealtimeSubscription: function () { | ||
513 | + this.subscriptionTimewindow = subscription.updateRealtimeSubscription(); | ||
514 | + return this.subscriptionTimewindow; | ||
515 | + }, | ||
516 | + setRealtimeSubscription: function (subscriptionTimewindow) { | ||
517 | + subscription.updateRealtimeSubscription(angular.copy(subscriptionTimewindow)); | ||
518 | + }, | ||
519 | + datasourceIndex: index | ||
520 | + }; | ||
521 | + | ||
522 | + for (var a = 0; a < datasource.dataKeys.length; a++) { | ||
523 | + this.data[index + a].data = []; | ||
524 | + } | ||
525 | + | ||
526 | + index += datasource.dataKeys.length; | ||
527 | + | ||
528 | + this.datasourceListeners.push(listener); | ||
529 | + this.ctx.datasourceService.subscribeToDatasource(listener); | ||
530 | + } | ||
531 | + } | ||
532 | + | ||
533 | + unsubscribe() { | ||
534 | + if (this.type !== this.ctx.types.widgetType.rpc.value) { | ||
535 | + for (var i = 0; i < this.datasourceListeners.length; i++) { | ||
536 | + var listener = this.datasourceListeners[i]; | ||
537 | + this.ctx.datasourceService.unsubscribeFromDatasource(listener); | ||
538 | + } | ||
539 | + this.datasourceListeners = []; | ||
540 | + } | ||
541 | + } | ||
542 | + | ||
543 | + checkSubscriptions() { | ||
544 | + var subscriptionsChanged = false; | ||
545 | + for (var i = 0; i < this.datasourceListeners.length; i++) { | ||
546 | + var listener = this.datasourceListeners[i]; | ||
547 | + var deviceId = null; | ||
548 | + var aliasName = null; | ||
549 | + if (listener.datasource.type === this.ctx.types.datasourceType.device) { | ||
550 | + if (listener.datasource.deviceAliasId && | ||
551 | + this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId]) { | ||
552 | + deviceId = this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].deviceId; | ||
553 | + aliasName = this.ctx.aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].alias; | ||
554 | + } | ||
555 | + if (!angular.equals(deviceId, listener.deviceId) || | ||
556 | + !angular.equals(aliasName, listener.datasource.name)) { | ||
557 | + subscriptionsChanged = true; | ||
558 | + break; | ||
559 | + } | ||
560 | + } | ||
561 | + } | ||
562 | + if (subscriptionsChanged) { | ||
563 | + this.unsubscribe(); | ||
564 | + this.subscribe(); | ||
565 | + } | ||
566 | + } | ||
567 | + | ||
568 | + destroy() { | ||
569 | + this.unsubscribe(); | ||
570 | + for (var cafId in this.cafs) { | ||
571 | + if (this.cafs[cafId]) { | ||
572 | + this.cafs[cafId](); | ||
573 | + this.cafs[cafId] = null; | ||
574 | + } | ||
575 | + } | ||
576 | + this.registrations.forEach(function (registration) { | ||
577 | + registration(); | ||
578 | + }); | ||
579 | + this.registrations = []; | ||
580 | + } | ||
581 | + | ||
582 | +} | ||
583 | + | ||
584 | +const varsRegex = /\$\{([^\}]*)\}/g; | ||
585 | + | ||
586 | +function updateDataKeyLabel(dataKey, dsName, deviceName, aliasName) { | ||
587 | + var pattern = dataKey.pattern; | ||
588 | + var label = dataKey.pattern; | ||
589 | + var match = varsRegex.exec(pattern); | ||
590 | + while (match !== null) { | ||
591 | + var variable = match[0]; | ||
592 | + var variableName = match[1]; | ||
593 | + if (variableName === 'dsName') { | ||
594 | + label = label.split(variable).join(dsName); | ||
595 | + } else if (variableName === 'deviceName') { | ||
596 | + label = label.split(variable).join(deviceName); | ||
597 | + } else if (variableName === 'aliasName') { | ||
598 | + label = label.split(variable).join(aliasName); | ||
599 | + } | ||
600 | + match = varsRegex.exec(pattern); | ||
601 | + } | ||
602 | + dataKey.label = label; | ||
603 | +} | ||
604 | + | ||
605 | +function calculateMin(data) { | ||
606 | + if (data.length > 0) { | ||
607 | + var result = Number(data[0][1]); | ||
608 | + for (var i=1;i<data.length;i++) { | ||
609 | + result = Math.min(result, Number(data[i][1])); | ||
610 | + } | ||
611 | + return result; | ||
612 | + } else { | ||
613 | + return null; | ||
614 | + } | ||
615 | +} | ||
616 | + | ||
617 | +function calculateMax(data) { | ||
618 | + if (data.length > 0) { | ||
619 | + var result = Number(data[0][1]); | ||
620 | + for (var i=1;i<data.length;i++) { | ||
621 | + result = Math.max(result, Number(data[i][1])); | ||
622 | + } | ||
623 | + return result; | ||
624 | + } else { | ||
625 | + return null; | ||
626 | + } | ||
627 | +} | ||
628 | + | ||
629 | +function calculateAvg(data) { | ||
630 | + if (data.length > 0) { | ||
631 | + return calculateTotal(data)/data.length; | ||
632 | + } else { | ||
633 | + return null; | ||
634 | + } | ||
635 | +} | ||
636 | + | ||
637 | +function calculateTotal(data) { | ||
638 | + if (data.length > 0) { | ||
639 | + var result = 0; | ||
640 | + for (var i = 0; i < data.length; i++) { | ||
641 | + result += Number(data[i][1]); | ||
642 | + } | ||
643 | + return result; | ||
644 | + } else { | ||
645 | + return null; | ||
646 | + } | ||
647 | +} |
@@ -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); |
@@ -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,9 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -104,7 +104,9 @@ 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 | ||
108 | } | 110 | } |
109 | 111 | ||
110 | return service; | 112 | return service; |
@@ -276,4 +278,153 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | @@ -276,4 +278,153 @@ function Utils($mdColorPalette, $rootScope, $window, types) { | ||
276 | deferred.resolve(response); | 278 | deferred.resolve(response); |
277 | } | 279 | } |
278 | 280 | ||
281 | + function guid() { | ||
282 | + function s4() { | ||
283 | + return Math.floor((1 + Math.random()) * 0x10000) | ||
284 | + .toString(16) | ||
285 | + .substring(1); | ||
286 | + } | ||
287 | + return s4() + s4() + '-' + s4() + '-' + s4() + '-' + | ||
288 | + s4() + '-' + s4() + s4() + s4(); | ||
289 | + } | ||
290 | + | ||
291 | + function genNextColor(datasources) { | ||
292 | + var index = 0; | ||
293 | + if (datasources) { | ||
294 | + for (var i = 0; i < datasources.length; i++) { | ||
295 | + var datasource = datasources[i]; | ||
296 | + index += datasource.dataKeys.length; | ||
297 | + } | ||
298 | + } | ||
299 | + return getMaterialColor(index); | ||
300 | + } | ||
301 | + | ||
302 | + /*var defaultDataKey = { | ||
303 | + name: 'f(x)', | ||
304 | + type: types.dataKeyType.function, | ||
305 | + label: 'Sin', | ||
306 | + color: getMaterialColor(0), | ||
307 | + funcBody: getPredefinedFunctionBody('Sin'), | ||
308 | + settings: {}, | ||
309 | + _hash: Math.random() | ||
310 | + }; | ||
311 | + | ||
312 | + var defaultDatasource = { | ||
313 | + type: types.datasourceType.function, | ||
314 | + name: types.datasourceType.function, | ||
315 | + dataKeys: [angular.copy(defaultDataKey)] | ||
316 | + };*/ | ||
317 | + | ||
318 | + function createKey(keyInfo, type, datasources) { | ||
319 | + var dataKey = { | ||
320 | + name: keyInfo.name, | ||
321 | + type: type, | ||
322 | + label: keyInfo.label || keyInfo.name, | ||
323 | + color: genNextColor(datasources), | ||
324 | + funcBody: keyInfo.funcBody, | ||
325 | + settings: {}, | ||
326 | + _hash: Math.random() | ||
327 | + } | ||
328 | + return dataKey; | ||
329 | + } | ||
330 | + | ||
331 | + function createDatasourceKeys(keyInfos, type, datasource, datasources) { | ||
332 | + for (var i=0;i<keyInfos.length;i++) { | ||
333 | + var keyInfo = keyInfos[i]; | ||
334 | + var dataKey = createKey(keyInfo, type, datasources); | ||
335 | + datasource.dataKeys.push(dataKey); | ||
336 | + } | ||
337 | + } | ||
338 | + | ||
339 | + function createDatasourceFromSubscription(subscriptionInfo, datasources, device) { | ||
340 | + var datasource; | ||
341 | + if (subscriptionInfo.type === types.datasourceType.device) { | ||
342 | + datasource = { | ||
343 | + type: subscriptionInfo.type, | ||
344 | + deviceName: device.name, | ||
345 | + deviceId: device.id.id, | ||
346 | + dataKeys: [] | ||
347 | + } | ||
348 | + } else if (subscriptionInfo.type === types.datasourceType.function) { | ||
349 | + datasource = { | ||
350 | + type: subscriptionInfo.type, | ||
351 | + name: subscriptionInfo.name || types.datasourceType.function, | ||
352 | + dataKeys: [] | ||
353 | + } | ||
354 | + } | ||
355 | + datasources.push(datasource); | ||
356 | + if (subscriptionInfo.timeseries) { | ||
357 | + createDatasourceKeys(subscriptionInfo.timeseries, types.dataKeyType.timeseries, datasource, datasources); | ||
358 | + } | ||
359 | + if (subscriptionInfo.attributes) { | ||
360 | + createDatasourceKeys(subscriptionInfo.attributes, types.dataKeyType.attribute, datasource, datasources); | ||
361 | + } | ||
362 | + if (subscriptionInfo.functions) { | ||
363 | + createDatasourceKeys(subscriptionInfo.functions, types.dataKeyType.function, datasource, datasources); | ||
364 | + } | ||
365 | + } | ||
366 | + | ||
367 | + function processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred) { | ||
368 | + if (index < subscriptionsInfo.length) { | ||
369 | + var subscriptionInfo = subscriptionsInfo[index]; | ||
370 | + if (subscriptionInfo.type === types.datasourceType.device) { | ||
371 | + if (subscriptionInfo.deviceId) { | ||
372 | + deviceService.getDevice(subscriptionInfo.deviceId, true, {ignoreLoading: true}).then( | ||
373 | + function success(device) { | ||
374 | + createDatasourceFromSubscription(subscriptionInfo, datasources, device); | ||
375 | + index++; | ||
376 | + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred); | ||
377 | + }, | ||
378 | + function fail() { | ||
379 | + index++; | ||
380 | + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred); | ||
381 | + } | ||
382 | + ); | ||
383 | + } else if (subscriptionInfo.deviceName || subscriptionInfo.deviceNamePrefix | ||
384 | + || subscriptionInfo.deviceIds) { | ||
385 | + var promise; | ||
386 | + if (subscriptionInfo.deviceName) { | ||
387 | + promise = deviceService.fetchAliasDeviceByNameFilter(subscriptionInfo.deviceName, 1, false, {ignoreLoading: true}); | ||
388 | + } else if (subscriptionInfo.deviceNamePrefix) { | ||
389 | + promise = deviceService.fetchAliasDeviceByNameFilter(subscriptionInfo.deviceNamePrefix, 100, false, {ignoreLoading: true}); | ||
390 | + } else if (subscriptionInfo.deviceIds) { | ||
391 | + promise = deviceService.getDevices(subscriptionInfo.deviceIds, {ignoreLoading: true}); | ||
392 | + } | ||
393 | + promise.then( | ||
394 | + function success(devices) { | ||
395 | + if (devices && devices.length > 0) { | ||
396 | + for (var i = 0; i < devices.length; i++) { | ||
397 | + var device = devices[i]; | ||
398 | + createDatasourceFromSubscription(subscriptionInfo, datasources, device); | ||
399 | + } | ||
400 | + } | ||
401 | + index++; | ||
402 | + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred); | ||
403 | + }, | ||
404 | + function fail() { | ||
405 | + index++; | ||
406 | + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred); | ||
407 | + } | ||
408 | + ) | ||
409 | + } else { | ||
410 | + index++; | ||
411 | + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred); | ||
412 | + } | ||
413 | + } else if (subscriptionInfo.type === types.datasourceType.function) { | ||
414 | + createDatasourceFromSubscription(subscriptionInfo, datasources); | ||
415 | + index++; | ||
416 | + processSubscriptionsInfo(index, subscriptionsInfo, datasources, deferred); | ||
417 | + } | ||
418 | + } else { | ||
419 | + deferred.resolve(datasources); | ||
420 | + } | ||
421 | + } | ||
422 | + | ||
423 | + function createDatasoucesFromSubscriptionsInfo(subscriptionsInfo) { | ||
424 | + var deferred = $q.defer(); | ||
425 | + var datasources = []; | ||
426 | + processSubscriptionsInfo(0, subscriptionsInfo, datasources, deferred); | ||
427 | + return deferred.promise; | ||
428 | + } | ||
429 | + | ||
279 | } | 430 | } |
@@ -182,12 +182,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t | @@ -182,12 +182,12 @@ function DashboardController($scope, $rootScope, $element, $timeout, $mdMedia, t | ||
182 | 182 | ||
183 | vm.dashboardTimewindowApi = { | 183 | vm.dashboardTimewindowApi = { |
184 | onResetTimewindow: function() { | 184 | onResetTimewindow: function() { |
185 | - if (vm.originalDashboardTimewindow) { | ||
186 | - $timeout(function() { | 185 | + $timeout(function() { |
186 | + if (vm.originalDashboardTimewindow) { | ||
187 | vm.dashboardTimewindow = angular.copy(vm.originalDashboardTimewindow); | 187 | vm.dashboardTimewindow = angular.copy(vm.originalDashboardTimewindow); |
188 | vm.originalDashboardTimewindow = null; | 188 | vm.originalDashboardTimewindow = null; |
189 | - }, 0); | ||
190 | - } | 189 | + } |
190 | + }, 0); | ||
191 | }, | 191 | }, |
192 | onUpdateTimewindow: function(startTimeMs, endTimeMs) { | 192 | onUpdateTimewindow: function(startTimeMs, endTimeMs) { |
193 | if (!vm.originalDashboardTimewindow) { | 193 | if (!vm.originalDashboardTimewindow) { |
@@ -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(); |
@@ -49,7 +49,7 @@ | @@ -49,7 +49,7 @@ | ||
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); |
@@ -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 }} |
@@ -15,6 +15,7 @@ | @@ -15,6 +15,7 @@ | ||
15 | */ | 15 | */ |
16 | import $ from 'jquery'; | 16 | import $ from 'jquery'; |
17 | import 'javascript-detect-element-resize/detect-element-resize'; | 17 | import 'javascript-detect-element-resize/detect-element-resize'; |
18 | +import Subscription from '../api/subscription'; | ||
18 | 19 | ||
19 | /* eslint-disable angular/angularelement */ | 20 | /* eslint-disable angular/angularelement */ |
20 | 21 | ||
@@ -34,19 +35,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -34,19 +35,11 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
34 | $scope.rpcErrorText = null; | 35 | $scope.rpcErrorText = null; |
35 | $scope.rpcEnabled = false; | 36 | $scope.rpcEnabled = false; |
36 | $scope.executingRpcRequest = false; | 37 | $scope.executingRpcRequest = false; |
37 | - $scope.executingPromises = []; | ||
38 | 38 | ||
39 | var gridsterItemInited = false; | 39 | var gridsterItemInited = false; |
40 | 40 | ||
41 | - var datasourceListeners = []; | ||
42 | - var targetDeviceAliasId = null; | ||
43 | - var targetDeviceId = null; | ||
44 | - var originalTimewindow = null; | ||
45 | - var subscriptionTimewindow = null; | ||
46 | var cafs = {}; | 41 | var cafs = {}; |
47 | 42 | ||
48 | - var varsRegex = /\$\{([^\}]*)\}/g; | ||
49 | - | ||
50 | /* | 43 | /* |
51 | * data = array of datasourceData | 44 | * data = array of datasourceData |
52 | * datasourceData = { | 45 | * datasourceData = { |
@@ -54,8 +47,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -54,8 +47,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
54 | * dataKey, { name, config } | 47 | * dataKey, { name, config } |
55 | * data = array of [time, value] | 48 | * data = array of [time, value] |
56 | * } | 49 | * } |
57 | - * | ||
58 | - * | ||
59 | */ | 50 | */ |
60 | 51 | ||
61 | var widgetContext = { | 52 | var widgetContext = { |
@@ -71,22 +62,70 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -71,22 +62,70 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
71 | settings: widget.config.settings, | 62 | settings: widget.config.settings, |
72 | units: widget.config.units || '', | 63 | units: widget.config.units || '', |
73 | decimals: angular.isDefined(widget.config.decimals) ? widget.config.decimals : 2, | 64 | decimals: angular.isDefined(widget.config.decimals) ? widget.config.decimals : 2, |
74 | - datasources: angular.copy(widget.config.datasources), | ||
75 | - data: [], | ||
76 | - hiddenData: [], | ||
77 | - timeWindow: { | ||
78 | - stDiff: stDiff | ||
79 | - }, | 65 | + subscriptions: {}, |
66 | + defaultSubscription: null, | ||
80 | timewindowFunctions: { | 67 | timewindowFunctions: { |
81 | - onUpdateTimewindow: onUpdateTimewindow, | ||
82 | - onResetTimewindow: onResetTimewindow | 68 | + onUpdateTimewindow: function(startTimeMs, endTimeMs) { |
69 | + if (widgetContext.defaultSubscription) { | ||
70 | + widgetContext.defaultSubscription.onUpdateTimewindow(startTimeMs, endTimeMs); | ||
71 | + } | ||
72 | + }, | ||
73 | + onResetTimewindow: function() { | ||
74 | + if (widgetContext.defaultSubscription) { | ||
75 | + widgetContext.defaultSubscription.onResetTimewindow(); | ||
76 | + } | ||
77 | + } | ||
78 | + }, | ||
79 | + subscriptionApi: { | ||
80 | + createSubscription: function(options, subscribe) { | ||
81 | + return createSubscription(options, subscribe); | ||
82 | + }, | ||
83 | + | ||
84 | + | ||
85 | + // type: "timeseries" or "latest" or "rpc" | ||
86 | + /* devicesSubscriptionInfo = [ | ||
87 | + { | ||
88 | + deviceId: "" | ||
89 | + deviceName: "" | ||
90 | + timeseries: [{ name: "", label: "" }, ..] | ||
91 | + attributes: [{ name: "", label: "" }, ..] | ||
92 | + } | ||
93 | + .. | ||
94 | + ]*/ | ||
95 | + | ||
96 | + // options = { | ||
97 | + // timeWindowConfig, | ||
98 | + // useDashboardTimewindow, | ||
99 | + // legendConfig, | ||
100 | + // decimals, | ||
101 | + // units, | ||
102 | + // callbacks [ onDataUpdated(subscription, apply) ] | ||
103 | + // } | ||
104 | + // | ||
105 | + | ||
106 | + createSubscriptionFromInfo: function (type, subscriptionsInfo, options, useDefaultComponents, subscribe) { | ||
107 | + return createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe); | ||
108 | + }, | ||
109 | + removeSubscription: function(id) { | ||
110 | + var subscription = widgetContext.subscriptions[id]; | ||
111 | + if (subscription) { | ||
112 | + subscription.destroy(); | ||
113 | + delete widgetContext.subscriptions[id]; | ||
114 | + } | ||
115 | + } | ||
83 | }, | 116 | }, |
84 | controlApi: { | 117 | controlApi: { |
85 | sendOneWayCommand: function(method, params, timeout) { | 118 | sendOneWayCommand: function(method, params, timeout) { |
86 | - return sendCommand(true, method, params, timeout); | 119 | + if (widgetContext.defaultSubscription) { |
120 | + return widgetContext.defaultSubscription.sendOneWayCommand(method, params, timeout); | ||
121 | + } | ||
122 | + return null; | ||
87 | }, | 123 | }, |
88 | sendTwoWayCommand: function(method, params, timeout) { | 124 | sendTwoWayCommand: function(method, params, timeout) { |
89 | - return sendCommand(false, method, params, timeout); | 125 | + if (widgetContext.defaultSubscription) { |
126 | + return widgetContext.defaultSubscription.sendTwoWayCommand(method, params, timeout); | ||
127 | + } | ||
128 | + return null; | ||
90 | } | 129 | } |
91 | }, | 130 | }, |
92 | utils: { | 131 | utils: { |
@@ -94,7 +133,27 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -94,7 +133,27 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
94 | } | 133 | } |
95 | }; | 134 | }; |
96 | 135 | ||
136 | + var subscriptionContext = { | ||
137 | + $scope: $scope, | ||
138 | + $q: $q, | ||
139 | + $filter: $filter, | ||
140 | + $timeout: $timeout, | ||
141 | + tbRaf: tbRaf, | ||
142 | + timeService: timeService, | ||
143 | + deviceService: deviceService, | ||
144 | + datasourceService: datasourceService, | ||
145 | + utils: utils, | ||
146 | + widgetUtils: widgetContext.utils, | ||
147 | + dashboardTimewindowApi: dashboardTimewindowApi, | ||
148 | + types: types, | ||
149 | + stDiff: stDiff, | ||
150 | + aliasesInfo: aliasesInfo | ||
151 | + }; | ||
152 | + | ||
97 | var widgetTypeInstance; | 153 | var widgetTypeInstance; |
154 | + | ||
155 | + vm.useCustomDatasources = false; | ||
156 | + | ||
98 | try { | 157 | try { |
99 | widgetTypeInstance = new widgetType(widgetContext); | 158 | widgetTypeInstance = new widgetType(widgetContext); |
100 | } catch (e) { | 159 | } catch (e) { |
@@ -119,19 +178,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -119,19 +178,14 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
119 | if (!widgetTypeInstance.onDestroy) { | 178 | if (!widgetTypeInstance.onDestroy) { |
120 | widgetTypeInstance.onDestroy = function() {}; | 179 | widgetTypeInstance.onDestroy = function() {}; |
121 | } | 180 | } |
122 | - | ||
123 | - //var bounds = {top: 0, left: 0, bottom: 0, right: 0}; | ||
124 | - //TODO: widgets visibility | ||
125 | - /*var visible = false;*/ | ||
126 | - | ||
127 | - $scope.clearRpcError = function() { | ||
128 | - $scope.rpcRejection = null; | ||
129 | - $scope.rpcErrorText = null; | 181 | + if (widgetTypeInstance.useCustomDatasources) { |
182 | + vm.useCustomDatasources = widgetTypeInstance.useCustomDatasources(); | ||
130 | } | 183 | } |
131 | 184 | ||
132 | - vm.gridsterItemInitialized = gridsterItemInitialized; | ||
133 | - | ||
134 | //TODO: widgets visibility | 185 | //TODO: widgets visibility |
186 | + | ||
187 | + //var bounds = {top: 0, left: 0, bottom: 0, right: 0}; | ||
188 | + /*var visible = false;*/ | ||
135 | /*vm.visibleRectChanged = visibleRectChanged; | 189 | /*vm.visibleRectChanged = visibleRectChanged; |
136 | 190 | ||
137 | function visibleRectChanged(newVisibleRect) { | 191 | function visibleRectChanged(newVisibleRect) { |
@@ -139,23 +193,216 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -139,23 +193,216 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
139 | updateVisibility(); | 193 | updateVisibility(); |
140 | }*/ | 194 | }*/ |
141 | 195 | ||
196 | + $scope.clearRpcError = function() { | ||
197 | + if (widgetContext.defaultSubscription) { | ||
198 | + widgetContext.defaultSubscription.clearRpcError(); | ||
199 | + } | ||
200 | + } | ||
201 | + | ||
202 | + vm.gridsterItemInitialized = gridsterItemInitialized; | ||
203 | + | ||
142 | initialize(); | 204 | initialize(); |
143 | 205 | ||
144 | - function handleWidgetException(e) { | ||
145 | - $log.error(e); | ||
146 | - $scope.widgetErrorData = utils.processWidgetException(e); | 206 | + |
207 | + /* | ||
208 | + options = { | ||
209 | + type, | ||
210 | + targetDeviceAliasIds, // RPC | ||
211 | + targetDeviceIds, // RPC | ||
212 | + datasources, | ||
213 | + timeWindowConfig, | ||
214 | + useDashboardTimewindow, | ||
215 | + legendConfig, | ||
216 | + decimals, | ||
217 | + units, | ||
218 | + callbacks | ||
219 | + } | ||
220 | + */ | ||
221 | + | ||
222 | + function createSubscriptionFromInfo(type, subscriptionsInfo, options, useDefaultComponents, subscribe) { | ||
223 | + var deferred = $q.defer(); | ||
224 | + options.type = type; | ||
225 | + | ||
226 | + if (useDefaultComponents) { | ||
227 | + defaultComponentsOptions(options); | ||
228 | + } else { | ||
229 | + if (!options.timeWindowConfig) { | ||
230 | + options.useDashboardTimewindow = true; | ||
231 | + } | ||
232 | + } | ||
233 | + | ||
234 | + utils.createDatasoucesFromSubscriptionsInfo(subscriptionsInfo).then( | ||
235 | + function (datasources) { | ||
236 | + options.datasources = datasources; | ||
237 | + var subscription = createSubscription(options, subscribe); | ||
238 | + if (useDefaultComponents) { | ||
239 | + defaultSubscriptionOptions(subscription, options); | ||
240 | + } | ||
241 | + deferred.resolve(subscription); | ||
242 | + } | ||
243 | + ); | ||
244 | + return deferred.promise; | ||
245 | + } | ||
246 | + | ||
247 | + function createSubscription(options, subscribe) { | ||
248 | + options.dashboardTimewindow = dashboardTimewindow; | ||
249 | + var subscription = | ||
250 | + new Subscription(subscriptionContext, options); | ||
251 | + widgetContext.subscriptions[subscription.id] = subscription; | ||
252 | + if (subscribe) { | ||
253 | + subscription.subscribe(); | ||
254 | + } | ||
255 | + return subscription; | ||
147 | } | 256 | } |
148 | 257 | ||
149 | - function notifyDataLoaded() { | ||
150 | - if ($scope.loadingData === true) { | 258 | + function defaultComponentsOptions(options) { |
259 | + options.useDashboardTimewindow = angular.isDefined(widget.config.useDashboardTimewindow) | ||
260 | + ? widget.config.useDashboardTimewindow : true; | ||
261 | + | ||
262 | + options.timeWindowConfig = options.useDashboardTimewindow ? dashboardTimewindow : widget.config.timewindow; | ||
263 | + options.legendConfig = null; | ||
264 | + | ||
265 | + if ($scope.displayLegend) { | ||
266 | + options.legendConfig = $scope.legendConfig; | ||
267 | + } | ||
268 | + options.decimals = widgetContext.decimals; | ||
269 | + options.units = widgetContext.units; | ||
270 | + | ||
271 | + options.callbacks = { | ||
272 | + onDataUpdated: function() { | ||
273 | + widgetTypeInstance.onDataUpdated(); | ||
274 | + }, | ||
275 | + onDataUpdateError: function(subscription, e) { | ||
276 | + handleWidgetException(e); | ||
277 | + }, | ||
278 | + dataLoading: function(subscription) { | ||
279 | + if ($scope.loadingData !== subscription.loadingData) { | ||
280 | + $scope.loadingData = subscription.loadingData; | ||
281 | + } | ||
282 | + }, | ||
283 | + legendDataUpdated: function(subscription, apply) { | ||
284 | + if (apply) { | ||
285 | + $scope.$digest(); | ||
286 | + } | ||
287 | + }, | ||
288 | + timeWindowUpdated: function(subscription, timeWindowConfig) { | ||
289 | + widget.config.timewindow = timeWindowConfig; | ||
290 | + $scope.$apply(); | ||
291 | + } | ||
292 | + } | ||
293 | + } | ||
294 | + | ||
295 | + function defaultSubscriptionOptions(subscription, options) { | ||
296 | + if (!options.useDashboardTimewindow) { | ||
297 | + $scope.$watch(function () { | ||
298 | + return widget.config.timewindow; | ||
299 | + }, function (newTimewindow, prevTimewindow) { | ||
300 | + if (!angular.equals(newTimewindow, prevTimewindow)) { | ||
301 | + subscription.updateTimewindowConfig(widget.config.timewindow); | ||
302 | + } | ||
303 | + }); | ||
304 | + } | ||
305 | + if ($scope.displayLegend) { | ||
306 | + $scope.legendData = subscription.legendData; | ||
307 | + } | ||
308 | + } | ||
309 | + | ||
310 | + function createDefaultSubscription() { | ||
311 | + var subscription; | ||
312 | + var options; | ||
313 | + if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { | ||
314 | + options = { | ||
315 | + type: widget.type, | ||
316 | + datasources: angular.copy(widget.config.datasources) | ||
317 | + }; | ||
318 | + defaultComponentsOptions(options); | ||
319 | + | ||
320 | + subscription = createSubscription(options); | ||
321 | + | ||
322 | + defaultSubscriptionOptions(subscription, options); | ||
323 | + | ||
324 | + // backward compatibility | ||
325 | + | ||
326 | + widgetContext.datasources = subscription.datasources; | ||
327 | + widgetContext.data = subscription.data; | ||
328 | + widgetContext.hiddenData = subscription.hiddenData; | ||
329 | + widgetContext.timeWindow = subscription.timeWindow; | ||
330 | + | ||
331 | + } else if (widget.type === types.widgetType.rpc.value) { | ||
332 | + $scope.loadingData = false; | ||
333 | + options = { | ||
334 | + type: widget.type, | ||
335 | + targetDeviceAliasIds: widget.config.targetDeviceAliasIds | ||
336 | + } | ||
337 | + options.callbacks = { | ||
338 | + rpcStateChanged: function(subscription) { | ||
339 | + $scope.rpcEnabled = subscription.rpcEnabled; | ||
340 | + $scope.executingRpcRequest = subscription.executingRpcRequest; | ||
341 | + }, | ||
342 | + onRpcSuccess: function(subscription) { | ||
343 | + $scope.executingRpcRequest = subscription.executingRpcRequest; | ||
344 | + }, | ||
345 | + onRpcFailed: function(subscription) { | ||
346 | + $scope.executingRpcRequest = subscription.executingRpcRequest; | ||
347 | + $scope.rpcErrorText = subscription.rpcErrorText; | ||
348 | + $scope.rpcRejection = subscription.rpcRejection; | ||
349 | + }, | ||
350 | + onRpcErrorCleared: function() { | ||
351 | + $scope.rpcErrorText = null; | ||
352 | + $scope.rpcRejection = null; | ||
353 | + } | ||
354 | + } | ||
355 | + subscription = createSubscription(options); | ||
356 | + } else if (widget.type === types.widgetType.static.value) { | ||
151 | $scope.loadingData = false; | 357 | $scope.loadingData = false; |
152 | } | 358 | } |
359 | + if (subscription) { | ||
360 | + widgetContext.defaultSubscription = subscription; | ||
361 | + } | ||
153 | } | 362 | } |
154 | 363 | ||
155 | - function notifyDataLoading() { | ||
156 | - if ($scope.loadingData === false) { | ||
157 | - $scope.loadingData = true; | 364 | + |
365 | + function initialize() { | ||
366 | + | ||
367 | + if (!vm.useCustomDatasources) { | ||
368 | + createDefaultSubscription(); | ||
369 | + } else { | ||
370 | + $scope.loadingData = false; | ||
158 | } | 371 | } |
372 | + | ||
373 | + $scope.$on('toggleDashboardEditMode', function (event, isEdit) { | ||
374 | + onEditModeChanged(isEdit); | ||
375 | + }); | ||
376 | + | ||
377 | + addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | ||
378 | + | ||
379 | + $scope.$watch(function () { | ||
380 | + return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder; | ||
381 | + }, function () { | ||
382 | + //updateBounds(); | ||
383 | + $scope.$emit("widgetPositionChanged", widget); | ||
384 | + }); | ||
385 | + | ||
386 | + $scope.$on('gridster-item-resized', function (event, item) { | ||
387 | + if (!widgetContext.isMobile) { | ||
388 | + widget.sizeX = item.sizeX; | ||
389 | + widget.sizeY = item.sizeY; | ||
390 | + } | ||
391 | + }); | ||
392 | + | ||
393 | + $scope.$on('mobileModeChanged', function (event, newIsMobile) { | ||
394 | + onMobileModeChanged(newIsMobile); | ||
395 | + }); | ||
396 | + | ||
397 | + $scope.$on("$destroy", function () { | ||
398 | + removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | ||
399 | + onDestroy(); | ||
400 | + }); | ||
401 | + } | ||
402 | + | ||
403 | + function handleWidgetException(e) { | ||
404 | + $log.error(e); | ||
405 | + $scope.widgetErrorData = utils.processWidgetException(e); | ||
159 | } | 406 | } |
160 | 407 | ||
161 | function onInit() { | 408 | function onInit() { |
@@ -166,42 +413,12 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -166,42 +413,12 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
166 | } catch (e) { | 413 | } catch (e) { |
167 | handleWidgetException(e); | 414 | handleWidgetException(e); |
168 | } | 415 | } |
169 | - if (widgetContext.dataUpdatePending) { | ||
170 | - widgetContext.dataUpdatePending = false; | ||
171 | - onDataUpdated(); | 416 | + if (!vm.useCustomDatasources && widgetContext.defaultSubscription) { |
417 | + widgetContext.defaultSubscription.subscribe(); | ||
172 | } | 418 | } |
173 | } | 419 | } |
174 | } | 420 | } |
175 | 421 | ||
176 | - function updateTimewindow() { | ||
177 | - widgetContext.timeWindow.interval = subscriptionTimewindow.aggregation.interval || 1000; | ||
178 | - if (subscriptionTimewindow.realtimeWindowMs) { | ||
179 | - widgetContext.timeWindow.maxTime = (new Date).getTime() + widgetContext.timeWindow.stDiff; | ||
180 | - widgetContext.timeWindow.minTime = widgetContext.timeWindow.maxTime - subscriptionTimewindow.realtimeWindowMs; | ||
181 | - } else if (subscriptionTimewindow.fixedWindow) { | ||
182 | - widgetContext.timeWindow.maxTime = subscriptionTimewindow.fixedWindow.endTimeMs; | ||
183 | - widgetContext.timeWindow.minTime = subscriptionTimewindow.fixedWindow.startTimeMs; | ||
184 | - } | ||
185 | - } | ||
186 | - | ||
187 | - function onDataUpdated() { | ||
188 | - if (widgetContext.inited) { | ||
189 | - if (cafs['dataUpdate']) { | ||
190 | - cafs['dataUpdate'](); | ||
191 | - cafs['dataUpdate'] = null; | ||
192 | - } | ||
193 | - cafs['dataUpdate'] = tbRaf(function() { | ||
194 | - try { | ||
195 | - widgetTypeInstance.onDataUpdated(); | ||
196 | - } catch (e) { | ||
197 | - handleWidgetException(e); | ||
198 | - } | ||
199 | - }); | ||
200 | - } else { | ||
201 | - widgetContext.dataUpdatePending = true; | ||
202 | - } | ||
203 | - } | ||
204 | - | ||
205 | function checkSize() { | 422 | function checkSize() { |
206 | var width = widgetContext.$containerParent.width(); | 423 | var width = widgetContext.$containerParent.width(); |
207 | var height = widgetContext.$containerParent.height(); | 424 | var height = widgetContext.$containerParent.height(); |
@@ -289,11 +506,35 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -289,11 +506,35 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
289 | } | 506 | } |
290 | } | 507 | } |
291 | 508 | ||
509 | + function isNumeric(val) { | ||
510 | + return (val - parseFloat( val ) + 1) >= 0; | ||
511 | + } | ||
512 | + | ||
513 | + function formatValue(value, dec, units) { | ||
514 | + if (angular.isDefined(value) && | ||
515 | + value !== null && isNumeric(value)) { | ||
516 | + var formatted = value; | ||
517 | + if (angular.isDefined(dec)) { | ||
518 | + formatted = formatted.toFixed(dec); | ||
519 | + } | ||
520 | + formatted = (formatted * 1).toString(); | ||
521 | + if (angular.isDefined(units) && units.length > 0) { | ||
522 | + formatted += ' ' + units; | ||
523 | + } | ||
524 | + return formatted; | ||
525 | + } else { | ||
526 | + return ''; | ||
527 | + } | ||
528 | + } | ||
529 | + | ||
292 | function onDestroy() { | 530 | function onDestroy() { |
293 | - unsubscribe(); | 531 | + for (var id in widgetContext.subscriptions) { |
532 | + var subscription = widgetContext.subscriptions[id]; | ||
533 | + subscription.destroy(); | ||
534 | + } | ||
535 | + widgetContext.subscriptions = []; | ||
294 | if (widgetContext.inited) { | 536 | if (widgetContext.inited) { |
295 | widgetContext.inited = false; | 537 | widgetContext.inited = false; |
296 | - widgetContext.dataUpdatePending = false; | ||
297 | for (var cafId in cafs) { | 538 | for (var cafId in cafs) { |
298 | if (cafs[cafId]) { | 539 | if (cafs[cafId]) { |
299 | cafs[cafId](); | 540 | cafs[cafId](); |
@@ -308,244 +549,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -308,244 +549,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
308 | } | 549 | } |
309 | } | 550 | } |
310 | 551 | ||
311 | - function onRestart() { | ||
312 | - onDestroy(); | ||
313 | - onInit(); | ||
314 | - } | ||
315 | - | ||
316 | -/* scope.legendData = { | ||
317 | - keys: [], | ||
318 | - data: [] | ||
319 | - | ||
320 | - key: { | ||
321 | - label: '', | ||
322 | - color: '' | ||
323 | - dataIndex: 0 | ||
324 | - } | ||
325 | - data: { | ||
326 | - min: null, | ||
327 | - max: null, | ||
328 | - avg: null, | ||
329 | - total: null | ||
330 | - } | ||
331 | - };*/ | ||
332 | - | ||
333 | - | ||
334 | - function initialize() { | ||
335 | - | ||
336 | - $scope.caulculateLegendData = $scope.displayLegend && | ||
337 | - widget.type === types.widgetType.timeseries.value && | ||
338 | - ($scope.legendConfig.showMin === true || | ||
339 | - $scope.legendConfig.showMax === true || | ||
340 | - $scope.legendConfig.showAvg === true || | ||
341 | - $scope.legendConfig.showTotal === true); | ||
342 | - | ||
343 | - if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { | ||
344 | - var dataIndex = 0; | ||
345 | - for (var i = 0; i < widgetContext.datasources.length; i++) { | ||
346 | - var datasource = widgetContext.datasources[i]; | ||
347 | - for (var a = 0; a < datasource.dataKeys.length; a++) { | ||
348 | - var dataKey = datasource.dataKeys[a]; | ||
349 | - dataKey.pattern = angular.copy(dataKey.label); | ||
350 | - var datasourceData = { | ||
351 | - datasource: datasource, | ||
352 | - dataKey: dataKey, | ||
353 | - data: [] | ||
354 | - }; | ||
355 | - widgetContext.data.push(datasourceData); | ||
356 | - widgetContext.hiddenData.push({data: []}); | ||
357 | - if ($scope.displayLegend) { | ||
358 | - var legendKey = { | ||
359 | - dataKey: dataKey, | ||
360 | - dataIndex: dataIndex++ | ||
361 | - }; | ||
362 | - $scope.legendData.keys.push(legendKey); | ||
363 | - var legendKeyData = { | ||
364 | - min: null, | ||
365 | - max: null, | ||
366 | - avg: null, | ||
367 | - total: null, | ||
368 | - hidden: false | ||
369 | - }; | ||
370 | - $scope.legendData.data.push(legendKeyData); | ||
371 | - } | ||
372 | - } | ||
373 | - } | ||
374 | - if ($scope.displayLegend) { | ||
375 | - $scope.legendData.keys = $filter('orderBy')($scope.legendData.keys, '+label'); | ||
376 | - $scope.$on('legendDataHiddenChanged', function (event, index) { | ||
377 | - event.stopPropagation(); | ||
378 | - var hidden = $scope.legendData.data[index].hidden; | ||
379 | - if (hidden) { | ||
380 | - widgetContext.hiddenData[index].data = widgetContext.data[index].data; | ||
381 | - widgetContext.data[index].data = []; | ||
382 | - } else { | ||
383 | - widgetContext.data[index].data = widgetContext.hiddenData[index].data; | ||
384 | - widgetContext.hiddenData[index].data = []; | ||
385 | - } | ||
386 | - onDataUpdated(); | ||
387 | - }); | ||
388 | - } | ||
389 | - } else if (widget.type === types.widgetType.rpc.value) { | ||
390 | - if (widget.config.targetDeviceAliasIds && widget.config.targetDeviceAliasIds.length > 0) { | ||
391 | - targetDeviceAliasId = widget.config.targetDeviceAliasIds[0]; | ||
392 | - if (aliasesInfo.deviceAliases[targetDeviceAliasId]) { | ||
393 | - targetDeviceId = aliasesInfo.deviceAliases[targetDeviceAliasId].deviceId; | ||
394 | - } | ||
395 | - } | ||
396 | - if (targetDeviceId) { | ||
397 | - $scope.rpcEnabled = true; | ||
398 | - } else { | ||
399 | - $scope.rpcEnabled = $scope.widgetEditMode ? true : false; | ||
400 | - } | ||
401 | - } | ||
402 | - | ||
403 | - $scope.$on('toggleDashboardEditMode', function (event, isEdit) { | ||
404 | - onEditModeChanged(isEdit); | ||
405 | - }); | ||
406 | - | ||
407 | - addResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | ||
408 | - | ||
409 | - $scope.$watch(function () { | ||
410 | - return widget.row + ',' + widget.col + ',' + widget.config.mobileOrder; | ||
411 | - }, function () { | ||
412 | - //updateBounds(); | ||
413 | - $scope.$emit("widgetPositionChanged", widget); | ||
414 | - }); | ||
415 | - | ||
416 | - $scope.$on('gridster-item-resized', function (event, item) { | ||
417 | - if (!widgetContext.isMobile) { | ||
418 | - widget.sizeX = item.sizeX; | ||
419 | - widget.sizeY = item.sizeY; | ||
420 | - } | ||
421 | - }); | ||
422 | - | ||
423 | - $scope.$on('mobileModeChanged', function (event, newIsMobile) { | ||
424 | - onMobileModeChanged(newIsMobile); | ||
425 | - }); | ||
426 | - | ||
427 | - $scope.$on('deviceAliasListChanged', function (event, newAliasesInfo) { | ||
428 | - aliasesInfo = newAliasesInfo; | ||
429 | - if (widget.type === types.widgetType.rpc.value) { | ||
430 | - if (targetDeviceAliasId) { | ||
431 | - var deviceId = null; | ||
432 | - if (aliasesInfo.deviceAliases[targetDeviceAliasId]) { | ||
433 | - deviceId = aliasesInfo.deviceAliases[targetDeviceAliasId].deviceId; | ||
434 | - } | ||
435 | - if (!angular.equals(deviceId, targetDeviceId)) { | ||
436 | - targetDeviceId = deviceId; | ||
437 | - if (targetDeviceId) { | ||
438 | - $scope.rpcEnabled = true; | ||
439 | - } else { | ||
440 | - $scope.rpcEnabled = $scope.widgetEditMode ? true : false; | ||
441 | - } | ||
442 | - onRestart(); | ||
443 | - } | ||
444 | - } | ||
445 | - } else { | ||
446 | - checkSubscriptions(); | ||
447 | - } | ||
448 | - }); | ||
449 | - | ||
450 | - $scope.$on("$destroy", function () { | ||
451 | - removeResizeListener(widgetContext.$containerParent[0], onResize); // eslint-disable-line no-undef | ||
452 | - onDestroy(); | ||
453 | - }); | ||
454 | - | ||
455 | - if (widget.type === types.widgetType.timeseries.value) { | ||
456 | - widgetContext.useDashboardTimewindow = angular.isDefined(widget.config.useDashboardTimewindow) | ||
457 | - ? widget.config.useDashboardTimewindow : true; | ||
458 | - if (widgetContext.useDashboardTimewindow) { | ||
459 | - $scope.$on('dashboardTimewindowChanged', function (event, newDashboardTimewindow) { | ||
460 | - if (!angular.equals(dashboardTimewindow, newDashboardTimewindow)) { | ||
461 | - dashboardTimewindow = newDashboardTimewindow; | ||
462 | - unsubscribe(); | ||
463 | - subscribe(); | ||
464 | - } | ||
465 | - }); | ||
466 | - } else { | ||
467 | - $scope.$watch(function () { | ||
468 | - return widgetContext.useDashboardTimewindow ? dashboardTimewindow : widget.config.timewindow; | ||
469 | - }, function (newTimewindow, prevTimewindow) { | ||
470 | - if (!angular.equals(newTimewindow, prevTimewindow)) { | ||
471 | - unsubscribe(); | ||
472 | - subscribe(); | ||
473 | - } | ||
474 | - }); | ||
475 | - } | ||
476 | - } | ||
477 | - subscribe(); | ||
478 | - } | ||
479 | - | ||
480 | - function sendCommand(oneWayElseTwoWay, method, params, timeout) { | ||
481 | - if (!$scope.rpcEnabled) { | ||
482 | - return $q.reject(); | ||
483 | - } | ||
484 | - | ||
485 | - if ($scope.rpcRejection && $scope.rpcRejection.status !== 408) { | ||
486 | - $scope.rpcRejection = null; | ||
487 | - $scope.rpcErrorText = null; | ||
488 | - } | ||
489 | - | ||
490 | - var requestBody = { | ||
491 | - method: method, | ||
492 | - params: params | ||
493 | - }; | ||
494 | - | ||
495 | - if (timeout && timeout > 0) { | ||
496 | - requestBody.timeout = timeout; | ||
497 | - } | ||
498 | - | ||
499 | - var deferred = $q.defer(); | ||
500 | - $scope.executingRpcRequest = true; | ||
501 | - if ($scope.widgetEditMode) { | ||
502 | - $timeout(function() { | ||
503 | - $scope.executingRpcRequest = false; | ||
504 | - if (oneWayElseTwoWay) { | ||
505 | - deferred.resolve(); | ||
506 | - } else { | ||
507 | - deferred.resolve(requestBody); | ||
508 | - } | ||
509 | - }, 500); | ||
510 | - } else { | ||
511 | - $scope.executingPromises.push(deferred.promise); | ||
512 | - var targetSendFunction = oneWayElseTwoWay ? deviceService.sendOneWayRpcCommand : deviceService.sendTwoWayRpcCommand; | ||
513 | - targetSendFunction(targetDeviceId, requestBody).then( | ||
514 | - function success(responseBody) { | ||
515 | - $scope.rpcRejection = null; | ||
516 | - $scope.rpcErrorText = null; | ||
517 | - var index = $scope.executingPromises.indexOf(deferred.promise); | ||
518 | - if (index >= 0) { | ||
519 | - $scope.executingPromises.splice( index, 1 ); | ||
520 | - } | ||
521 | - $scope.executingRpcRequest = $scope.executingPromises.length > 0; | ||
522 | - deferred.resolve(responseBody); | ||
523 | - }, | ||
524 | - function fail(rejection) { | ||
525 | - var index = $scope.executingPromises.indexOf(deferred.promise); | ||
526 | - if (index >= 0) { | ||
527 | - $scope.executingPromises.splice( index, 1 ); | ||
528 | - } | ||
529 | - $scope.executingRpcRequest = $scope.executingPromises.length > 0; | ||
530 | - if (!$scope.executingRpcRequest || rejection.status === 408) { | ||
531 | - $scope.rpcRejection = rejection; | ||
532 | - if (rejection.status === 408) { | ||
533 | - $scope.rpcErrorText = 'Device is offline.'; | ||
534 | - } else { | ||
535 | - $scope.rpcErrorText = 'Error : ' + rejection.status + ' - ' + rejection.statusText; | ||
536 | - if (rejection.data && rejection.data.length > 0) { | ||
537 | - $scope.rpcErrorText += '</br>'; | ||
538 | - $scope.rpcErrorText += rejection.data; | ||
539 | - } | ||
540 | - } | ||
541 | - } | ||
542 | - deferred.reject(rejection); | ||
543 | - } | ||
544 | - ); | ||
545 | - } | ||
546 | - return deferred.promise; | ||
547 | - } | ||
548 | - | ||
549 | //TODO: widgets visibility | 552 | //TODO: widgets visibility |
550 | /*function updateVisibility(forceRedraw) { | 553 | /*function updateVisibility(forceRedraw) { |
551 | if (visibleRect) { | 554 | if (visibleRect) { |
@@ -584,285 +587,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | @@ -584,285 +587,6 @@ export default function WidgetController($scope, $timeout, $window, $element, $q | ||
584 | onRedraw(); | 587 | onRedraw(); |
585 | }*/ | 588 | }*/ |
586 | 589 | ||
587 | - function onResetTimewindow() { | ||
588 | - if (widgetContext.useDashboardTimewindow) { | ||
589 | - dashboardTimewindowApi.onResetTimewindow(); | ||
590 | - } else { | ||
591 | - if (originalTimewindow) { | ||
592 | - widget.config.timewindow = angular.copy(originalTimewindow); | ||
593 | - originalTimewindow = null; | ||
594 | - } | ||
595 | - } | ||
596 | - } | ||
597 | - | ||
598 | - function onUpdateTimewindow(startTimeMs, endTimeMs) { | ||
599 | - if (widgetContext.useDashboardTimewindow) { | ||
600 | - dashboardTimewindowApi.onUpdateTimewindow(startTimeMs, endTimeMs); | ||
601 | - } else { | ||
602 | - if (!originalTimewindow) { | ||
603 | - originalTimewindow = angular.copy(widget.config.timewindow); | ||
604 | - } | ||
605 | - widget.config.timewindow = timeService.toHistoryTimewindow(widget.config.timewindow, startTimeMs, endTimeMs); | ||
606 | - } | ||
607 | - } | ||
608 | - | ||
609 | - function dataUpdated(sourceData, datasourceIndex, dataKeyIndex, apply) { | ||
610 | - notifyDataLoaded(); | ||
611 | - var update = true; | ||
612 | - var currentData; | ||
613 | - if ($scope.displayLegend && $scope.legendData.data[datasourceIndex + dataKeyIndex].hidden) { | ||
614 | - currentData = widgetContext.hiddenData[datasourceIndex + dataKeyIndex]; | ||
615 | - } else { | ||
616 | - currentData = widgetContext.data[datasourceIndex + dataKeyIndex]; | ||
617 | - } | ||
618 | - if (widget.type === types.widgetType.latest.value) { | ||
619 | - var prevData = currentData.data; | ||
620 | - if (prevData && prevData[0] && prevData[0].length > 1 && sourceData.data.length > 0) { | ||
621 | - var prevValue = prevData[0][1]; | ||
622 | - if (prevValue === sourceData.data[0][1]) { | ||
623 | - update = false; | ||
624 | - } | ||
625 | - } | ||
626 | - } | ||
627 | - if (update) { | ||
628 | - if (subscriptionTimewindow && subscriptionTimewindow.realtimeWindowMs) { | ||
629 | - updateTimewindow(); | ||
630 | - } | ||
631 | - currentData.data = sourceData.data; | ||
632 | - onDataUpdated(); | ||
633 | - if ($scope.caulculateLegendData) { | ||
634 | - updateLegend(datasourceIndex + dataKeyIndex, sourceData.data, apply); | ||
635 | - } | ||
636 | - } | ||
637 | - if (apply) { | ||
638 | - $scope.$digest(); | ||
639 | - } | ||
640 | - } | ||
641 | - | ||
642 | - function updateLegend(dataIndex, data, apply) { | ||
643 | - var legendKeyData = $scope.legendData.data[dataIndex]; | ||
644 | - if ($scope.legendConfig.showMin) { | ||
645 | - legendKeyData.min = formatValue(calculateMin(data), widgetContext.decimals, widgetContext.units); | ||
646 | - } | ||
647 | - if ($scope.legendConfig.showMax) { | ||
648 | - legendKeyData.max = formatValue(calculateMax(data), widgetContext.decimals, widgetContext.units); | ||
649 | - } | ||
650 | - if ($scope.legendConfig.showAvg) { | ||
651 | - legendKeyData.avg = formatValue(calculateAvg(data), widgetContext.decimals, widgetContext.units); | ||
652 | - } | ||
653 | - if ($scope.legendConfig.showTotal) { | ||
654 | - legendKeyData.total = formatValue(calculateTotal(data), widgetContext.decimals, widgetContext.units); | ||
655 | - } | ||
656 | - $scope.$broadcast('legendDataUpdated', apply !== false); | ||
657 | - } | ||
658 | - | ||
659 | - function isNumeric(val) { | ||
660 | - return (val - parseFloat( val ) + 1) >= 0; | ||
661 | - } | ||
662 | - | ||
663 | - function formatValue(value, dec, units) { | ||
664 | - if (angular.isDefined(value) && | ||
665 | - value !== null && isNumeric(value)) { | ||
666 | - var formatted = value; | ||
667 | - if (angular.isDefined(dec)) { | ||
668 | - formatted = formatted.toFixed(dec); | ||
669 | - } | ||
670 | - formatted = (formatted * 1).toString(); | ||
671 | - if (angular.isDefined(units) && units.length > 0) { | ||
672 | - formatted += ' ' + units; | ||
673 | - } | ||
674 | - return formatted; | ||
675 | - } else { | ||
676 | - return ''; | ||
677 | - } | ||
678 | - } | ||
679 | - | ||
680 | - function calculateMin(data) { | ||
681 | - if (data.length > 0) { | ||
682 | - var result = Number(data[0][1]); | ||
683 | - for (var i=1;i<data.length;i++) { | ||
684 | - result = Math.min(result, Number(data[i][1])); | ||
685 | - } | ||
686 | - return result; | ||
687 | - } else { | ||
688 | - return null; | ||
689 | - } | ||
690 | - } | ||
691 | - | ||
692 | - function calculateMax(data) { | ||
693 | - if (data.length > 0) { | ||
694 | - var result = Number(data[0][1]); | ||
695 | - for (var i=1;i<data.length;i++) { | ||
696 | - result = Math.max(result, Number(data[i][1])); | ||
697 | - } | ||
698 | - return result; | ||
699 | - } else { | ||
700 | - return null; | ||
701 | - } | ||
702 | - } | ||
703 | - | ||
704 | - function calculateAvg(data) { | ||
705 | - if (data.length > 0) { | ||
706 | - return calculateTotal(data)/data.length; | ||
707 | - } else { | ||
708 | - return null; | ||
709 | - } | ||
710 | - } | ||
711 | - | ||
712 | - function calculateTotal(data) { | ||
713 | - if (data.length > 0) { | ||
714 | - var result = 0; | ||
715 | - for (var i = 0; i < data.length; i++) { | ||
716 | - result += Number(data[i][1]); | ||
717 | - } | ||
718 | - return result; | ||
719 | - } else { | ||
720 | - return null; | ||
721 | - } | ||
722 | - } | ||
723 | - | ||
724 | - function checkSubscriptions() { | ||
725 | - if (widget.type !== types.widgetType.rpc.value) { | ||
726 | - var subscriptionsChanged = false; | ||
727 | - for (var i = 0; i < datasourceListeners.length; i++) { | ||
728 | - var listener = datasourceListeners[i]; | ||
729 | - var deviceId = null; | ||
730 | - var aliasName = null; | ||
731 | - if (listener.datasource.type === types.datasourceType.device) { | ||
732 | - if (aliasesInfo.deviceAliases[listener.datasource.deviceAliasId]) { | ||
733 | - deviceId = aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].deviceId; | ||
734 | - aliasName = aliasesInfo.deviceAliases[listener.datasource.deviceAliasId].alias; | ||
735 | - } | ||
736 | - if (!angular.equals(deviceId, listener.deviceId) || | ||
737 | - !angular.equals(aliasName, listener.datasource.name)) { | ||
738 | - subscriptionsChanged = true; | ||
739 | - break; | ||
740 | - } | ||
741 | - } | ||
742 | - } | ||
743 | - if (subscriptionsChanged) { | ||
744 | - unsubscribe(); | ||
745 | - subscribe(); | ||
746 | - } | ||
747 | - } | ||
748 | - } | ||
749 | - | ||
750 | - function unsubscribe() { | ||
751 | - if (widget.type !== types.widgetType.rpc.value) { | ||
752 | - for (var i = 0; i < datasourceListeners.length; i++) { | ||
753 | - var listener = datasourceListeners[i]; | ||
754 | - datasourceService.unsubscribeFromDatasource(listener); | ||
755 | - } | ||
756 | - datasourceListeners = []; | ||
757 | - } | ||
758 | - } | ||
759 | - | ||
760 | - function updateRealtimeSubscription(_subscriptionTimewindow) { | ||
761 | - if (_subscriptionTimewindow) { | ||
762 | - subscriptionTimewindow = _subscriptionTimewindow; | ||
763 | - } else { | ||
764 | - subscriptionTimewindow = | ||
765 | - timeService.createSubscriptionTimewindow( | ||
766 | - widgetContext.useDashboardTimewindow ? dashboardTimewindow : widget.config.timewindow, | ||
767 | - widgetContext.timeWindow.stDiff); | ||
768 | - } | ||
769 | - updateTimewindow(); | ||
770 | - return subscriptionTimewindow; | ||
771 | - } | ||
772 | - | ||
773 | - function hasTimewindow() { | ||
774 | - if (widgetContext.useDashboardTimewindow) { | ||
775 | - return angular.isDefined(dashboardTimewindow); | ||
776 | - } else { | ||
777 | - return angular.isDefined(widget.config.timewindow); | ||
778 | - } | ||
779 | - } | ||
780 | - | ||
781 | - function updateDataKeyLabel(dataKey, deviceName, aliasName) { | ||
782 | - var pattern = dataKey.pattern; | ||
783 | - var label = dataKey.pattern; | ||
784 | - var match = varsRegex.exec(pattern); | ||
785 | - while (match !== null) { | ||
786 | - var variable = match[0]; | ||
787 | - var variableName = match[1]; | ||
788 | - if (variableName === 'deviceName') { | ||
789 | - label = label.split(variable).join(deviceName); | ||
790 | - } else if (variableName === 'aliasName') { | ||
791 | - label = label.split(variable).join(aliasName); | ||
792 | - } | ||
793 | - match = varsRegex.exec(pattern); | ||
794 | - } | ||
795 | - dataKey.label = label; | ||
796 | - } | ||
797 | - | ||
798 | - function subscribe() { | ||
799 | - if (widget.type !== types.widgetType.rpc.value && widget.type !== types.widgetType.static.value) { | ||
800 | - notifyDataLoading(); | ||
801 | - if (widget.type === types.widgetType.timeseries.value && | ||
802 | - hasTimewindow()) { | ||
803 | - updateRealtimeSubscription(); | ||
804 | - if (subscriptionTimewindow.fixedWindow) { | ||
805 | - onDataUpdated(); | ||
806 | - } | ||
807 | - } | ||
808 | - var index = 0; | ||
809 | - for (var i = 0; i < widgetContext.datasources.length; i++) { | ||
810 | - var datasource = widgetContext.datasources[i]; | ||
811 | - if (angular.isFunction(datasource)) | ||
812 | - continue; | ||
813 | - var deviceId = null; | ||
814 | - if (datasource.type === types.datasourceType.device && datasource.deviceAliasId) { | ||
815 | - if (aliasesInfo.deviceAliases[datasource.deviceAliasId]) { | ||
816 | - deviceId = aliasesInfo.deviceAliases[datasource.deviceAliasId].deviceId; | ||
817 | - datasource.name = aliasesInfo.deviceAliases[datasource.deviceAliasId].alias; | ||
818 | - var aliasName = aliasesInfo.deviceAliases[datasource.deviceAliasId].alias; | ||
819 | - var deviceName = ''; | ||
820 | - var devicesInfo = aliasesInfo.deviceAliasesInfo[datasource.deviceAliasId]; | ||
821 | - for (var d=0;d<devicesInfo.length;d++) { | ||
822 | - if (devicesInfo[d].id === deviceId) { | ||
823 | - deviceName = devicesInfo[d].name; | ||
824 | - break; | ||
825 | - } | ||
826 | - } | ||
827 | - for (var dk = 0; dk < datasource.dataKeys.length; dk++) { | ||
828 | - updateDataKeyLabel(datasource.dataKeys[dk], deviceName, aliasName); | ||
829 | - } | ||
830 | - } | ||
831 | - } else { | ||
832 | - datasource.name = types.datasourceType.function; | ||
833 | - } | ||
834 | - var listener = { | ||
835 | - widget: widget, | ||
836 | - subscriptionTimewindow: subscriptionTimewindow, | ||
837 | - datasource: datasource, | ||
838 | - deviceId: deviceId, | ||
839 | - dataUpdated: function (data, datasourceIndex, dataKeyIndex, apply) { | ||
840 | - dataUpdated(data, datasourceIndex, dataKeyIndex, apply); | ||
841 | - }, | ||
842 | - updateRealtimeSubscription: function() { | ||
843 | - this.subscriptionTimewindow = updateRealtimeSubscription(); | ||
844 | - return this.subscriptionTimewindow; | ||
845 | - }, | ||
846 | - setRealtimeSubscription: function(subscriptionTimewindow) { | ||
847 | - updateRealtimeSubscription(angular.copy(subscriptionTimewindow)); | ||
848 | - }, | ||
849 | - datasourceIndex: index | ||
850 | - }; | ||
851 | - | ||
852 | - for (var a = 0; a < datasource.dataKeys.length; a++) { | ||
853 | - widgetContext.data[index + a].data = []; | ||
854 | - } | ||
855 | - | ||
856 | - index += datasource.dataKeys.length; | ||
857 | - | ||
858 | - datasourceListeners.push(listener); | ||
859 | - datasourceService.subscribeToDatasource(listener); | ||
860 | - } | ||
861 | - } else { | ||
862 | - notifyDataLoaded(); | ||
863 | - } | ||
864 | - } | ||
865 | - | ||
866 | } | 590 | } |
867 | 591 | ||
868 | /* eslint-enable angular/angularelement */ | 592 | /* eslint-enable angular/angularelement */ |
@@ -15,4 +15,4 @@ | @@ -15,4 +15,4 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div class="tb-uppercase">{{ item | contactShort }}</div> | ||
18 | +<div ng-show="item && (!item.additionalInfo || !item.additionalInfo.isPublic)" class="tb-uppercase">{{ item | contactShort }}</div> |
@@ -15,13 +15,13 @@ | @@ -15,13 +15,13 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<md-button ng-click="onManageUsers({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-users' | translate }}</md-button> | 18 | +<md-button ng-click="onManageUsers({event: $event})" ng-show="!isEdit && !isPublic" class="md-raised md-primary">{{ 'customer.manage-users' | translate }}</md-button> |
19 | <md-button ng-click="onManageDevices({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-devices' | translate }}</md-button> | 19 | <md-button ng-click="onManageDevices({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-devices' | translate }}</md-button> |
20 | <md-button ng-click="onManageDashboards({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-dashboards' | translate }}</md-button> | 20 | <md-button ng-click="onManageDashboards({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.manage-dashboards' | translate }}</md-button> |
21 | -<md-button ng-click="onDeleteCustomer({event: $event})" ng-show="!isEdit" class="md-raised md-primary">{{ 'customer.delete' | translate }}</md-button> | 21 | +<md-button ng-click="onDeleteCustomer({event: $event})" ng-show="!isEdit && !isPublic" class="md-raised md-primary">{{ 'customer.delete' | translate }}</md-button> |
22 | 22 | ||
23 | <md-content class="md-padding" layout="column"> | 23 | <md-content class="md-padding" layout="column"> |
24 | - <fieldset ng-disabled="loading || !isEdit"> | 24 | + <fieldset ng-show="!isPublic" ng-disabled="loading || !isEdit"> |
25 | <md-input-container class="md-block"> | 25 | <md-input-container class="md-block"> |
26 | <label translate>customer.title</label> | 26 | <label translate>customer.title</label> |
27 | <input required name="title" ng-model="customer.title"> | 27 | <input required name="title" ng-model="customer.title"> |
@@ -30,14 +30,23 @@ export default function CustomerController(customerService, $state, $stateParams | @@ -30,14 +30,23 @@ export default function CustomerController(customerService, $state, $stateParams | ||
30 | }, | 30 | }, |
31 | name: function() { return $translate.instant('user.users') }, | 31 | name: function() { return $translate.instant('user.users') }, |
32 | details: function() { return $translate.instant('customer.manage-customer-users') }, | 32 | details: function() { return $translate.instant('customer.manage-customer-users') }, |
33 | - icon: "account_circle" | 33 | + icon: "account_circle", |
34 | + isEnabled: function(customer) { | ||
35 | + return customer && (!customer.additionalInfo || !customer.additionalInfo.isPublic); | ||
36 | + } | ||
34 | }, | 37 | }, |
35 | { | 38 | { |
36 | onAction: function ($event, item) { | 39 | onAction: function ($event, item) { |
37 | openCustomerDevices($event, item); | 40 | openCustomerDevices($event, item); |
38 | }, | 41 | }, |
39 | name: function() { return $translate.instant('device.devices') }, | 42 | name: function() { return $translate.instant('device.devices') }, |
40 | - details: function() { return $translate.instant('customer.manage-customer-devices') }, | 43 | + details: function(customer) { |
44 | + if (customer && customer.additionalInfo && customer.additionalInfo.isPublic) { | ||
45 | + return $translate.instant('customer.manage-public-devices') | ||
46 | + } else { | ||
47 | + return $translate.instant('customer.manage-customer-devices') | ||
48 | + } | ||
49 | + }, | ||
41 | icon: "devices_other" | 50 | icon: "devices_other" |
42 | }, | 51 | }, |
43 | { | 52 | { |
@@ -45,7 +54,13 @@ export default function CustomerController(customerService, $state, $stateParams | @@ -45,7 +54,13 @@ export default function CustomerController(customerService, $state, $stateParams | ||
45 | openCustomerDashboards($event, item); | 54 | openCustomerDashboards($event, item); |
46 | }, | 55 | }, |
47 | name: function() { return $translate.instant('dashboard.dashboards') }, | 56 | name: function() { return $translate.instant('dashboard.dashboards') }, |
48 | - details: function() { return $translate.instant('customer.manage-customer-dashboards') }, | 57 | + details: function(customer) { |
58 | + if (customer && customer.additionalInfo && customer.additionalInfo.isPublic) { | ||
59 | + return $translate.instant('customer.manage-public-dashboards') | ||
60 | + } else { | ||
61 | + return $translate.instant('customer.manage-customer-dashboards') | ||
62 | + } | ||
63 | + }, | ||
49 | icon: "dashboard" | 64 | icon: "dashboard" |
50 | }, | 65 | }, |
51 | { | 66 | { |
@@ -54,7 +69,10 @@ export default function CustomerController(customerService, $state, $stateParams | @@ -54,7 +69,10 @@ export default function CustomerController(customerService, $state, $stateParams | ||
54 | }, | 69 | }, |
55 | name: function() { return $translate.instant('action.delete') }, | 70 | name: function() { return $translate.instant('action.delete') }, |
56 | details: function() { return $translate.instant('customer.delete') }, | 71 | details: function() { return $translate.instant('customer.delete') }, |
57 | - icon: "delete" | 72 | + icon: "delete", |
73 | + isEnabled: function(customer) { | ||
74 | + return customer && (!customer.additionalInfo || !customer.additionalInfo.isPublic); | ||
75 | + } | ||
58 | } | 76 | } |
59 | ]; | 77 | ]; |
60 | 78 | ||
@@ -86,7 +104,19 @@ export default function CustomerController(customerService, $state, $stateParams | @@ -86,7 +104,19 @@ export default function CustomerController(customerService, $state, $stateParams | ||
86 | 104 | ||
87 | addItemText: function() { return $translate.instant('customer.add-customer-text') }, | 105 | addItemText: function() { return $translate.instant('customer.add-customer-text') }, |
88 | noItemsText: function() { return $translate.instant('customer.no-customers-text') }, | 106 | noItemsText: function() { return $translate.instant('customer.no-customers-text') }, |
89 | - itemDetailsText: function() { return $translate.instant('customer.customer-details') } | 107 | + itemDetailsText: function(customer) { |
108 | + if (customer && (!customer.additionalInfo || !customer.additionalInfo.isPublic)) { | ||
109 | + return $translate.instant('customer.customer-details') | ||
110 | + } else { | ||
111 | + return ''; | ||
112 | + } | ||
113 | + }, | ||
114 | + isSelectionEnabled: function (customer) { | ||
115 | + return customer && (!customer.additionalInfo || !customer.additionalInfo.isPublic); | ||
116 | + }, | ||
117 | + isDetailsReadOnly: function (customer) { | ||
118 | + return customer && customer.additionalInfo && customer.additionalInfo.isPublic; | ||
119 | + } | ||
90 | }; | 120 | }; |
91 | 121 | ||
92 | if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { | 122 | if (angular.isDefined($stateParams.items) && $stateParams.items !== null) { |
@@ -24,7 +24,21 @@ export default function CustomerDirective($compile, $templateCache) { | @@ -24,7 +24,21 @@ export default function CustomerDirective($compile, $templateCache) { | ||
24 | var linker = function (scope, element) { | 24 | var linker = function (scope, element) { |
25 | var template = $templateCache.get(customerFieldsetTemplate); | 25 | var template = $templateCache.get(customerFieldsetTemplate); |
26 | element.html(template); | 26 | element.html(template); |
27 | + | ||
28 | + scope.isPublic = false; | ||
29 | + | ||
30 | + scope.$watch('customer', function(newVal) { | ||
31 | + if (newVal) { | ||
32 | + if (scope.customer.additionalInfo) { | ||
33 | + scope.isPublic = scope.customer.additionalInfo.isPublic; | ||
34 | + } else { | ||
35 | + scope.isPublic = false; | ||
36 | + } | ||
37 | + } | ||
38 | + }); | ||
39 | + | ||
27 | $compile(element.contents())(scope); | 40 | $compile(element.contents())(scope); |
41 | + | ||
28 | } | 42 | } |
29 | return { | 43 | return { |
30 | restrict: "E", | 44 | restrict: "E", |
@@ -15,5 +15,6 @@ | @@ -15,5 +15,6 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'dashboard.assignedToCustomer' | translate}} '{{vm.customerTitle}}'</div> | 18 | +<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'dashboard.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> |
19 | +<div class="tb-small" ng-show="vm.isPublic()">{{'dashboard.public' | translate}}</div> | ||
19 | 20 |
@@ -15,25 +15,42 @@ | @@ -15,25 +15,42 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | +<md-button ng-click="onExportDashboard({event: $event})" | ||
19 | + ng-show="!isEdit && dashboardScope === 'tenant'" | ||
20 | + class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button> | ||
21 | +<md-button ng-click="onMakePublic({event: $event})" | ||
22 | + ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer && !isPublic" | ||
23 | + class="md-raised md-primary">{{ 'dashboard.make-public' | translate }}</md-button> | ||
18 | <md-button ng-click="onAssignToCustomer({event: $event})" | 24 | <md-button ng-click="onAssignToCustomer({event: $event})" |
19 | ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer" | 25 | ng-show="!isEdit && dashboardScope === 'tenant' && !isAssignedToCustomer" |
20 | class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button> | 26 | class="md-raised md-primary">{{ 'dashboard.assign-to-customer' | translate }}</md-button> |
21 | -<md-button ng-click="onUnassignFromCustomer({event: $event})" | 27 | +<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})" |
22 | ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer" | 28 | ng-show="!isEdit && (dashboardScope === 'customer' || dashboardScope === 'tenant') && isAssignedToCustomer" |
23 | - class="md-raised md-primary">{{ 'dashboard.unassign-from-customer' | translate }}</md-button> | ||
24 | -<md-button ng-click="onExportDashboard({event: $event})" | ||
25 | - ng-show="!isEdit && dashboardScope === 'tenant'" | ||
26 | - class="md-raised md-primary">{{ 'dashboard.export' | translate }}</md-button> | 29 | + class="md-raised md-primary">{{ isPublic ? 'dashboard.make-private' : 'dashboard.unassign-from-customer' | translate }}</md-button> |
27 | <md-button ng-click="onDeleteDashboard({event: $event})" | 30 | <md-button ng-click="onDeleteDashboard({event: $event})" |
28 | ng-show="!isEdit && dashboardScope === 'tenant'" | 31 | ng-show="!isEdit && dashboardScope === 'tenant'" |
29 | class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button> | 32 | class="md-raised md-primary">{{ 'dashboard.delete' | translate }}</md-button> |
30 | - | ||
31 | <md-content class="md-padding" layout="column"> | 33 | <md-content class="md-padding" layout="column"> |
32 | <md-input-container class="md-block" | 34 | <md-input-container class="md-block" |
33 | - ng-show="isAssignedToCustomer && dashboardScope === 'tenant'"> | 35 | + ng-show="!isEdit && isAssignedToCustomer && !isPublic && dashboardScope === 'tenant'"> |
34 | <label translate>dashboard.assignedToCustomer</label> | 36 | <label translate>dashboard.assignedToCustomer</label> |
35 | <input ng-model="assignedCustomer.title" disabled> | 37 | <input ng-model="assignedCustomer.title" disabled> |
36 | </md-input-container> | 38 | </md-input-container> |
39 | + <div layout="row" ng-show="!isEdit && isPublic && (dashboardScope === 'customer' || dashboardScope === 'tenant')"> | ||
40 | + <md-input-container class="md-block" flex> | ||
41 | + <label translate>dashboard.public-link</label> | ||
42 | + <input ng-model="publicLink" disabled> | ||
43 | + </md-input-container> | ||
44 | + <md-button class="md-icon-button" style="margin-top: 14px;" | ||
45 | + ngclipboard | ||
46 | + data-clipboard-text="{{ publicLink }}" | ||
47 | + ngclipboard-success="onPublicLinkCopied(e)"> | ||
48 | + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> | ||
49 | + <md-tooltip md-direction="top"> | ||
50 | + {{ 'dashboard.copy-public-link' | translate }} | ||
51 | + </md-tooltip> | ||
52 | + </md-button> | ||
53 | + </div> | ||
37 | <fieldset ng-disabled="loading || !isEdit"> | 54 | <fieldset ng-disabled="loading || !isEdit"> |
38 | <md-input-container class="md-block"> | 55 | <md-input-container class="md-block"> |
39 | <label translate>dashboard.title</label> | 56 | <label translate>dashboard.title</label> |
@@ -86,6 +86,7 @@ export default function DashboardController(types, widgetService, userService, | @@ -86,6 +86,7 @@ export default function DashboardController(types, widgetService, userService, | ||
86 | vm.exportDashboard = exportDashboard; | 86 | vm.exportDashboard = exportDashboard; |
87 | vm.exportWidget = exportWidget; | 87 | vm.exportWidget = exportWidget; |
88 | vm.importWidget = importWidget; | 88 | vm.importWidget = importWidget; |
89 | + vm.isPublicUser = isPublicUser; | ||
89 | vm.isTenantAdmin = isTenantAdmin; | 90 | vm.isTenantAdmin = isTenantAdmin; |
90 | vm.isSystemAdmin = isSystemAdmin; | 91 | vm.isSystemAdmin = isSystemAdmin; |
91 | vm.loadDashboard = loadDashboard; | 92 | vm.loadDashboard = loadDashboard; |
@@ -273,6 +274,10 @@ export default function DashboardController(types, widgetService, userService, | @@ -273,6 +274,10 @@ export default function DashboardController(types, widgetService, userService, | ||
273 | vm.dashboardInitComplete = true; | 274 | vm.dashboardInitComplete = true; |
274 | } | 275 | } |
275 | 276 | ||
277 | + function isPublicUser() { | ||
278 | + return vm.user.isPublic === true; | ||
279 | + } | ||
280 | + | ||
276 | function isTenantAdmin() { | 281 | function isTenantAdmin() { |
277 | return vm.user.authority === 'TENANT_ADMIN'; | 282 | return vm.user.authority === 'TENANT_ADMIN'; |
278 | } | 283 | } |
@@ -617,22 +622,8 @@ export default function DashboardController(types, widgetService, userService, | @@ -617,22 +622,8 @@ export default function DashboardController(types, widgetService, userService, | ||
617 | sizeY: widgetTypeInfo.sizeY, | 622 | sizeY: widgetTypeInfo.sizeY, |
618 | config: config | 623 | config: config |
619 | }; | 624 | }; |
620 | - $mdDialog.show({ | ||
621 | - controller: 'AddWidgetController', | ||
622 | - controllerAs: 'vm', | ||
623 | - templateUrl: addWidgetTemplate, | ||
624 | - locals: {dashboard: vm.dashboard, aliasesInfo: vm.aliasesInfo, widget: newWidget, widgetInfo: widgetTypeInfo}, | ||
625 | - parent: angular.element($document[0].body), | ||
626 | - fullscreen: true, | ||
627 | - skipHide: true, | ||
628 | - targetEvent: event, | ||
629 | - onComplete: function () { | ||
630 | - var w = angular.element($window); | ||
631 | - w.triggerHandler('resize'); | ||
632 | - } | ||
633 | - }).then(function (result) { | ||
634 | - var widget = result.widget; | ||
635 | - vm.aliasesInfo = result.aliasesInfo; | 625 | + |
626 | + function addWidget(widget) { | ||
636 | var columns = 24; | 627 | var columns = 24; |
637 | if (vm.dashboard.configuration.gridSettings && vm.dashboard.configuration.gridSettings.columns) { | 628 | if (vm.dashboard.configuration.gridSettings && vm.dashboard.configuration.gridSettings.columns) { |
638 | columns = vm.dashboard.configuration.gridSettings.columns; | 629 | columns = vm.dashboard.configuration.gridSettings.columns; |
@@ -643,9 +634,37 @@ export default function DashboardController(types, widgetService, userService, | @@ -643,9 +634,37 @@ export default function DashboardController(types, widgetService, userService, | ||
643 | widget.sizeY *= ratio; | 634 | widget.sizeY *= ratio; |
644 | } | 635 | } |
645 | vm.widgets.push(widget); | 636 | vm.widgets.push(widget); |
646 | - }, function (rejection) { | ||
647 | - vm.aliasesInfo = rejection.aliasesInfo; | ||
648 | - }); | 637 | + } |
638 | + | ||
639 | + if (widgetTypeInfo.useCustomDatasources) { | ||
640 | + addWidget(newWidget); | ||
641 | + } else { | ||
642 | + $mdDialog.show({ | ||
643 | + controller: 'AddWidgetController', | ||
644 | + controllerAs: 'vm', | ||
645 | + templateUrl: addWidgetTemplate, | ||
646 | + locals: { | ||
647 | + dashboard: vm.dashboard, | ||
648 | + aliasesInfo: vm.aliasesInfo, | ||
649 | + widget: newWidget, | ||
650 | + widgetInfo: widgetTypeInfo | ||
651 | + }, | ||
652 | + parent: angular.element($document[0].body), | ||
653 | + fullscreen: true, | ||
654 | + skipHide: true, | ||
655 | + targetEvent: event, | ||
656 | + onComplete: function () { | ||
657 | + var w = angular.element($window); | ||
658 | + w.triggerHandler('resize'); | ||
659 | + } | ||
660 | + }).then(function (result) { | ||
661 | + var widget = result.widget; | ||
662 | + vm.aliasesInfo = result.aliasesInfo; | ||
663 | + addWidget(widget); | ||
664 | + }, function (rejection) { | ||
665 | + vm.aliasesInfo = rejection.aliasesInfo; | ||
666 | + }); | ||
667 | + } | ||
649 | } | 668 | } |
650 | ); | 669 | ); |
651 | } | 670 | } |
@@ -20,30 +20,44 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html'; | @@ -20,30 +20,44 @@ import dashboardFieldsetTemplate from './dashboard-fieldset.tpl.html'; | ||
20 | /* eslint-enable import/no-unresolved, import/default */ | 20 | /* eslint-enable import/no-unresolved, import/default */ |
21 | 21 | ||
22 | /*@ngInject*/ | 22 | /*@ngInject*/ |
23 | -export default function DashboardDirective($compile, $templateCache, types, customerService) { | 23 | +export default function DashboardDirective($compile, $templateCache, $translate, types, toast, customerService, dashboardService) { |
24 | var linker = function (scope, element) { | 24 | var linker = function (scope, element) { |
25 | var template = $templateCache.get(dashboardFieldsetTemplate); | 25 | var template = $templateCache.get(dashboardFieldsetTemplate); |
26 | element.html(template); | 26 | element.html(template); |
27 | 27 | ||
28 | scope.isAssignedToCustomer = false; | 28 | scope.isAssignedToCustomer = false; |
29 | + scope.isPublic = false; | ||
29 | scope.assignedCustomer = null; | 30 | scope.assignedCustomer = null; |
31 | + scope.publicLink = null; | ||
30 | 32 | ||
31 | scope.$watch('dashboard', function(newVal) { | 33 | scope.$watch('dashboard', function(newVal) { |
32 | if (newVal) { | 34 | if (newVal) { |
33 | if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) { | 35 | if (scope.dashboard.customerId && scope.dashboard.customerId.id !== types.id.nullUid) { |
34 | scope.isAssignedToCustomer = true; | 36 | scope.isAssignedToCustomer = true; |
35 | - customerService.getCustomer(scope.dashboard.customerId.id).then( | 37 | + customerService.getShortCustomerInfo(scope.dashboard.customerId.id).then( |
36 | function success(customer) { | 38 | function success(customer) { |
37 | scope.assignedCustomer = customer; | 39 | scope.assignedCustomer = customer; |
40 | + scope.isPublic = customer.isPublic; | ||
41 | + if (scope.isPublic) { | ||
42 | + scope.publicLink = dashboardService.getPublicDashboardLink(scope.dashboard); | ||
43 | + } else { | ||
44 | + scope.publicLink = null; | ||
45 | + } | ||
38 | } | 46 | } |
39 | ); | 47 | ); |
40 | } else { | 48 | } else { |
41 | scope.isAssignedToCustomer = false; | 49 | scope.isAssignedToCustomer = false; |
50 | + scope.isPublic = false; | ||
51 | + scope.publicLink = null; | ||
42 | scope.assignedCustomer = null; | 52 | scope.assignedCustomer = null; |
43 | } | 53 | } |
44 | } | 54 | } |
45 | }); | 55 | }); |
46 | 56 | ||
57 | + scope.onPublicLinkCopied = function() { | ||
58 | + toast.showSuccess($translate.instant('dashboard.public-link-copied-message'), 750, angular.element(element).parent().parent(), 'bottom left'); | ||
59 | + }; | ||
60 | + | ||
47 | $compile(element.contents())(scope); | 61 | $compile(element.contents())(scope); |
48 | } | 62 | } |
49 | return { | 63 | return { |
@@ -55,6 +69,7 @@ export default function DashboardDirective($compile, $templateCache, types, cust | @@ -55,6 +69,7 @@ export default function DashboardDirective($compile, $templateCache, types, cust | ||
55 | dashboardScope: '=', | 69 | dashboardScope: '=', |
56 | theForm: '=', | 70 | theForm: '=', |
57 | onAssignToCustomer: '&', | 71 | onAssignToCustomer: '&', |
72 | + onMakePublic: '&', | ||
58 | onUnassignFromCustomer: '&', | 73 | onUnassignFromCustomer: '&', |
59 | onExportDashboard: '&', | 74 | onExportDashboard: '&', |
60 | onDeleteDashboard: '&' | 75 | onDeleteDashboard: '&' |
@@ -62,7 +62,7 @@ export default function DashboardRoutes($stateProvider) { | @@ -62,7 +62,7 @@ export default function DashboardRoutes($stateProvider) { | ||
62 | pageTitle: 'customer.dashboards' | 62 | pageTitle: 'customer.dashboards' |
63 | }, | 63 | }, |
64 | ncyBreadcrumb: { | 64 | ncyBreadcrumb: { |
65 | - label: '{"icon": "dashboard", "label": "customer.dashboards"}' | 65 | + label: '{"icon": "dashboard", "label": "{{ vm.customerDashboardsTitle }}", "translate": "false"}' |
66 | } | 66 | } |
67 | }) | 67 | }) |
68 | .state('home.dashboards.dashboard', { | 68 | .state('home.dashboards.dashboard', { |
@@ -47,7 +47,7 @@ | @@ -47,7 +47,7 @@ | ||
47 | aria-label="{{ 'fullscreen.fullscreen' | translate }}" | 47 | aria-label="{{ 'fullscreen.fullscreen' | translate }}" |
48 | class="md-icon-button"> | 48 | class="md-icon-button"> |
49 | </md-button> | 49 | </md-button> |
50 | - <tb-user-menu ng-show="forceFullscreen" display-user-info="true"> | 50 | + <tb-user-menu ng-if="!vm.isPublicUser() && forceFullscreen" display-user-info="true"> |
51 | </tb-user-menu> | 51 | </tb-user-menu> |
52 | <md-button aria-label="{{ 'action.export' | translate }}" class="md-icon-button" | 52 | <md-button aria-label="{{ 'action.export' | translate }}" class="md-icon-button" |
53 | ng-click="vm.exportDashboard($event)"> | 53 | ng-click="vm.exportDashboard($event)"> |
@@ -19,11 +19,33 @@ import addDashboardTemplate from './add-dashboard.tpl.html'; | @@ -19,11 +19,33 @@ import addDashboardTemplate from './add-dashboard.tpl.html'; | ||
19 | import dashboardCard from './dashboard-card.tpl.html'; | 19 | import dashboardCard from './dashboard-card.tpl.html'; |
20 | import assignToCustomerTemplate from './assign-to-customer.tpl.html'; | 20 | import assignToCustomerTemplate from './assign-to-customer.tpl.html'; |
21 | import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html'; | 21 | import addDashboardsToCustomerTemplate from './add-dashboards-to-customer.tpl.html'; |
22 | +import makeDashboardPublicDialogTemplate from './make-dashboard-public-dialog.tpl.html'; | ||
22 | 23 | ||
23 | /* eslint-enable import/no-unresolved, import/default */ | 24 | /* eslint-enable import/no-unresolved, import/default */ |
24 | 25 | ||
25 | /*@ngInject*/ | 26 | /*@ngInject*/ |
26 | -export function DashboardCardController($scope, types, customerService) { | 27 | +export function MakeDashboardPublicDialogController($mdDialog, $translate, toast, dashboardService, dashboard) { |
28 | + | ||
29 | + var vm = this; | ||
30 | + | ||
31 | + vm.dashboard = dashboard; | ||
32 | + vm.publicLink = dashboardService.getPublicDashboardLink(dashboard); | ||
33 | + | ||
34 | + vm.onPublicLinkCopied = onPublicLinkCopied; | ||
35 | + vm.close = close; | ||
36 | + | ||
37 | + function onPublicLinkCopied(){ | ||
38 | + toast.showSuccess($translate.instant('dashboard.public-link-copied-message'), 750, angular.element('#make-dialog-public-content'), 'bottom left'); | ||
39 | + } | ||
40 | + | ||
41 | + function close() { | ||
42 | + $mdDialog.hide(); | ||
43 | + } | ||
44 | + | ||
45 | +} | ||
46 | + | ||
47 | +/*@ngInject*/ | ||
48 | +export function DashboardCardController(types) { | ||
27 | 49 | ||
28 | var vm = this; | 50 | var vm = this; |
29 | 51 | ||
@@ -31,27 +53,22 @@ export function DashboardCardController($scope, types, customerService) { | @@ -31,27 +53,22 @@ export function DashboardCardController($scope, types, customerService) { | ||
31 | 53 | ||
32 | vm.isAssignedToCustomer = function() { | 54 | vm.isAssignedToCustomer = function() { |
33 | if (vm.item && vm.item.customerId && vm.parentCtl.dashboardsScope === 'tenant' && | 55 | if (vm.item && vm.item.customerId && vm.parentCtl.dashboardsScope === 'tenant' && |
34 | - vm.item.customerId.id != vm.types.id.nullUid) { | 56 | + vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) { |
35 | return true; | 57 | return true; |
36 | } | 58 | } |
37 | return false; | 59 | return false; |
38 | } | 60 | } |
39 | 61 | ||
40 | - $scope.$watch('vm.item', | ||
41 | - function() { | ||
42 | - if (vm.isAssignedToCustomer()) { | ||
43 | - customerService.getCustomerTitle(vm.item.customerId.id).then( | ||
44 | - function success(title) { | ||
45 | - vm.customerTitle = title; | ||
46 | - } | ||
47 | - ); | ||
48 | - } | 62 | + vm.isPublic = function() { |
63 | + if (vm.item && vm.item.assignedCustomer && vm.parentCtl.dashboardsScope === 'tenant' && vm.item.assignedCustomer.isPublic) { | ||
64 | + return true; | ||
49 | } | 65 | } |
50 | - ); | 66 | + return false; |
67 | + } | ||
51 | } | 68 | } |
52 | 69 | ||
53 | /*@ngInject*/ | 70 | /*@ngInject*/ |
54 | -export function DashboardsController(userService, dashboardService, customerService, importExport, types, $scope, $controller, | 71 | +export function DashboardsController(userService, dashboardService, customerService, importExport, types, |
55 | $state, $stateParams, $mdDialog, $document, $q, $translate) { | 72 | $state, $stateParams, $mdDialog, $document, $q, $translate) { |
56 | 73 | ||
57 | var customerId = $stateParams.customerId; | 74 | var customerId = $stateParams.customerId; |
@@ -119,6 +136,7 @@ export function DashboardsController(userService, dashboardService, customerServ | @@ -119,6 +136,7 @@ export function DashboardsController(userService, dashboardService, customerServ | ||
119 | vm.dashboardsScope = $state.$current.data.dashboardsType; | 136 | vm.dashboardsScope = $state.$current.data.dashboardsType; |
120 | 137 | ||
121 | vm.assignToCustomer = assignToCustomer; | 138 | vm.assignToCustomer = assignToCustomer; |
139 | + vm.makePublic = makePublic; | ||
122 | vm.unassignFromCustomer = unassignFromCustomer; | 140 | vm.unassignFromCustomer = unassignFromCustomer; |
123 | vm.exportDashboard = exportDashboard; | 141 | vm.exportDashboard = exportDashboard; |
124 | 142 | ||
@@ -136,6 +154,17 @@ export function DashboardsController(userService, dashboardService, customerServ | @@ -136,6 +154,17 @@ export function DashboardsController(userService, dashboardService, customerServ | ||
136 | customerId = user.customerId; | 154 | customerId = user.customerId; |
137 | } | 155 | } |
138 | 156 | ||
157 | + if (customerId) { | ||
158 | + vm.customerDashboardsTitle = $translate.instant('customer.dashboards'); | ||
159 | + customerService.getShortCustomerInfo(customerId).then( | ||
160 | + function success(info) { | ||
161 | + if (info.isPublic) { | ||
162 | + vm.customerDashboardsTitle = $translate.instant('customer.public-dashboards'); | ||
163 | + } | ||
164 | + } | ||
165 | + ); | ||
166 | + } | ||
167 | + | ||
139 | if (vm.dashboardsScope === 'tenant') { | 168 | if (vm.dashboardsScope === 'tenant') { |
140 | fetchDashboardsFunction = function (pageLink) { | 169 | fetchDashboardsFunction = function (pageLink) { |
141 | return dashboardService.getTenantDashboards(pageLink); | 170 | return dashboardService.getTenantDashboards(pageLink); |
@@ -155,8 +184,21 @@ export function DashboardsController(userService, dashboardService, customerServ | @@ -155,8 +184,21 @@ export function DashboardsController(userService, dashboardService, customerServ | ||
155 | name: function() { $translate.instant('action.export') }, | 184 | name: function() { $translate.instant('action.export') }, |
156 | details: function() { return $translate.instant('dashboard.export') }, | 185 | details: function() { return $translate.instant('dashboard.export') }, |
157 | icon: "file_download" | 186 | icon: "file_download" |
158 | - }, | ||
159 | - { | 187 | + }); |
188 | + | ||
189 | + dashboardActionsList.push({ | ||
190 | + onAction: function ($event, item) { | ||
191 | + makePublic($event, item); | ||
192 | + }, | ||
193 | + name: function() { return $translate.instant('action.share') }, | ||
194 | + details: function() { return $translate.instant('dashboard.make-public') }, | ||
195 | + icon: "share", | ||
196 | + isEnabled: function(dashboard) { | ||
197 | + return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); | ||
198 | + } | ||
199 | + }); | ||
200 | + | ||
201 | + dashboardActionsList.push({ | ||
160 | onAction: function ($event, item) { | 202 | onAction: function ($event, item) { |
161 | assignToCustomer($event, [ item.id.id ]); | 203 | assignToCustomer($event, [ item.id.id ]); |
162 | }, | 204 | }, |
@@ -166,19 +208,29 @@ export function DashboardsController(userService, dashboardService, customerServ | @@ -166,19 +208,29 @@ export function DashboardsController(userService, dashboardService, customerServ | ||
166 | isEnabled: function(dashboard) { | 208 | isEnabled: function(dashboard) { |
167 | return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); | 209 | return dashboard && (!dashboard.customerId || dashboard.customerId.id === types.id.nullUid); |
168 | } | 210 | } |
169 | - }, | ||
170 | - { | 211 | + }); |
212 | + dashboardActionsList.push({ | ||
171 | onAction: function ($event, item) { | 213 | onAction: function ($event, item) { |
172 | - unassignFromCustomer($event, item); | 214 | + unassignFromCustomer($event, item, false); |
173 | }, | 215 | }, |
174 | name: function() { return $translate.instant('action.unassign') }, | 216 | name: function() { return $translate.instant('action.unassign') }, |
175 | details: function() { return $translate.instant('dashboard.unassign-from-customer') }, | 217 | details: function() { return $translate.instant('dashboard.unassign-from-customer') }, |
176 | icon: "assignment_return", | 218 | icon: "assignment_return", |
177 | isEnabled: function(dashboard) { | 219 | isEnabled: function(dashboard) { |
178 | - return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid; | 220 | + return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && !dashboard.assignedCustomer.isPublic; |
179 | } | 221 | } |
180 | - } | ||
181 | - ); | 222 | + }); |
223 | + dashboardActionsList.push({ | ||
224 | + onAction: function ($event, item) { | ||
225 | + unassignFromCustomer($event, item, true); | ||
226 | + }, | ||
227 | + name: function() { return $translate.instant('action.unshare') }, | ||
228 | + details: function() { return $translate.instant('dashboard.make-private') }, | ||
229 | + icon: "reply", | ||
230 | + isEnabled: function(dashboard) { | ||
231 | + return dashboard && dashboard.customerId && dashboard.customerId.id !== types.id.nullUid && dashboard.assignedCustomer.isPublic; | ||
232 | + } | ||
233 | + }); | ||
182 | 234 | ||
183 | dashboardActionsList.push( | 235 | dashboardActionsList.push( |
184 | { | 236 | { |
@@ -262,11 +314,27 @@ export function DashboardsController(userService, dashboardService, customerServ | @@ -262,11 +314,27 @@ export function DashboardsController(userService, dashboardService, customerServ | ||
262 | dashboardActionsList.push( | 314 | dashboardActionsList.push( |
263 | { | 315 | { |
264 | onAction: function ($event, item) { | 316 | onAction: function ($event, item) { |
265 | - unassignFromCustomer($event, item); | 317 | + unassignFromCustomer($event, item, false); |
266 | }, | 318 | }, |
267 | name: function() { return $translate.instant('action.unassign') }, | 319 | name: function() { return $translate.instant('action.unassign') }, |
268 | details: function() { return $translate.instant('dashboard.unassign-from-customer') }, | 320 | details: function() { return $translate.instant('dashboard.unassign-from-customer') }, |
269 | - icon: "assignment_return" | 321 | + icon: "assignment_return", |
322 | + isEnabled: function(dashboard) { | ||
323 | + return dashboard && !dashboard.assignedCustomer.isPublic; | ||
324 | + } | ||
325 | + } | ||
326 | + ); | ||
327 | + dashboardActionsList.push( | ||
328 | + { | ||
329 | + onAction: function ($event, item) { | ||
330 | + unassignFromCustomer($event, item, true); | ||
331 | + }, | ||
332 | + name: function() { return $translate.instant('action.unshare') }, | ||
333 | + details: function() { return $translate.instant('dashboard.make-private') }, | ||
334 | + icon: "reply", | ||
335 | + isEnabled: function(dashboard) { | ||
336 | + return dashboard && dashboard.assignedCustomer.isPublic; | ||
337 | + } | ||
270 | } | 338 | } |
271 | ); | 339 | ); |
272 | 340 | ||
@@ -418,15 +486,27 @@ export function DashboardsController(userService, dashboardService, customerServ | @@ -418,15 +486,27 @@ export function DashboardsController(userService, dashboardService, customerServ | ||
418 | assignToCustomer($event, dashboardIds); | 486 | assignToCustomer($event, dashboardIds); |
419 | } | 487 | } |
420 | 488 | ||
421 | - function unassignFromCustomer($event, dashboard) { | 489 | + function unassignFromCustomer($event, dashboard, isPublic) { |
422 | if ($event) { | 490 | if ($event) { |
423 | $event.stopPropagation(); | 491 | $event.stopPropagation(); |
424 | } | 492 | } |
493 | + var title; | ||
494 | + var content; | ||
495 | + var label; | ||
496 | + if (isPublic) { | ||
497 | + title = $translate.instant('dashboard.make-private-dashboard-title', {dashboardTitle: dashboard.title}); | ||
498 | + content = $translate.instant('dashboard.make-private-dashboard-text'); | ||
499 | + label = $translate.instant('dashboard.make-private-dashboard'); | ||
500 | + } else { | ||
501 | + title = $translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title}); | ||
502 | + content = $translate.instant('dashboard.unassign-dashboard-text'); | ||
503 | + label = $translate.instant('dashboard.unassign-dashboard'); | ||
504 | + } | ||
425 | var confirm = $mdDialog.confirm() | 505 | var confirm = $mdDialog.confirm() |
426 | .targetEvent($event) | 506 | .targetEvent($event) |
427 | - .title($translate.instant('dashboard.unassign-dashboard-title', {dashboardTitle: dashboard.title})) | ||
428 | - .htmlContent($translate.instant('dashboard.unassign-dashboard-text')) | ||
429 | - .ariaLabel($translate.instant('dashboard.unassign-dashboard')) | 507 | + .title(title) |
508 | + .htmlContent(content) | ||
509 | + .ariaLabel(label) | ||
430 | .cancel($translate.instant('action.no')) | 510 | .cancel($translate.instant('action.no')) |
431 | .ok($translate.instant('action.yes')); | 511 | .ok($translate.instant('action.yes')); |
432 | $mdDialog.show(confirm).then(function () { | 512 | $mdDialog.show(confirm).then(function () { |
@@ -436,6 +516,25 @@ export function DashboardsController(userService, dashboardService, customerServ | @@ -436,6 +516,25 @@ export function DashboardsController(userService, dashboardService, customerServ | ||
436 | }); | 516 | }); |
437 | } | 517 | } |
438 | 518 | ||
519 | + function makePublic($event, dashboard) { | ||
520 | + if ($event) { | ||
521 | + $event.stopPropagation(); | ||
522 | + } | ||
523 | + dashboardService.makeDashboardPublic(dashboard.id.id).then(function success(dashboard) { | ||
524 | + $mdDialog.show({ | ||
525 | + controller: 'MakeDashboardPublicDialogController', | ||
526 | + controllerAs: 'vm', | ||
527 | + templateUrl: makeDashboardPublicDialogTemplate, | ||
528 | + locals: {dashboard: dashboard}, | ||
529 | + parent: angular.element($document[0].body), | ||
530 | + fullscreen: true, | ||
531 | + targetEvent: $event | ||
532 | + }).then(function () { | ||
533 | + vm.grid.refreshList(); | ||
534 | + }); | ||
535 | + }); | ||
536 | + } | ||
537 | + | ||
439 | function exportDashboard($event, dashboard) { | 538 | function exportDashboard($event, dashboard) { |
440 | $event.stopPropagation(); | 539 | $event.stopPropagation(); |
441 | importExport.exportDashboard(dashboard.id.id); | 540 | importExport.exportDashboard(dashboard.id.id); |
@@ -24,7 +24,8 @@ | @@ -24,7 +24,8 @@ | ||
24 | dashboard-scope="vm.dashboardsScope" | 24 | dashboard-scope="vm.dashboardsScope" |
25 | the-form="vm.grid.detailsForm" | 25 | the-form="vm.grid.detailsForm" |
26 | on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" | 26 | on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" |
27 | - on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem)" | 27 | + on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)" |
28 | + on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" | ||
28 | on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" | 29 | on-export-dashboard="vm.exportDashboard(event, vm.grid.detailsConfig.currentItem)" |
29 | on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> | 30 | on-delete-dashboard="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-dashboard-details> |
30 | </tb-grid> | 31 | </tb-grid> |
@@ -37,6 +37,7 @@ export default function EditWidgetDirective($compile, $templateCache, widgetServ | @@ -37,6 +37,7 @@ export default function EditWidgetDirective($compile, $templateCache, widgetServ | ||
37 | scope.widgetConfig = scope.widget.config; | 37 | scope.widgetConfig = scope.widget.config; |
38 | var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; | 38 | var settingsSchema = widgetInfo.typeSettingsSchema || widgetInfo.settingsSchema; |
39 | var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; | 39 | var dataKeySettingsSchema = widgetInfo.typeDataKeySettingsSchema || widgetInfo.dataKeySettingsSchema; |
40 | + scope.isDataEnabled = !widgetInfo.useCustomDatasources; | ||
40 | if (!settingsSchema || settingsSchema === '') { | 41 | if (!settingsSchema || settingsSchema === '') { |
41 | scope.settingsSchema = {}; | 42 | scope.settingsSchema = {}; |
42 | } else { | 43 | } else { |
@@ -18,6 +18,7 @@ | @@ -18,6 +18,7 @@ | ||
18 | <fieldset ng-disabled="loading"> | 18 | <fieldset ng-disabled="loading"> |
19 | <tb-widget-config widget-type="widget.type" | 19 | <tb-widget-config widget-type="widget.type" |
20 | ng-model="widgetConfig" | 20 | ng-model="widgetConfig" |
21 | + is-data-enabled="isDataEnabled" | ||
21 | widget-settings-schema="settingsSchema" | 22 | widget-settings-schema="settingsSchema" |
22 | datakey-settings-schema="dataKeySettingsSchema" | 23 | datakey-settings-schema="dataKeySettingsSchema" |
23 | device-aliases="aliasesInfo.deviceAliases" | 24 | device-aliases="aliasesInfo.deviceAliases" |
@@ -35,7 +35,7 @@ import thingsboardItemBuffer from '../services/item-buffer.service'; | @@ -35,7 +35,7 @@ import thingsboardItemBuffer from '../services/item-buffer.service'; | ||
35 | import thingsboardImportExport from '../import-export'; | 35 | import thingsboardImportExport from '../import-export'; |
36 | 36 | ||
37 | import DashboardRoutes from './dashboard.routes'; | 37 | import DashboardRoutes from './dashboard.routes'; |
38 | -import {DashboardsController, DashboardCardController} from './dashboards.controller'; | 38 | +import {DashboardsController, DashboardCardController, MakeDashboardPublicDialogController} from './dashboards.controller'; |
39 | import DashboardController from './dashboard.controller'; | 39 | import DashboardController from './dashboard.controller'; |
40 | import DeviceAliasesController from './device-aliases.controller'; | 40 | import DeviceAliasesController from './device-aliases.controller'; |
41 | import AliasesDeviceSelectPanelController from './aliases-device-select-panel.controller'; | 41 | import AliasesDeviceSelectPanelController from './aliases-device-select-panel.controller'; |
@@ -69,6 +69,7 @@ export default angular.module('thingsboard.dashboard', [ | @@ -69,6 +69,7 @@ export default angular.module('thingsboard.dashboard', [ | ||
69 | .config(DashboardRoutes) | 69 | .config(DashboardRoutes) |
70 | .controller('DashboardsController', DashboardsController) | 70 | .controller('DashboardsController', DashboardsController) |
71 | .controller('DashboardCardController', DashboardCardController) | 71 | .controller('DashboardCardController', DashboardCardController) |
72 | + .controller('MakeDashboardPublicDialogController', MakeDashboardPublicDialogController) | ||
72 | .controller('DashboardController', DashboardController) | 73 | .controller('DashboardController', DashboardController) |
73 | .controller('DeviceAliasesController', DeviceAliasesController) | 74 | .controller('DeviceAliasesController', DeviceAliasesController) |
74 | .controller('AliasesDeviceSelectPanelController', AliasesDeviceSelectPanelController) | 75 | .controller('AliasesDeviceSelectPanelController', AliasesDeviceSelectPanelController) |
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 | +<md-dialog aria-label="{{ 'dashboard.make-public' | translate }}" style="min-width: 400px;"> | ||
19 | + <form> | ||
20 | + <md-toolbar> | ||
21 | + <div class="md-toolbar-tools"> | ||
22 | + <h2 translate="dashboard.public-dashboard-title"></h2> | ||
23 | + <span flex></span> | ||
24 | + <md-button class="md-icon-button" ng-click="vm.close()"> | ||
25 | + <ng-md-icon icon="close" aria-label="{{ 'dialog.close' | translate }}"></ng-md-icon> | ||
26 | + </md-button> | ||
27 | + </div> | ||
28 | + </md-toolbar> | ||
29 | + <md-dialog-content> | ||
30 | + <div id="make-dialog-public-content" class="md-dialog-content"> | ||
31 | + <md-content class="md-padding" layout="column"> | ||
32 | + <span translate="dashboard.public-dashboard-text" translate-values="{dashboardTitle: vm.dashboard.title, publicLink: vm.publicLink}"></span> | ||
33 | + <div layout="row" layout-align="start center"> | ||
34 | + <pre class="tb-highlight" flex><code>{{ vm.publicLink }}</code></pre> | ||
35 | + <md-button class="md-icon-button" | ||
36 | + ngclipboard | ||
37 | + data-clipboard-text="{{ vm.publicLink }}" | ||
38 | + ngclipboard-success="vm.onPublicLinkCopied(e)"> | ||
39 | + <md-icon md-svg-icon="mdi:clipboard-arrow-left"></md-icon> | ||
40 | + <md-tooltip md-direction="top"> | ||
41 | + {{ 'dashboard.copy-public-link' | translate }} | ||
42 | + </md-tooltip> | ||
43 | + </md-button> | ||
44 | + </div> | ||
45 | + <div class="tb-notice" translate>dashboard.public-dashboard-notice</div> | ||
46 | + </md-content> | ||
47 | + </div> | ||
48 | + </md-dialog-content> | ||
49 | + <md-dialog-actions layout="row"> | ||
50 | + <span flex></span> | ||
51 | + <md-button ng-click="vm.close()">{{ 'action.ok' | | ||
52 | + translate }} | ||
53 | + </md-button> | ||
54 | + </md-dialog-actions> | ||
55 | + </form> | ||
56 | +</md-dialog> |
@@ -52,7 +52,7 @@ export default function AddDevicesToCustomerController(deviceService, $mdDialog, | @@ -52,7 +52,7 @@ export default function AddDevicesToCustomerController(deviceService, $mdDialog, | ||
52 | fetchMoreItems_: function () { | 52 | fetchMoreItems_: function () { |
53 | if (vm.devices.hasNext && !vm.devices.pending) { | 53 | if (vm.devices.hasNext && !vm.devices.pending) { |
54 | vm.devices.pending = true; | 54 | vm.devices.pending = true; |
55 | - deviceService.getTenantDevices(vm.devices.nextPageLink).then( | 55 | + deviceService.getTenantDevices(vm.devices.nextPageLink, false).then( |
56 | function success(devices) { | 56 | function success(devices) { |
57 | vm.devices.data = vm.devices.data.concat(devices.data); | 57 | vm.devices.data = vm.devices.data.concat(devices.data); |
58 | vm.devices.nextPageLink = devices.nextPageLink; | 58 | vm.devices.nextPageLink = devices.nextPageLink; |
@@ -15,4 +15,5 @@ | @@ -15,4 +15,5 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | -<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.customerTitle}}'</div> | 18 | +<div class="tb-small" ng-show="vm.isAssignedToCustomer()">{{'device.assignedToCustomer' | translate}} '{{vm.item.assignedCustomer.title}}'</div> |
19 | +<div class="tb-small" ng-show="vm.isPublic()">{{'device.public' | translate}}</div> |
@@ -15,12 +15,15 @@ | @@ -15,12 +15,15 @@ | ||
15 | limitations under the License. | 15 | limitations under the License. |
16 | 16 | ||
17 | --> | 17 | --> |
18 | +<md-button ng-click="onMakePublic({event: $event})" | ||
19 | + ng-show="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer && !isPublic" | ||
20 | + class="md-raised md-primary">{{ 'device.make-public' | translate }}</md-button> | ||
18 | <md-button ng-click="onAssignToCustomer({event: $event})" | 21 | <md-button ng-click="onAssignToCustomer({event: $event})" |
19 | ng-show="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer" | 22 | ng-show="!isEdit && deviceScope === 'tenant' && !isAssignedToCustomer" |
20 | class="md-raised md-primary">{{ 'device.assign-to-customer' | translate }}</md-button> | 23 | class="md-raised md-primary">{{ 'device.assign-to-customer' | translate }}</md-button> |
21 | -<md-button ng-click="onUnassignFromCustomer({event: $event})" | 24 | +<md-button ng-click="onUnassignFromCustomer({event: $event, isPublic: isPublic})" |
22 | ng-show="!isEdit && (deviceScope === 'customer' || deviceScope === 'tenant') && isAssignedToCustomer" | 25 | ng-show="!isEdit && (deviceScope === 'customer' || deviceScope === 'tenant') && isAssignedToCustomer" |
23 | - class="md-raised md-primary">{{ 'device.unassign-from-customer' | translate }}</md-button> | 26 | + class="md-raised md-primary">{{ isPublic ? 'device.make-private' : 'device.unassign-from-customer' | translate }}</md-button> |
24 | <md-button ng-click="onManageCredentials({event: $event})" | 27 | <md-button ng-click="onManageCredentials({event: $event})" |
25 | ng-show="!isEdit" | 28 | ng-show="!isEdit" |
26 | class="md-raised md-primary">{{ (deviceScope === 'customer_user' ? 'device.view-credentials' : 'device.manage-credentials') | translate }}</md-button> | 29 | class="md-raised md-primary">{{ (deviceScope === 'customer_user' ? 'device.view-credentials' : 'device.manage-credentials') | translate }}</md-button> |
@@ -47,10 +50,14 @@ | @@ -47,10 +50,14 @@ | ||
47 | 50 | ||
48 | <md-content class="md-padding" layout="column"> | 51 | <md-content class="md-padding" layout="column"> |
49 | <md-input-container class="md-block" | 52 | <md-input-container class="md-block" |
50 | - ng-show="isAssignedToCustomer && deviceScope === 'tenant'"> | 53 | + ng-show="!isEdit && isAssignedToCustomer && !isPublic && deviceScope === 'tenant'"> |
51 | <label translate>device.assignedToCustomer</label> | 54 | <label translate>device.assignedToCustomer</label> |
52 | <input ng-model="assignedCustomer.title" disabled> | 55 | <input ng-model="assignedCustomer.title" disabled> |
53 | </md-input-container> | 56 | </md-input-container> |
57 | + <div class="tb-small" style="padding-bottom: 10px; padding-left: 2px;" | ||
58 | + ng-show="!isEdit && isPublic && (deviceScope === 'customer' || deviceScope === 'tenant')"> | ||
59 | + {{ 'device.device-public' | translate }} | ||
60 | + </div> | ||
54 | <fieldset ng-disabled="loading || !isEdit"> | 61 | <fieldset ng-disabled="loading || !isEdit"> |
55 | <md-input-container class="md-block"> | 62 | <md-input-container class="md-block"> |
56 | <label translate>device.name</label> | 63 | <label translate>device.name</label> |
@@ -24,7 +24,7 @@ import deviceCredentialsTemplate from './device-credentials.tpl.html'; | @@ -24,7 +24,7 @@ import deviceCredentialsTemplate from './device-credentials.tpl.html'; | ||
24 | /* eslint-enable import/no-unresolved, import/default */ | 24 | /* eslint-enable import/no-unresolved, import/default */ |
25 | 25 | ||
26 | /*@ngInject*/ | 26 | /*@ngInject*/ |
27 | -export function DeviceCardController($scope, types, customerService) { | 27 | +export function DeviceCardController(types) { |
28 | 28 | ||
29 | var vm = this; | 29 | var vm = this; |
30 | 30 | ||
@@ -32,28 +32,23 @@ export function DeviceCardController($scope, types, customerService) { | @@ -32,28 +32,23 @@ export function DeviceCardController($scope, types, customerService) { | ||
32 | 32 | ||
33 | vm.isAssignedToCustomer = function() { | 33 | vm.isAssignedToCustomer = function() { |
34 | if (vm.item && vm.item.customerId && vm.parentCtl.devicesScope === 'tenant' && | 34 | if (vm.item && vm.item.customerId && vm.parentCtl.devicesScope === 'tenant' && |
35 | - vm.item.customerId.id != vm.types.id.nullUid) { | 35 | + vm.item.customerId.id != vm.types.id.nullUid && !vm.item.assignedCustomer.isPublic) { |
36 | return true; | 36 | return true; |
37 | } | 37 | } |
38 | return false; | 38 | return false; |
39 | } | 39 | } |
40 | 40 | ||
41 | - $scope.$watch('vm.item', | ||
42 | - function() { | ||
43 | - if (vm.isAssignedToCustomer()) { | ||
44 | - customerService.getCustomerTitle(vm.item.customerId.id).then( | ||
45 | - function success(title) { | ||
46 | - vm.customerTitle = title; | ||
47 | - } | ||
48 | - ); | ||
49 | - } | 41 | + vm.isPublic = function() { |
42 | + if (vm.item && vm.item.assignedCustomer && vm.parentCtl.devicesScope === 'tenant' && vm.item.assignedCustomer.isPublic) { | ||
43 | + return true; | ||
50 | } | 44 | } |
51 | - ); | 45 | + return false; |
46 | + } | ||
52 | } | 47 | } |
53 | 48 | ||
54 | 49 | ||
55 | /*@ngInject*/ | 50 | /*@ngInject*/ |
56 | -export function DeviceController(userService, deviceService, customerService, $scope, $controller, $state, $stateParams, $document, $mdDialog, $q, $translate, types) { | 51 | +export function DeviceController(userService, deviceService, customerService, $state, $stateParams, $document, $mdDialog, $q, $translate, types) { |
57 | 52 | ||
58 | var customerId = $stateParams.customerId; | 53 | var customerId = $stateParams.customerId; |
59 | 54 | ||
@@ -107,6 +102,7 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -107,6 +102,7 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
107 | vm.devicesScope = $state.$current.data.devicesType; | 102 | vm.devicesScope = $state.$current.data.devicesType; |
108 | 103 | ||
109 | vm.assignToCustomer = assignToCustomer; | 104 | vm.assignToCustomer = assignToCustomer; |
105 | + vm.makePublic = makePublic; | ||
110 | vm.unassignFromCustomer = unassignFromCustomer; | 106 | vm.unassignFromCustomer = unassignFromCustomer; |
111 | vm.manageCredentials = manageCredentials; | 107 | vm.manageCredentials = manageCredentials; |
112 | 108 | ||
@@ -123,10 +119,20 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -123,10 +119,20 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
123 | vm.devicesScope = 'customer_user'; | 119 | vm.devicesScope = 'customer_user'; |
124 | customerId = user.customerId; | 120 | customerId = user.customerId; |
125 | } | 121 | } |
122 | + if (customerId) { | ||
123 | + vm.customerDevicesTitle = $translate.instant('customer.devices'); | ||
124 | + customerService.getShortCustomerInfo(customerId).then( | ||
125 | + function success(info) { | ||
126 | + if (info.isPublic) { | ||
127 | + vm.customerDevicesTitle = $translate.instant('customer.public-devices'); | ||
128 | + } | ||
129 | + } | ||
130 | + ); | ||
131 | + } | ||
126 | 132 | ||
127 | if (vm.devicesScope === 'tenant') { | 133 | if (vm.devicesScope === 'tenant') { |
128 | fetchDevicesFunction = function (pageLink) { | 134 | fetchDevicesFunction = function (pageLink) { |
129 | - return deviceService.getTenantDevices(pageLink); | 135 | + return deviceService.getTenantDevices(pageLink, true); |
130 | }; | 136 | }; |
131 | deleteDeviceFunction = function (deviceId) { | 137 | deleteDeviceFunction = function (deviceId) { |
132 | return deviceService.deleteDevice(deviceId); | 138 | return deviceService.deleteDevice(deviceId); |
@@ -135,6 +141,18 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -135,6 +141,18 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
135 | return {"topIndex": vm.topIndex}; | 141 | return {"topIndex": vm.topIndex}; |
136 | }; | 142 | }; |
137 | 143 | ||
144 | + deviceActionsList.push({ | ||
145 | + onAction: function ($event, item) { | ||
146 | + makePublic($event, item); | ||
147 | + }, | ||
148 | + name: function() { return $translate.instant('action.share') }, | ||
149 | + details: function() { return $translate.instant('device.make-public') }, | ||
150 | + icon: "share", | ||
151 | + isEnabled: function(device) { | ||
152 | + return device && (!device.customerId || device.customerId.id === types.id.nullUid); | ||
153 | + } | ||
154 | + }); | ||
155 | + | ||
138 | deviceActionsList.push( | 156 | deviceActionsList.push( |
139 | { | 157 | { |
140 | onAction: function ($event, item) { | 158 | onAction: function ($event, item) { |
@@ -152,17 +170,29 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -152,17 +170,29 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
152 | deviceActionsList.push( | 170 | deviceActionsList.push( |
153 | { | 171 | { |
154 | onAction: function ($event, item) { | 172 | onAction: function ($event, item) { |
155 | - unassignFromCustomer($event, item); | 173 | + unassignFromCustomer($event, item, false); |
156 | }, | 174 | }, |
157 | name: function() { return $translate.instant('action.unassign') }, | 175 | name: function() { return $translate.instant('action.unassign') }, |
158 | details: function() { return $translate.instant('device.unassign-from-customer') }, | 176 | details: function() { return $translate.instant('device.unassign-from-customer') }, |
159 | icon: "assignment_return", | 177 | icon: "assignment_return", |
160 | isEnabled: function(device) { | 178 | isEnabled: function(device) { |
161 | - return device && device.customerId && device.customerId.id !== types.id.nullUid; | 179 | + return device && device.customerId && device.customerId.id !== types.id.nullUid && !device.assignedCustomer.isPublic; |
162 | } | 180 | } |
163 | } | 181 | } |
164 | ); | 182 | ); |
165 | 183 | ||
184 | + deviceActionsList.push({ | ||
185 | + onAction: function ($event, item) { | ||
186 | + unassignFromCustomer($event, item, true); | ||
187 | + }, | ||
188 | + name: function() { return $translate.instant('action.unshare') }, | ||
189 | + details: function() { return $translate.instant('device.make-private') }, | ||
190 | + icon: "reply", | ||
191 | + isEnabled: function(device) { | ||
192 | + return device && device.customerId && device.customerId.id !== types.id.nullUid && device.assignedCustomer.isPublic; | ||
193 | + } | ||
194 | + }); | ||
195 | + | ||
166 | deviceActionsList.push( | 196 | deviceActionsList.push( |
167 | { | 197 | { |
168 | onAction: function ($event, item) { | 198 | onAction: function ($event, item) { |
@@ -213,7 +243,7 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -213,7 +243,7 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
213 | 243 | ||
214 | } else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') { | 244 | } else if (vm.devicesScope === 'customer' || vm.devicesScope === 'customer_user') { |
215 | fetchDevicesFunction = function (pageLink) { | 245 | fetchDevicesFunction = function (pageLink) { |
216 | - return deviceService.getCustomerDevices(customerId, pageLink); | 246 | + return deviceService.getCustomerDevices(customerId, pageLink, true); |
217 | }; | 247 | }; |
218 | deleteDeviceFunction = function (deviceId) { | 248 | deleteDeviceFunction = function (deviceId) { |
219 | return deviceService.unassignDeviceFromCustomer(deviceId); | 249 | return deviceService.unassignDeviceFromCustomer(deviceId); |
@@ -226,16 +256,33 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -226,16 +256,33 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
226 | deviceActionsList.push( | 256 | deviceActionsList.push( |
227 | { | 257 | { |
228 | onAction: function ($event, item) { | 258 | onAction: function ($event, item) { |
229 | - unassignFromCustomer($event, item); | 259 | + unassignFromCustomer($event, item, false); |
230 | }, | 260 | }, |
231 | name: function() { return $translate.instant('action.unassign') }, | 261 | name: function() { return $translate.instant('action.unassign') }, |
232 | details: function() { return $translate.instant('device.unassign-from-customer') }, | 262 | details: function() { return $translate.instant('device.unassign-from-customer') }, |
233 | - icon: "assignment_return" | 263 | + icon: "assignment_return", |
264 | + isEnabled: function(device) { | ||
265 | + return device && !device.assignedCustomer.isPublic; | ||
266 | + } | ||
234 | } | 267 | } |
235 | ); | 268 | ); |
236 | deviceActionsList.push( | 269 | deviceActionsList.push( |
237 | { | 270 | { |
238 | onAction: function ($event, item) { | 271 | onAction: function ($event, item) { |
272 | + unassignFromCustomer($event, item, true); | ||
273 | + }, | ||
274 | + name: function() { return $translate.instant('action.unshare') }, | ||
275 | + details: function() { return $translate.instant('device.make-private') }, | ||
276 | + icon: "reply", | ||
277 | + isEnabled: function(device) { | ||
278 | + return device && device.assignedCustomer.isPublic; | ||
279 | + } | ||
280 | + } | ||
281 | + ); | ||
282 | + | ||
283 | + deviceActionsList.push( | ||
284 | + { | ||
285 | + onAction: function ($event, item) { | ||
239 | manageCredentials($event, item); | 286 | manageCredentials($event, item); |
240 | }, | 287 | }, |
241 | name: function() { return $translate.instant('device.credentials') }, | 288 | name: function() { return $translate.instant('device.credentials') }, |
@@ -365,7 +412,7 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -365,7 +412,7 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
365 | $event.stopPropagation(); | 412 | $event.stopPropagation(); |
366 | } | 413 | } |
367 | var pageSize = 10; | 414 | var pageSize = 10; |
368 | - deviceService.getTenantDevices({limit: pageSize, textSearch: ''}).then( | 415 | + deviceService.getTenantDevices({limit: pageSize, textSearch: ''}, false).then( |
369 | function success(_devices) { | 416 | function success(_devices) { |
370 | var devices = { | 417 | var devices = { |
371 | pageSize: pageSize, | 418 | pageSize: pageSize, |
@@ -404,15 +451,27 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -404,15 +451,27 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
404 | assignToCustomer($event, deviceIds); | 451 | assignToCustomer($event, deviceIds); |
405 | } | 452 | } |
406 | 453 | ||
407 | - function unassignFromCustomer($event, device) { | 454 | + function unassignFromCustomer($event, device, isPublic) { |
408 | if ($event) { | 455 | if ($event) { |
409 | $event.stopPropagation(); | 456 | $event.stopPropagation(); |
410 | } | 457 | } |
458 | + var title; | ||
459 | + var content; | ||
460 | + var label; | ||
461 | + if (isPublic) { | ||
462 | + title = $translate.instant('device.make-private-device-title', {deviceName: device.name}); | ||
463 | + content = $translate.instant('device.make-private-device-text'); | ||
464 | + label = $translate.instant('device.make-private'); | ||
465 | + } else { | ||
466 | + title = $translate.instant('device.unassign-device-title', {deviceName: device.name}); | ||
467 | + content = $translate.instant('device.unassign-device-text'); | ||
468 | + label = $translate.instant('device.unassign-device'); | ||
469 | + } | ||
411 | var confirm = $mdDialog.confirm() | 470 | var confirm = $mdDialog.confirm() |
412 | .targetEvent($event) | 471 | .targetEvent($event) |
413 | - .title($translate.instant('device.unassign-device-title', {deviceName: device.name})) | ||
414 | - .htmlContent($translate.instant('device.unassign-device-text')) | ||
415 | - .ariaLabel($translate.instant('device.unassign-device')) | 472 | + .title(title) |
473 | + .htmlContent(content) | ||
474 | + .ariaLabel(label) | ||
416 | .cancel($translate.instant('action.no')) | 475 | .cancel($translate.instant('action.no')) |
417 | .ok($translate.instant('action.yes')); | 476 | .ok($translate.instant('action.yes')); |
418 | $mdDialog.show(confirm).then(function () { | 477 | $mdDialog.show(confirm).then(function () { |
@@ -441,6 +500,24 @@ export function DeviceController(userService, deviceService, customerService, $s | @@ -441,6 +500,24 @@ export function DeviceController(userService, deviceService, customerService, $s | ||
441 | }); | 500 | }); |
442 | } | 501 | } |
443 | 502 | ||
503 | + function makePublic($event, device) { | ||
504 | + if ($event) { | ||
505 | + $event.stopPropagation(); | ||
506 | + } | ||
507 | + var confirm = $mdDialog.confirm() | ||
508 | + .targetEvent($event) | ||
509 | + .title($translate.instant('device.make-public-device-title', {deviceName: device.name})) | ||
510 | + .htmlContent($translate.instant('device.make-public-device-text')) | ||
511 | + .ariaLabel($translate.instant('device.make-public')) | ||
512 | + .cancel($translate.instant('action.no')) | ||
513 | + .ok($translate.instant('action.yes')); | ||
514 | + $mdDialog.show(confirm).then(function () { | ||
515 | + deviceService.makeDevicePublic(device.id.id).then(function success() { | ||
516 | + vm.grid.refreshList(); | ||
517 | + }); | ||
518 | + }); | ||
519 | + } | ||
520 | + | ||
444 | function manageCredentials($event, device) { | 521 | function manageCredentials($event, device) { |
445 | if ($event) { | 522 | if ($event) { |
446 | $event.stopPropagation(); | 523 | $event.stopPropagation(); |
@@ -26,6 +26,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | @@ -26,6 +26,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | ||
26 | element.html(template); | 26 | element.html(template); |
27 | 27 | ||
28 | scope.isAssignedToCustomer = false; | 28 | scope.isAssignedToCustomer = false; |
29 | + scope.isPublic = false; | ||
29 | scope.assignedCustomer = null; | 30 | scope.assignedCustomer = null; |
30 | 31 | ||
31 | scope.deviceCredentials = null; | 32 | scope.deviceCredentials = null; |
@@ -41,13 +42,15 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | @@ -41,13 +42,15 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | ||
41 | } | 42 | } |
42 | if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) { | 43 | if (scope.device.customerId && scope.device.customerId.id !== types.id.nullUid) { |
43 | scope.isAssignedToCustomer = true; | 44 | scope.isAssignedToCustomer = true; |
44 | - customerService.getCustomer(scope.device.customerId.id).then( | 45 | + customerService.getShortCustomerInfo(scope.device.customerId.id).then( |
45 | function success(customer) { | 46 | function success(customer) { |
46 | scope.assignedCustomer = customer; | 47 | scope.assignedCustomer = customer; |
48 | + scope.isPublic = customer.isPublic; | ||
47 | } | 49 | } |
48 | ); | 50 | ); |
49 | } else { | 51 | } else { |
50 | scope.isAssignedToCustomer = false; | 52 | scope.isAssignedToCustomer = false; |
53 | + scope.isPublic = false; | ||
51 | scope.assignedCustomer = null; | 54 | scope.assignedCustomer = null; |
52 | } | 55 | } |
53 | } | 56 | } |
@@ -72,6 +75,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | @@ -72,6 +75,7 @@ export default function DeviceDirective($compile, $templateCache, toast, $transl | ||
72 | deviceScope: '=', | 75 | deviceScope: '=', |
73 | theForm: '=', | 76 | theForm: '=', |
74 | onAssignToCustomer: '&', | 77 | onAssignToCustomer: '&', |
78 | + onMakePublic: '&', | ||
75 | onUnassignFromCustomer: '&', | 79 | onUnassignFromCustomer: '&', |
76 | onManageCredentials: '&', | 80 | onManageCredentials: '&', |
77 | onDeleteDevice: '&' | 81 | onDeleteDevice: '&' |
@@ -61,7 +61,7 @@ export default function DeviceRoutes($stateProvider) { | @@ -61,7 +61,7 @@ export default function DeviceRoutes($stateProvider) { | ||
61 | pageTitle: 'customer.devices' | 61 | pageTitle: 'customer.devices' |
62 | }, | 62 | }, |
63 | ncyBreadcrumb: { | 63 | ncyBreadcrumb: { |
64 | - label: '{"icon": "devices_other", "label": "customer.devices"}' | 64 | + label: '{"icon": "devices_other", "label": "{{ vm.customerDevicesTitle }}", "translate": "false"}' |
65 | } | 65 | } |
66 | }); | 66 | }); |
67 | 67 |
@@ -27,7 +27,8 @@ | @@ -27,7 +27,8 @@ | ||
27 | device-scope="vm.devicesScope" | 27 | device-scope="vm.devicesScope" |
28 | the-form="vm.grid.detailsForm" | 28 | the-form="vm.grid.detailsForm" |
29 | on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" | 29 | on-assign-to-customer="vm.assignToCustomer(event, [ vm.grid.detailsConfig.currentItem.id.id ])" |
30 | - on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem)" | 30 | + on-make-public="vm.makePublic(event, vm.grid.detailsConfig.currentItem)" |
31 | + on-unassign-from-customer="vm.unassignFromCustomer(event, vm.grid.detailsConfig.currentItem, isPublic)" | ||
31 | on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)" | 32 | on-manage-credentials="vm.manageCredentials(event, vm.grid.detailsConfig.currentItem)" |
32 | on-delete-device="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-device> | 33 | on-delete-device="vm.grid.deleteItem(event, vm.grid.detailsConfig.currentItem)"></tb-device> |
33 | </md-tab> | 34 | </md-tab> |